You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
502 lines
18 KiB
502 lines
18 KiB
/******************************************************************** |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
Copyright (C) 2011 Thomas Lübking <thomas.luebking@web.de> |
|
|
|
This program is free software; you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation; either version 2 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
*********************************************************************/ |
|
|
|
#include "kwinanimationeffect.h" |
|
#include "anidata_p.h" |
|
|
|
#include <QTime> |
|
#include <QTimer> |
|
|
|
namespace KWin { |
|
struct AnimationEffectPrivate { |
|
public: |
|
AnimationEffectPrivate() { m_animated = false; } |
|
AnimationEffect::AniMap m_animations; |
|
EffectWindowList m_zombies; |
|
bool m_animated; |
|
}; |
|
} |
|
|
|
using namespace KWin; |
|
|
|
AnimationEffect::AnimationEffect() : d_ptr(new AnimationEffectPrivate()) |
|
{ |
|
Q_D(AnimationEffect); |
|
d->m_animated = false; |
|
/* this is the same as the QTimer::singleShot(0, SLOT(init())) kludge |
|
* defering the init and esp. the connection to the windowClosed slot */ |
|
QMetaObject::invokeMethod( this, "init", Qt::QueuedConnection ); |
|
} |
|
|
|
void AnimationEffect::init() |
|
{ |
|
/* by connecting the signal from a slot AFTER the inheriting class constructor had the chance to |
|
* connect it we can provide auto-referencing of animated and closed windows, since at the time |
|
* our slot will be called, the slot of the subclass has been (SIGNAL/SLOT connections are FIFO) |
|
* and has pot. started an animation so we have the window in our hash :) */ |
|
connect ( effects, SIGNAL(windowClosed(KWin::EffectWindow*)), SLOT(_windowClosed(KWin::EffectWindow*)) ); |
|
connect ( effects, SIGNAL(windowDeleted(KWin::EffectWindow*)), SLOT(_windowDeleted(KWin::EffectWindow*)) ); |
|
} |
|
|
|
bool AnimationEffect::isActive() const |
|
{ |
|
Q_D(const AnimationEffect); |
|
return !d->m_animations.isEmpty(); |
|
} |
|
|
|
|
|
#define RELATIVE_XY(_FIELD_) const bool relative[2] = { metaData(Relative##_FIELD_##X, meta), metaData(Relative##_FIELD_##Y, meta) } |
|
|
|
void AnimationEffect::animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from ) |
|
{ |
|
const bool waitAtSource = from.isValid(); |
|
if (a < NonFloatBase) { |
|
if (a == Scale) { |
|
QRect area = effects->clientArea(ScreenArea , w); |
|
if (from.isValid()) { |
|
RELATIVE_XY(Source); |
|
from.set( relative[0] ? from[0] * area.width() / w->width() : from[0], |
|
relative[1] ? from[1] * area.height() / w->height() : from[1] ); |
|
} |
|
if (to.isValid()) { |
|
RELATIVE_XY(Target); |
|
to.set( relative[0] ? to[0] * area.width() / w->width() : to[0], |
|
relative[1] ? to[1] * area.height() / w->height() : to[1] ); |
|
} |
|
} else if (a == Rotation) { |
|
if (!from.isValid()) { |
|
setMetaData( SourceAnchor, metaData(TargetAnchor, meta), meta ); |
|
from.set(0.0,0.0); |
|
} |
|
if (!to.isValid()) { |
|
setMetaData( TargetAnchor, metaData(SourceAnchor, meta), meta ); |
|
to.set(0.0,0.0); |
|
} |
|
} |
|
if (!from.isValid()) |
|
from.set(1.0,1.0); |
|
if (!to.isValid()) |
|
to.set(1.0,1.0); |
|
|
|
|
|
} else if (a == Position) { |
|
QRect area = effects->clientArea(ScreenArea , w); |
|
QPoint pt = w->geometry().bottomRight(); // cannot be < 0 ;-) |
|
if (from.isValid()) { |
|
RELATIVE_XY(Source); |
|
from.set( relative[0] ? area.x() + from[0] * area.width() : from[0], |
|
relative[1] ? area.y() + from[1] * area.height() : from[1] ); |
|
} else { |
|
from.set(pt.x(), pt.y()); |
|
setMetaData( SourceAnchor, AnimationEffect::Bottom|AnimationEffect::Right, meta ); |
|
} |
|
|
|
if (to.isValid()) { |
|
RELATIVE_XY(Target); |
|
to.set( relative[0] ? area.x() + to[0] * area.width() : to[0], |
|
relative[1] ? area.y() + to[1] * area.height() : to[1] ); |
|
} else { |
|
to.set(pt.x(), pt.y()); |
|
setMetaData( TargetAnchor, AnimationEffect::Bottom|AnimationEffect::Right, meta ); |
|
} |
|
|
|
|
|
} else if (a == Size) { |
|
QRect area = effects->clientArea(ScreenArea , w); |
|
if (from.isValid()) { |
|
RELATIVE_XY(Source); |
|
from.set( relative[0] ? from[0] * area.width() : from[0], |
|
relative[1] ? from[1] * area.height() : from[1] ); |
|
} |
|
else |
|
from.set(w->width(), w->height()); |
|
|
|
if (to.isValid()) { |
|
RELATIVE_XY(Target); |
|
to.set( relative[0] ? to[0] * area.width() : to[0], |
|
relative[1] ? to[1] * area.height() : to[1] ); |
|
} |
|
else |
|
from.set(w->width(), w->height()); |
|
|
|
|
|
} else if (a == Translation) { |
|
QRect area = w->rect(); |
|
if (from.isValid()) { |
|
RELATIVE_XY(Source); |
|
from.set( relative[0] ? from[0] * area.width() : from[0], |
|
relative[1] ? from[1] * area.height() : from[1] ); |
|
} else |
|
from.set(0.0, 0.0); |
|
|
|
if (to.isValid()) { |
|
RELATIVE_XY(Target); |
|
to.set( relative[0] ? to[0] * area.width() : to[0], |
|
relative[1] ? to[1] * area.height() : to[1] ); |
|
} else |
|
to.set(0.0, 0.0); |
|
} |
|
|
|
Q_D(AnimationEffect); |
|
AniMap::iterator it = d->m_animations.find(w); |
|
if (it == d->m_animations.end()) |
|
it = d->m_animations.insert(w, QList<AniData>()); |
|
it->append(AniData(a, meta, ms, to, curve, delay, from, waitAtSource)); |
|
|
|
if (delay > 0) |
|
QTimer::singleShot(delay, this, SLOT(triggerRepaint())); |
|
else |
|
triggerRepaint(); |
|
} |
|
|
|
void AnimationEffect::prePaintScreen( ScreenPrePaintData& data, int time ) |
|
{ |
|
Q_D(AnimationEffect); |
|
if (d->m_animations.isEmpty()) { |
|
effects->prePaintScreen(data, time); |
|
return; |
|
} |
|
|
|
AniMap::iterator entry = d->m_animations.begin(); |
|
d->m_animated = false; |
|
while (entry != d->m_animations.end()) { |
|
QList<AniData>::iterator anim = entry->begin(); |
|
while (anim != entry->end()) { |
|
if (QTime::currentTime() < anim->startTime) { |
|
if (!anim->waitAtSource) { |
|
++anim; |
|
continue; |
|
} |
|
} else |
|
anim->addTime(time); |
|
|
|
if (anim->time < anim->duration) { |
|
d->m_animated = true; |
|
++anim; |
|
} |
|
else { |
|
animationEnded(entry.key(), anim->attribute); |
|
anim = entry->erase(anim); |
|
} |
|
} |
|
if (entry->isEmpty()) { |
|
const int i = d->m_zombies.indexOf(entry.key()); |
|
if ( i > -1 ) { |
|
d->m_zombies.removeAt( i ); |
|
entry.key()->unrefWindow(); |
|
} |
|
entry = d->m_animations.erase(entry); |
|
} |
|
else |
|
++entry; |
|
} |
|
|
|
if ( d->m_animated ) |
|
data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; |
|
|
|
// janitorial... |
|
if ( !(d->m_animations.count() || d->m_zombies.isEmpty()) ) |
|
{ |
|
foreach ( EffectWindow *w, d->m_zombies ) |
|
w->unrefWindow(); |
|
d->m_zombies.clear(); |
|
} |
|
|
|
effects->prePaintScreen(data, time); |
|
} |
|
|
|
void AnimationEffect::prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time ) |
|
{ |
|
Q_D(AnimationEffect); |
|
if ( d->m_animated ) { |
|
AniMap::const_iterator entry = d->m_animations.constFind( w ); |
|
if ( entry != d->m_animations.constEnd() ) { |
|
bool isUsed = false; |
|
for (QList<AniData>::const_iterator anim = entry->constBegin(); anim != entry->constEnd(); ++anim) { |
|
if (QTime::currentTime() < anim->startTime && !anim->waitAtSource) |
|
continue; |
|
|
|
isUsed = true; |
|
if (anim->attribute == Opacity) |
|
data.setTranslucent(); |
|
else { |
|
data.setTransformed(); |
|
data.mask |= PAINT_WINDOW_TRANSFORMED; |
|
} |
|
} |
|
if ( isUsed ) { |
|
if ( w->isMinimized() ) |
|
w->enablePainting( EffectWindow::PAINT_DISABLED_BY_MINIMIZE ); |
|
else if ( w->isDeleted() ) |
|
w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DELETE ); |
|
else if ( !w->isOnCurrentDesktop() ) |
|
w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); |
|
if( !w->isPaintingEnabled() && !effects->activeFullScreenEffect() ) |
|
w->addRepaintFull(); |
|
} |
|
} |
|
} |
|
effects->prePaintWindow( w, data, time ); |
|
} |
|
|
|
static inline float geometryCompensation(int flags, float v) |
|
{ |
|
if (flags & (AnimationEffect::Left|AnimationEffect::Top)) |
|
return 0.0; // no compensation required |
|
if (flags & (AnimationEffect::Right|AnimationEffect::Bottom)) |
|
return 1.0 - v; // full compensation |
|
return 0.5 * (1.0 - v); // half compensation |
|
} |
|
|
|
|
|
static int xCoord(const QRect &r, int flag) { |
|
if (flag & AnimationEffect::Left) |
|
return r.x(); |
|
else if (flag & AnimationEffect::Right) |
|
return r.right(); |
|
else |
|
return r.x() + r.width()/2; |
|
} |
|
|
|
static int yCoord(const QRect &r, int flag) { |
|
if (flag & AnimationEffect::Top) |
|
return r.y(); |
|
else if (flag & AnimationEffect::Bottom) |
|
return r.bottom(); |
|
else |
|
return r.y() + r.height()/2; |
|
} |
|
|
|
void AnimationEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) |
|
{ |
|
Q_D(AnimationEffect); |
|
if ( d->m_animated ) { |
|
AniMap::const_iterator entry = d->m_animations.constFind( w ); |
|
if ( entry != d->m_animations.constEnd() ) { |
|
for ( QList<AniData>::const_iterator anim = entry->constBegin(); anim != entry->constEnd(); ++anim ) { |
|
|
|
if (QTime::currentTime() < anim->startTime && !anim->waitAtSource) |
|
continue; |
|
|
|
switch (anim->attribute) { |
|
case Opacity: |
|
data.opacity *= interpolated(*anim); break; |
|
case Brightness: |
|
data.brightness *= interpolated(*anim); break; |
|
case Saturation: |
|
data.saturation *= interpolated(*anim); break; |
|
case Scale: { |
|
const QSize sz = w->geometry().size(); |
|
float f1(1.0), f2(0.0); |
|
if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) { // scale x |
|
f1 = interpolated(*anim, 0); |
|
f2 = geometryCompensation( anim->meta & AnimationEffect::Horizontal, f1 ); |
|
data.xTranslate += f2 * sz.width(); |
|
data.xScale *= f1; |
|
} |
|
if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) { // scale y |
|
if (!anim->isOneDimensional()) { |
|
f1 = interpolated(*anim, 1); |
|
f2 = geometryCompensation( anim->meta & AnimationEffect::Vertical, f1 ); |
|
} else if ( ((anim->meta & AnimationEffect::Vertical)>>1) != (anim->meta & AnimationEffect::Horizontal) ) |
|
f2 = geometryCompensation( anim->meta & AnimationEffect::Vertical, f1 ); |
|
data.yTranslate += f2 * sz.height(); |
|
data.yScale *= f1; |
|
} |
|
break; |
|
} |
|
case Translation: |
|
data.xTranslate += interpolated(*anim, 0); |
|
data.yTranslate += interpolated(*anim, 1); |
|
break; |
|
case Size: { |
|
FPx2 dest = anim->from + progress(*anim) * (anim->to - anim->from); |
|
const QSize sz = w->geometry().size(); |
|
float f; |
|
if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) { // resize x |
|
f = dest[0]/sz.width(); |
|
data.xTranslate += geometryCompensation( anim->meta & AnimationEffect::Horizontal, f ) * sz.width(); |
|
data.xScale *= f; |
|
} |
|
if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) { // resize y |
|
f = dest[1]/sz.height(); |
|
data.yTranslate += geometryCompensation( anim->meta & AnimationEffect::Vertical, f ) * sz.height(); |
|
data.yScale *= f; |
|
} |
|
break; |
|
} |
|
case Position: { |
|
const QRect geo = w->geometry(); |
|
const float prgrs = progress(*anim); |
|
if ( anim->from[0] >= 0.0 && anim->to[0] >= 0.0 ) { |
|
float dest = interpolated(*anim, 0); |
|
const int x[2] = { xCoord(geo, metaData(SourceAnchor, anim->meta)), |
|
xCoord(geo, metaData(TargetAnchor, anim->meta)) }; |
|
data.xTranslate += dest - (x[0] + prgrs*(x[1] - x[0])); |
|
} |
|
if ( anim->from[1] >= 0.0 && anim->to[1] >= 0.0 ) { |
|
float dest = interpolated(*anim, 1); |
|
const int y[2] = { yCoord(geo, metaData(SourceAnchor, anim->meta)), |
|
yCoord(geo, metaData(TargetAnchor, anim->meta)) }; |
|
data.yTranslate += dest - (y[0] + prgrs*(y[1] - y[0])); |
|
} |
|
break; |
|
} |
|
case Rotation: { |
|
RotationData rot; |
|
rot.axis = (RotationData::RotationAxis)metaData(Axis, anim->meta); |
|
const float prgrs = progress(*anim); |
|
rot.angle = anim->from[0] + prgrs*(anim->to[0] - anim->from[0]); |
|
|
|
const QRect geo = w->rect(); |
|
const uint sAnchor = metaData(SourceAnchor, anim->meta), |
|
tAnchor = metaData(TargetAnchor, anim->meta); |
|
QPointF pt(xCoord(geo, sAnchor), yCoord(geo, sAnchor)); |
|
|
|
if (tAnchor != sAnchor) { |
|
QPointF pt2(xCoord(geo, tAnchor), yCoord(geo, tAnchor)); |
|
pt += static_cast<qreal>(prgrs)*(pt2 - pt); |
|
} |
|
|
|
rot.xRotationPoint = pt.x(); |
|
rot.yRotationPoint = pt.y(); |
|
data.rotation = &rot; |
|
break; |
|
} |
|
case Generic: |
|
genericAnimation(w, data, progress(*anim), anim->meta); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
effects->paintWindow( w, mask, region, data ); |
|
} |
|
|
|
void AnimationEffect::postPaintScreen() |
|
{ |
|
Q_D(AnimationEffect); |
|
if ( d->m_animated ) |
|
effects->addRepaintFull(); |
|
effects->postPaintScreen(); |
|
} |
|
|
|
float AnimationEffect::interpolated( const AniData &a, int i ) const |
|
{ |
|
if (QTime::currentTime() < a.startTime) |
|
return a.from[i]; |
|
return a.from[i] + a.curve.valueForProgress( ((float)a.time)/a.duration )*(a.to[i] - a.from[i]); |
|
} |
|
|
|
float AnimationEffect::progress( const AniData &a ) const |
|
{ |
|
if (QTime::currentTime() < a.startTime) |
|
return 0.0; |
|
return a.curve.valueForProgress( ((float)a.time)/a.duration ); |
|
} |
|
|
|
|
|
// TODO - get this out of the header - the functionpointer usage of QEasingCurve somehow sucks ;-) |
|
// qreal AnimationEffect::qecGaussian(qreal progress) // exp(-5*(2*x-1)^2) |
|
// { |
|
// progress = 2*progress - 1; |
|
// progress *= -5*progress; |
|
// return qExp(progress); |
|
// } |
|
|
|
int AnimationEffect::metaData( MetaType type, uint meta ) |
|
{ |
|
switch (type) { |
|
case SourceAnchor: |
|
return ((meta>>5) & 0x1f); |
|
case TargetAnchor: |
|
return (meta& 0x1f); |
|
case RelativeSourceX: |
|
case RelativeSourceY: |
|
case RelativeTargetX: |
|
case RelativeTargetY: { |
|
const int shift = 10 + type - RelativeSourceX; |
|
return ((meta>>shift) & 1); |
|
} |
|
case Axis: |
|
return ((meta>>10) & 3); |
|
default: |
|
return 0; |
|
} |
|
} |
|
|
|
void AnimationEffect::setMetaData( MetaType type, uint value, uint &meta ) |
|
{ |
|
switch (type) { |
|
case SourceAnchor: |
|
meta &= ~(0x1f<<5); |
|
meta |= ((value & 0x1f)<<5); |
|
break; |
|
case TargetAnchor: |
|
meta &= ~(0x1f); |
|
meta |= (value & 0x1f); |
|
break; |
|
case RelativeSourceX: |
|
case RelativeSourceY: |
|
case RelativeTargetX: |
|
case RelativeTargetY: { |
|
const int shift = 10 + type - RelativeSourceX; |
|
if (value) |
|
meta |= (1<<shift); |
|
else |
|
meta &= ~(1<<shift); |
|
break; |
|
} |
|
case Axis: |
|
meta &= ~(3<<10); |
|
meta |= ((value & 3)<<10); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
void AnimationEffect::triggerRepaint() |
|
{ |
|
Q_D(AnimationEffect); |
|
if (!d->m_animated) |
|
effects->addRepaintFull(); |
|
} |
|
|
|
|
|
void AnimationEffect::_windowClosed( EffectWindow* w ) |
|
{ |
|
Q_D(AnimationEffect); |
|
if (d->m_animations.contains(w) && !d->m_zombies.contains(w)) { |
|
w->refWindow(); |
|
d->m_zombies << w; |
|
} |
|
} |
|
|
|
void AnimationEffect::_windowDeleted( EffectWindow* w ) |
|
{ |
|
Q_D(AnimationEffect); |
|
d->m_animations.remove( w ); |
|
} |
|
|
|
|
|
#include "moc_kwinanimationeffect.cpp"
|
|
|