1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2011 Thomas Lübking <thomas.luebking@web.de>
6     SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
7 
8     SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 
11 #include "kwinanimationeffect.h"
12 #include "anidata_p.h"
13 
14 #include <QDateTime>
15 #include <QTimer>
16 #include <QtDebug>
17 #include <QVector3D>
18 
19 namespace KWin
20 {
21 
operator <<(QDebug dbg,const KWin::FPx2 & fpx2)22 QDebug operator<<(QDebug dbg, const KWin::FPx2 &fpx2)
23 {
24     dbg.nospace() << fpx2[0] << "," << fpx2[1] << QString(fpx2.isValid() ? QStringLiteral(" (valid)") : QStringLiteral(" (invalid)"));
25     return dbg.space();
26 }
27 
28 QElapsedTimer AnimationEffect::s_clock;
29 
30 class AnimationEffectPrivate {
31 public:
AnimationEffectPrivate()32     AnimationEffectPrivate()
33     {
34         m_animationsTouched = m_isInitialized = false;
35         m_justEndedAnimation = 0;
36     }
37     AnimationEffect::AniMap m_animations;
38     static quint64 m_animCounter;
39     quint64 m_justEndedAnimation; // protect against cancel
40     QWeakPointer<FullScreenEffectLock> m_fullScreenEffectLock;
41     bool m_needSceneRepaint, m_animationsTouched, m_isInitialized;
42 };
43 
44 quint64 AnimationEffectPrivate::m_animCounter = 0;
45 
AnimationEffect()46 AnimationEffect::AnimationEffect() : d_ptr(new AnimationEffectPrivate())
47 {
48     Q_D(AnimationEffect);
49     if (!s_clock.isValid())
50         s_clock.start();
51     /* this is the same as the QTimer::singleShot(0, SLOT(init())) kludge
52      * defering the init and esp. the connection to the windowClosed slot */
53     QMetaObject::invokeMethod(this, &AnimationEffect::init, Qt::QueuedConnection);
54 }
55 
~AnimationEffect()56 AnimationEffect::~AnimationEffect()
57 {
58     delete d_ptr;
59 }
60 
init()61 void AnimationEffect::init()
62 {
63     Q_D(AnimationEffect);
64     if (d->m_isInitialized)
65         return; // not more than once, please
66     d->m_isInitialized = true;
67     /* by connecting the signal from a slot AFTER the inheriting class constructor had the chance to
68      * connect it we can provide auto-referencing of animated and closed windows, since at the time
69      * our slot will be called, the slot of the subclass has been (SIGNAL/SLOT connections are FIFO)
70      * and has pot. started an animation so we have the window in our hash :) */
71     connect(effects, &EffectsHandler::windowClosed, this, &AnimationEffect::_windowClosed);
72     connect(effects, &EffectsHandler::windowDeleted, this, &AnimationEffect::_windowDeleted);
73 }
74 
isActive() const75 bool AnimationEffect::isActive() const
76 {
77     Q_D(const AnimationEffect);
78     return !d->m_animations.isEmpty() && !effects->isScreenLocked();
79 }
80 
81 
82 #define RELATIVE_XY(_FIELD_) const bool relative[2] = { static_cast<bool>(metaData(Relative##_FIELD_##X, meta)), \
83                                                         static_cast<bool>(metaData(Relative##_FIELD_##Y, meta)) }
84 
validate(Attribute a,uint & meta,FPx2 * from,FPx2 * to,const EffectWindow * w) const85 void AnimationEffect::validate(Attribute a, uint &meta, FPx2 *from, FPx2 *to, const EffectWindow *w) const
86 {
87     if (a < NonFloatBase) {
88         if (a == Scale) {
89             QRect area = effects->clientArea(ScreenArea , w);
90             if (from && from->isValid()) {
91                 RELATIVE_XY(Source);
92                 from->set(relative[0] ? (*from)[0] * area.width() / w->width() : (*from)[0],
93                           relative[1] ? (*from)[1] * area.height() / w->height() : (*from)[1]);
94             }
95             if (to && to->isValid()) {
96                 RELATIVE_XY(Target);
97                 to->set(relative[0] ? (*to)[0] * area.width() / w->width() : (*to)[0],
98                         relative[1] ? (*to)[1] * area.height() / w->height() : (*to)[1] );
99             }
100         } else if (a == Rotation) {
101             if (from && !from->isValid()) {
102                 setMetaData(SourceAnchor, metaData(TargetAnchor, meta), meta);
103                 from->set(0.0,0.0);
104             }
105             if (to && !to->isValid()) {
106                 setMetaData(TargetAnchor, metaData(SourceAnchor, meta), meta);
107                 to->set(0.0,0.0);
108             }
109         }
110         if (from && !from->isValid())
111             from->set(1.0,1.0);
112         if (to && !to->isValid())
113             to->set(1.0,1.0);
114 
115 
116     } else if (a == Position) {
117         QRect area = effects->clientArea(ScreenArea , w);
118         QPoint pt = w->frameGeometry().bottomRight(); // cannot be < 0 ;-)
119         if (from) {
120             if (from->isValid()) {
121                 RELATIVE_XY(Source);
122                 from->set(relative[0] ? area.x() + (*from)[0] * area.width() : (*from)[0],
123                         relative[1] ? area.y() + (*from)[1] * area.height() : (*from)[1]);
124             } else {
125                 from->set(pt.x(), pt.y());
126                 setMetaData(SourceAnchor, AnimationEffect::Bottom|AnimationEffect::Right, meta);
127             }
128         }
129 
130         if (to) {
131             if (to->isValid()) {
132                 RELATIVE_XY(Target);
133                 to->set(relative[0] ? area.x() + (*to)[0] * area.width() : (*to)[0],
134                         relative[1] ? area.y() + (*to)[1] * area.height() : (*to)[1]);
135             } else {
136                 to->set(pt.x(), pt.y());
137                 setMetaData( TargetAnchor, AnimationEffect::Bottom|AnimationEffect::Right, meta );
138             }
139         }
140 
141 
142     } else if (a == Size) {
143         QRect area = effects->clientArea(ScreenArea , w);
144         if (from) {
145             if (from->isValid()) {
146                 RELATIVE_XY(Source);
147                 from->set(relative[0] ? (*from)[0] * area.width() : (*from)[0],
148                           relative[1] ? (*from)[1] * area.height() : (*from)[1]);
149             } else {
150                 from->set(w->width(), w->height());
151             }
152         }
153 
154         if (to) {
155             if (to->isValid()) {
156                 RELATIVE_XY(Target);
157                 to->set(relative[0] ? (*to)[0] * area.width() : (*to)[0],
158                         relative[1] ? (*to)[1] * area.height() : (*to)[1]);
159             } else {
160                 to->set(w->width(), w->height());
161             }
162         }
163 
164     } else if (a == Translation) {
165         QRect area = w->rect();
166         if (from) {
167             if (from->isValid()) {
168                 RELATIVE_XY(Source);
169                 from->set(relative[0] ? (*from)[0] * area.width() : (*from)[0],
170                           relative[1] ? (*from)[1] * area.height() : (*from)[1]);
171             } else {
172                 from->set(0.0, 0.0);
173             }
174         }
175 
176         if (to) {
177             if (to->isValid()) {
178                 RELATIVE_XY(Target);
179                 to->set(relative[0] ? (*to)[0] * area.width() : (*to)[0],
180                         relative[1] ? (*to)[1] * area.height() : (*to)[1]);
181             } else {
182                 to->set(0.0, 0.0);
183             }
184         }
185 
186     } else if (a == Clip) {
187         if (from && !from->isValid()) {
188             from->set(1.0,1.0);
189             setMetaData(SourceAnchor, metaData(TargetAnchor, meta), meta);
190         }
191         if (to && !to->isValid()) {
192             to->set(1.0,1.0);
193             setMetaData(TargetAnchor, metaData(SourceAnchor, meta), meta);
194         }
195 
196     } else if (a == CrossFadePrevious) {
197         if (from && !from->isValid()) {
198             from->set(0.0);
199         }
200         if (to && !to->isValid()) {
201             to->set(1.0);
202         }
203     }
204 }
205 
p_animate(EffectWindow * w,Attribute a,uint meta,int ms,FPx2 to,const QEasingCurve & curve,int delay,FPx2 from,bool keepAtTarget,bool fullScreenEffect,bool keepAlive)206 quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, const QEasingCurve &curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect, bool keepAlive)
207 {
208     const bool waitAtSource = from.isValid();
209     validate(a, meta, &from, &to, w);
210 
211     Q_D(AnimationEffect);
212     if (!d->m_isInitialized)
213         init(); // needs to ensure the window gets removed if deleted in the same event cycle
214     if (d->m_animations.isEmpty()) {
215         connect(effects, &EffectsHandler::windowExpandedGeometryChanged,
216                 this, &AnimationEffect::_windowExpandedGeometryChanged);
217     }
218     AniMap::iterator it = d->m_animations.find(w);
219     if (it == d->m_animations.end())
220         it = d->m_animations.insert(w, QPair<QList<AniData>, QRect>(QList<AniData>(), QRect()));
221 
222     FullScreenEffectLockPtr fullscreen;
223     if (fullScreenEffect) {
224         if (d->m_fullScreenEffectLock.isNull()) {
225             fullscreen = FullScreenEffectLockPtr::create(this);
226             d->m_fullScreenEffectLock = fullscreen.toWeakRef();
227         } else {
228             fullscreen = d->m_fullScreenEffectLock.toStrongRef();
229         }
230     }
231 
232     PreviousWindowPixmapLockPtr previousPixmap;
233     if (a == CrossFadePrevious) {
234         previousPixmap = PreviousWindowPixmapLockPtr::create(w);
235     }
236 
237     it->first.append(AniData(
238         a,              // Attribute
239         meta,           // Metadata
240         to,             // Target
241         delay,          // Delay
242         from,           // Source
243         waitAtSource,   // Whether the animation should be kept at source
244         fullscreen,     // Full screen effect lock
245         keepAlive,      // Keep alive flag
246         previousPixmap  // Previous window pixmap lock
247     ));
248 
249     const quint64 ret_id = ++d->m_animCounter;
250     AniData &animation = it->first.last();
251     animation.id = ret_id;
252 
253     animation.timeLine.setDirection(TimeLine::Forward);
254     animation.timeLine.setDuration(std::chrono::milliseconds(ms));
255     animation.timeLine.setEasingCurve(curve);
256     animation.timeLine.setSourceRedirectMode(TimeLine::RedirectMode::Strict);
257     animation.timeLine.setTargetRedirectMode(TimeLine::RedirectMode::Relaxed);
258 
259     animation.terminationFlags = TerminateAtSource;
260     if (!keepAtTarget) {
261         animation.terminationFlags |= TerminateAtTarget;
262     }
263 
264     it->second = QRect();
265 
266     d->m_animationsTouched = true;
267 
268     if (delay > 0) {
269         QTimer::singleShot(delay, this, &AnimationEffect::triggerRepaint);
270         const QSize &s = effects->virtualScreenSize();
271         if (waitAtSource)
272             w->addLayerRepaint(0, 0, s.width(), s.height());
273     }
274     else {
275         triggerRepaint();
276     }
277     return ret_id;
278 }
279 
retarget(quint64 animationId,FPx2 newTarget,int newRemainingTime)280 bool AnimationEffect::retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime)
281 {
282     Q_D(AnimationEffect);
283     if (animationId == d->m_justEndedAnimation)
284         return false; // this is just ending, do not try to retarget it
285     for (AniMap::iterator entry = d->m_animations.begin(),
286                          mapEnd = d->m_animations.end(); entry != mapEnd; ++entry) {
287         for (QList<AniData>::iterator anim = entry->first.begin(),
288                                    animEnd = entry->first.end(); anim != animEnd; ++anim) {
289             if (anim->id == animationId) {
290                 anim->from.set(interpolated(*anim, 0), interpolated(*anim, 1));
291                 validate(anim->attribute, anim->meta, nullptr, &newTarget, entry.key());
292                 anim->to.set(newTarget[0], newTarget[1]);
293 
294                 anim->timeLine.setDirection(TimeLine::Forward);
295                 anim->timeLine.setDuration(std::chrono::milliseconds(newRemainingTime));
296                 anim->timeLine.reset();
297 
298                 return true;
299             }
300         }
301     }
302     return false; // no animation found
303 }
304 
redirect(quint64 animationId,Direction direction,TerminationFlags terminationFlags)305 bool AnimationEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags)
306 {
307     Q_D(AnimationEffect);
308 
309     if (animationId == d->m_justEndedAnimation) {
310         return false;
311     }
312 
313     for (auto entryIt = d->m_animations.begin(); entryIt != d->m_animations.end(); ++entryIt) {
314         auto animIt = std::find_if(entryIt->first.begin(), entryIt->first.end(),
315             [animationId] (AniData &anim) {
316                 return anim.id == animationId;
317             }
318         );
319         if (animIt == entryIt->first.end()) {
320             continue;
321         }
322 
323         switch (direction) {
324         case Backward:
325             animIt->timeLine.setDirection(TimeLine::Backward);
326             break;
327 
328         case Forward:
329             animIt->timeLine.setDirection(TimeLine::Forward);
330             break;
331         }
332 
333         animIt->terminationFlags = terminationFlags & ~TerminateAtTarget;
334 
335         return true;
336     }
337 
338     return false;
339 }
340 
complete(quint64 animationId)341 bool AnimationEffect::complete(quint64 animationId)
342 {
343     Q_D(AnimationEffect);
344 
345     if (animationId == d->m_justEndedAnimation) {
346         return false;
347     }
348 
349     for (auto entryIt = d->m_animations.begin(); entryIt != d->m_animations.end(); ++entryIt) {
350         auto animIt = std::find_if(entryIt->first.begin(), entryIt->first.end(),
351             [animationId] (AniData &anim) {
352                 return anim.id == animationId;
353             }
354         );
355         if (animIt == entryIt->first.end()) {
356             continue;
357         }
358 
359         animIt->timeLine.setElapsed(animIt->timeLine.duration());
360 
361         return true;
362     }
363 
364     return false;
365 }
366 
cancel(quint64 animationId)367 bool AnimationEffect::cancel(quint64 animationId)
368 {
369     Q_D(AnimationEffect);
370     if (animationId == d->m_justEndedAnimation)
371         return true; // this is just ending, do not try to cancel it but fake success
372     for (AniMap::iterator entry = d->m_animations.begin(), mapEnd = d->m_animations.end(); entry != mapEnd; ++entry) {
373         for (QList<AniData>::iterator anim = entry->first.begin(), animEnd = entry->first.end(); anim != animEnd; ++anim) {
374             if (anim->id == animationId) {
375                 entry->first.erase(anim); // remove the animation
376                 if (entry->first.isEmpty()) { // no other animations on the window, release it.
377                     d->m_animations.erase(entry);
378                 }
379                 if (d->m_animations.isEmpty())
380                     disconnectGeometryChanges();
381                 d->m_animationsTouched = true; // could be called from animationEnded
382                 return true;
383             }
384         }
385     }
386     return false;
387 }
388 
prePaintScreen(ScreenPrePaintData & data,std::chrono::milliseconds presentTime)389 void AnimationEffect::prePaintScreen( ScreenPrePaintData& data, std::chrono::milliseconds presentTime )
390 {
391     Q_D(AnimationEffect);
392     if (d->m_animations.isEmpty()) {
393         effects->prePaintScreen(data, presentTime);
394         return;
395     }
396 
397     for (auto entry = d->m_animations.begin(); entry != d->m_animations.end(); ++entry) {
398         for (auto anim = entry->first.begin(); anim != entry->first.end(); ++anim) {
399             if (anim->startTime <= clock()) {
400                 if (anim->lastPresentTime.count()) {
401                     anim->timeLine.update(presentTime - anim->lastPresentTime);
402                 }
403                 anim->lastPresentTime = presentTime;
404             }
405         }
406     }
407 
408     effects->prePaintScreen(data, presentTime);
409 }
410 
xCoord(const QRect & r,int flag)411 static int xCoord(const QRect &r, int flag) {
412     if (flag & AnimationEffect::Left)
413         return r.x();
414     else if (flag & AnimationEffect::Right)
415         return r.right();
416     else
417         return r.x() + r.width()/2;
418 }
419 
yCoord(const QRect & r,int flag)420 static int yCoord(const QRect &r, int flag) {
421     if (flag & AnimationEffect::Top)
422         return r.y();
423     else if (flag & AnimationEffect::Bottom)
424         return r.bottom();
425     else
426         return r.y() + r.height()/2;
427 }
428 
clipRect(const QRect & geo,const AniData & anim) const429 QRect AnimationEffect::clipRect(const QRect &geo, const AniData &anim) const
430 {
431     QRect clip = geo;
432     FPx2 ratio = anim.from + progress(anim) * (anim.to - anim.from);
433     if (anim.from[0] < 1.0 || anim.to[0] < 1.0) {
434         clip.setWidth(clip.width() * ratio[0]);
435     }
436     if (anim.from[1] < 1.0 || anim.to[1] < 1.0) {
437         clip.setHeight(clip.height() * ratio[1]);
438     }
439     const QRect center = geo.adjusted(clip.width()/2, clip.height()/2,
440                                         -(clip.width()+1)/2, -(clip.height()+1)/2 );
441     const int x[2] = {  xCoord(center, metaData(SourceAnchor, anim.meta)),
442                         xCoord(center, metaData(TargetAnchor, anim.meta)) };
443     const int y[2] = {  yCoord(center, metaData(SourceAnchor, anim.meta)),
444                         yCoord(center, metaData(TargetAnchor, anim.meta)) };
445     const QPoint d(x[0] + ratio[0]*(x[1]-x[0]), y[0] + ratio[1]*(y[1]-y[0]));
446     clip.moveTopLeft(QPoint(d.x() - clip.width()/2, d.y() - clip.height()/2));
447     return clip;
448 }
449 
disconnectGeometryChanges()450 void AnimationEffect::disconnectGeometryChanges()
451 {
452     disconnect(effects, &EffectsHandler::windowExpandedGeometryChanged,
453                this, &AnimationEffect::_windowExpandedGeometryChanged);
454 }
455 
456 
prePaintWindow(EffectWindow * w,WindowPrePaintData & data,std::chrono::milliseconds presentTime)457 void AnimationEffect::prePaintWindow( EffectWindow* w, WindowPrePaintData& data, std::chrono::milliseconds presentTime )
458 {
459     Q_D(AnimationEffect);
460     AniMap::const_iterator entry = d->m_animations.constFind( w );
461     if ( entry != d->m_animations.constEnd() ) {
462         bool isUsed = false;
463         bool paintDeleted = false;
464         for (QList<AniData>::const_iterator anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim) {
465             if (anim->startTime > clock() && !anim->waitAtSource)
466                 continue;
467 
468             isUsed = true;
469             if (anim->attribute == Opacity || anim->attribute == CrossFadePrevious)
470                 data.setTranslucent();
471             else if (!(anim->attribute == Brightness || anim->attribute == Saturation)) {
472                 data.setTransformed();
473             }
474 
475             paintDeleted |= anim->keepAlive;
476         }
477         if ( isUsed ) {
478             if ( w->isMinimized() )
479                 w->enablePainting( EffectWindow::PAINT_DISABLED_BY_MINIMIZE );
480             else if ( w->isDeleted() && paintDeleted )
481                 w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DELETE );
482             else if ( !w->isOnCurrentDesktop() )
483                 w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP );
484 //            if( !w->isPaintingEnabled() && !effects->activeFullScreenEffect() )
485 //                effects->addLayerRepaint(w->expandedGeometry());
486         }
487     }
488     effects->prePaintWindow(w, data, presentTime);
489 }
490 
geometryCompensation(int flags,float v)491 static inline float geometryCompensation(int flags, float v)
492 {
493     if (flags & (AnimationEffect::Left|AnimationEffect::Top))
494         return 0.0; // no compensation required
495     if (flags & (AnimationEffect::Right|AnimationEffect::Bottom))
496         return 1.0 - v; // full compensation
497     return 0.5 * (1.0 - v); // half compensation
498 }
499 
paintWindow(EffectWindow * w,int mask,QRegion region,WindowPaintData & data)500 void AnimationEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data )
501 {
502     Q_D(AnimationEffect);
503     AniMap::const_iterator entry = d->m_animations.constFind( w );
504     if ( entry != d->m_animations.constEnd() ) {
505         for ( QList<AniData>::const_iterator anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim ) {
506 
507             if (anim->startTime > clock() && !anim->waitAtSource)
508                 continue;
509 
510             switch (anim->attribute) {
511             case Opacity:
512                 data.multiplyOpacity(interpolated(*anim)); break;
513             case Brightness:
514                 data.multiplyBrightness(interpolated(*anim)); break;
515             case Saturation:
516                 data.multiplySaturation(interpolated(*anim)); break;
517             case Scale: {
518                 const QSize sz = w->frameGeometry().size();
519                 float f1(1.0), f2(0.0);
520                 if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) { // scale x
521                     f1 = interpolated(*anim, 0);
522                     f2 = geometryCompensation( anim->meta & AnimationEffect::Horizontal, f1 );
523                     data.translate(f2 * sz.width());
524                     data.setXScale(data.xScale() * f1);
525                 }
526                 if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) { // scale y
527                     if (!anim->isOneDimensional()) {
528                         f1 = interpolated(*anim, 1);
529                         f2 = geometryCompensation( anim->meta & AnimationEffect::Vertical, f1 );
530                     }
531                     else if ( ((anim->meta & AnimationEffect::Vertical)>>1) != (anim->meta & AnimationEffect::Horizontal) )
532                         f2 = geometryCompensation( anim->meta & AnimationEffect::Vertical, f1 );
533                     data.translate(0.0, f2 * sz.height());
534                     data.setYScale(data.yScale() * f1);
535                 }
536                 break;
537             }
538             case Clip:
539                 region = clipRect(w->expandedGeometry(), *anim);
540                 break;
541             case Translation:
542                 data += QPointF(interpolated(*anim, 0), interpolated(*anim, 1));
543                 break;
544             case Size: {
545                 FPx2 dest = anim->from + progress(*anim) * (anim->to - anim->from);
546                 const QSize sz = w->frameGeometry().size();
547                 float f;
548                 if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) { // resize x
549                     f = dest[0]/sz.width();
550                     data.translate(geometryCompensation( anim->meta & AnimationEffect::Horizontal, f ) * sz.width());
551                     data.setXScale(data.xScale() * f);
552                 }
553                 if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) { // resize y
554                     f = dest[1]/sz.height();
555                     data.translate(0.0, geometryCompensation( anim->meta & AnimationEffect::Vertical, f ) * sz.height());
556                     data.setYScale(data.yScale() * f);
557                 }
558                 break;
559             }
560             case Position: {
561                 const QRect geo = w->frameGeometry();
562                 const float prgrs = progress(*anim);
563                 if ( anim->from[0] >= 0.0 && anim->to[0] >= 0.0 ) {
564                     float dest = interpolated(*anim, 0);
565                     const int x[2] = {  xCoord(geo, metaData(SourceAnchor, anim->meta)),
566                                         xCoord(geo, metaData(TargetAnchor, anim->meta)) };
567                     data.translate(dest - (x[0] + prgrs*(x[1] - x[0])));
568                 }
569                 if ( anim->from[1] >= 0.0 && anim->to[1] >= 0.0 ) {
570                     float dest = interpolated(*anim, 1);
571                     const int y[2] = {  yCoord(geo, metaData(SourceAnchor, anim->meta)),
572                                         yCoord(geo, metaData(TargetAnchor, anim->meta)) };
573                     data.translate(0.0, dest - (y[0] + prgrs*(y[1] - y[0])));
574                 }
575                 break;
576             }
577             case Rotation: {
578                 data.setRotationAxis((Qt::Axis)metaData(Axis, anim->meta));
579                 const float prgrs = progress(*anim);
580                 data.setRotationAngle(anim->from[0] + prgrs*(anim->to[0] - anim->from[0]));
581 
582                 const QRect geo = w->rect();
583                 const uint  sAnchor = metaData(SourceAnchor, anim->meta),
584                             tAnchor = metaData(TargetAnchor, anim->meta);
585                 QPointF pt(xCoord(geo, sAnchor), yCoord(geo, sAnchor));
586 
587                 if (tAnchor != sAnchor) {
588                     QPointF pt2(xCoord(geo, tAnchor), yCoord(geo, tAnchor));
589                     pt += static_cast<qreal>(prgrs)*(pt2 - pt);
590                 }
591                 data.setRotationOrigin(QVector3D(pt));
592                 break;
593             }
594             case Generic:
595                 genericAnimation(w, data, progress(*anim), anim->meta);
596                 break;
597             case CrossFadePrevious:
598                 data.setCrossFadeProgress(progress(*anim));
599                 break;
600             default:
601                 break;
602             }
603         }
604     }
605     effects->paintWindow( w, mask, region, data );
606 }
607 
postPaintScreen()608 void AnimationEffect::postPaintScreen()
609 {
610     Q_D(AnimationEffect);
611     d->m_animationsTouched = false;
612     bool damageDirty = false;
613 
614     for (auto entry = d->m_animations.begin(); entry != d->m_animations.end();) {
615         bool invalidateLayerRect = false;
616         int animCounter = 0;
617         for (auto anim = entry->first.begin(); anim != entry->first.end();) {
618             if (anim->isActive() || anim->startTime > clock() && !anim->waitAtSource) {
619                 ++anim;
620                 ++animCounter;
621                 continue;
622             }
623             EffectWindow *window = entry.key();
624             d->m_justEndedAnimation = anim->id;
625             animationEnded(window, anim->attribute, anim->meta);
626             d->m_justEndedAnimation = 0;
627             // NOTICE animationEnded is an external call and might have called "::animate"
628             // as a result our iterators could now point random junk on the heap
629             // so we've to restore the former states, ie. find our window list and animation
630             if (d->m_animationsTouched) {
631                 d->m_animationsTouched = false;
632                 entry = d->m_animations.begin();
633                 while (entry.key() != window && entry != d->m_animations.end()) {
634                     ++entry;
635                 }
636                 Q_ASSERT(entry != d->m_animations.end()); // usercode should not delete animations from animationEnded (not even possible atm.)
637                 anim = entry->first.begin();
638                 Q_ASSERT(animCounter < entry->first.count());
639                 for (int i = 0; i < animCounter; ++i) {
640                     ++anim;
641                 }
642             }
643             anim = entry->first.erase(anim);
644             invalidateLayerRect = damageDirty = true;
645         }
646         if (entry->first.isEmpty()) {
647             effects->addRepaint(entry->second);
648             entry = d->m_animations.erase(entry);
649         } else {
650             if (invalidateLayerRect) {
651                 *const_cast<QRect*>(&(entry->second)) = QRect(); // invalidate
652             }
653             ++entry;
654         }
655     }
656 
657     if (damageDirty) {
658         updateLayerRepaints();
659     }
660     if (d->m_needSceneRepaint) {
661         effects->addRepaintFull();
662     } else {
663         for (auto entry = d->m_animations.constBegin(); entry != d->m_animations.constEnd(); ++entry) {
664             for (auto anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim) {
665                 if (anim->startTime > clock())
666                     continue;
667                 if (!anim->timeLine.done()) {
668                     entry.key()->addLayerRepaint(entry->second);
669                     break;
670                 }
671             }
672         }
673     }
674 
675     // janitorial...
676     if (d->m_animations.isEmpty()) {
677         disconnectGeometryChanges();
678     }
679 
680     effects->postPaintScreen();
681 }
682 
interpolated(const AniData & a,int i) const683 float AnimationEffect::interpolated( const AniData &a, int i ) const
684 {
685     return a.from[i] + a.timeLine.value() * (a.to[i] - a.from[i]);
686 }
687 
progress(const AniData & a) const688 float AnimationEffect::progress( const AniData &a ) const
689 {
690     return a.startTime < clock() ? a.timeLine.value() : 0.0;
691 }
692 
693 
694 // TODO - get this out of the header - the functionpointer usage of QEasingCurve somehow sucks ;-)
695 // qreal AnimationEffect::qecGaussian(qreal progress) // exp(-5*(2*x-1)^2)
696 // {
697 //     progress = 2*progress - 1;
698 //     progress *= -5*progress;
699 //     return qExp(progress);
700 // }
701 
metaData(MetaType type,uint meta)702 int AnimationEffect::metaData( MetaType type, uint meta )
703 {
704     switch (type) {
705         case SourceAnchor:
706             return ((meta>>5) & 0x1f);
707         case TargetAnchor:
708             return (meta& 0x1f);
709         case RelativeSourceX:
710         case RelativeSourceY:
711         case RelativeTargetX:
712         case RelativeTargetY: {
713             const int shift = 10 + type - RelativeSourceX;
714             return ((meta>>shift) & 1);
715         }
716         case Axis:
717             return ((meta>>10) & 3);
718         default:
719             return 0;
720     }
721 }
722 
setMetaData(MetaType type,uint value,uint & meta)723 void AnimationEffect::setMetaData( MetaType type, uint value, uint &meta )
724 {
725     switch (type) {
726     case SourceAnchor:
727         meta &= ~(0x1f<<5);
728         meta |= ((value & 0x1f)<<5);
729         break;
730     case TargetAnchor:
731         meta &= ~(0x1f);
732         meta |= (value & 0x1f);
733         break;
734     case RelativeSourceX:
735     case RelativeSourceY:
736     case RelativeTargetX:
737     case RelativeTargetY: {
738         const int shift = 10 + type - RelativeSourceX;
739         if (value)
740             meta |= (1<<shift);
741         else
742             meta &= ~(1<<shift);
743         break;
744     }
745     case Axis:
746         meta &= ~(3<<10);
747         meta |= ((value & 3)<<10);
748         break;
749     default:
750         break;
751     }
752 }
753 
triggerRepaint()754 void AnimationEffect::triggerRepaint()
755 {
756     Q_D(AnimationEffect);
757     for (AniMap::const_iterator entry = d->m_animations.constBegin(), mapEnd = d->m_animations.constEnd(); entry != mapEnd; ++entry)
758         *const_cast<QRect*>(&(entry->second)) = QRect();
759     updateLayerRepaints();
760     if (d->m_needSceneRepaint) {
761         effects->addRepaintFull();
762     } else {
763         AniMap::const_iterator it = d->m_animations.constBegin(), end = d->m_animations.constEnd();
764         for (; it != end; ++it) {
765             it.key()->addLayerRepaint(it->second);
766         }
767     }
768 }
769 
fixOvershoot(float f,const AniData & d,short int dir,float s=1.1)770 static float fixOvershoot(float f, const AniData &d, short int dir, float s = 1.1)
771 {
772     switch(d.timeLine.easingCurve().type()) {
773         case QEasingCurve::InOutElastic:
774         case QEasingCurve::InOutBack:
775             return f * s;
776         case QEasingCurve::InElastic:
777         case QEasingCurve::OutInElastic:
778         case QEasingCurve::OutBack:
779             return (dir&2) ? f * s : f;
780         case QEasingCurve::OutElastic:
781         case QEasingCurve::InBack:
782             return (dir&1) ? f * s : f;
783         default:
784             return f;
785     }
786 }
787 
updateLayerRepaints()788 void AnimationEffect::updateLayerRepaints()
789 {
790     Q_D(AnimationEffect);
791     d->m_needSceneRepaint = false;
792     for (AniMap::const_iterator entry = d->m_animations.constBegin(), mapEnd = d->m_animations.constEnd(); entry != mapEnd; ++entry) {
793         if (!entry->second.isNull())
794             continue;
795         float f[2] = {1.0, 1.0};
796         float t[2] = {0.0, 0.0};
797         bool createRegion = false;
798         QList<QRect> rects;
799         QRect *layerRect = const_cast<QRect*>(&(entry->second));
800         for (QList<AniData>::const_iterator anim = entry->first.constBegin(), animEnd = entry->first.constEnd(); anim != animEnd; ++anim) {
801             if (anim->startTime > clock())
802                 continue;
803             switch (anim->attribute) {
804                 case Opacity:
805                 case Brightness:
806                 case Saturation:
807                 case CrossFadePrevious:
808                     createRegion = true;
809                     break;
810                 case Rotation:
811                     createRegion = false;
812                     *layerRect = QRect(QPoint(0, 0), effects->virtualScreenSize());
813                     goto region_creation; // sic! no need to do anything else
814                 case Generic:
815                     d->m_needSceneRepaint = true; // we don't know whether this will change visual stacking order
816                     return; // sic! no need to do anything else
817                 case Translation:
818                 case Position: {
819                     createRegion = true;
820                     QRect r(entry.key()->frameGeometry());
821                     int x[2] = {0,0};
822                     int y[2] = {0,0};
823                     if (anim->attribute == Translation) {
824                         x[0] = anim->from[0];
825                         x[1] = anim->to[0];
826                         y[0] = anim->from[1];
827                         y[1] = anim->to[1];
828                     } else {
829                         if ( anim->from[0] >= 0.0 && anim->to[0] >= 0.0 ) {
830                             x[0] = anim->from[0] - xCoord(r, metaData(SourceAnchor, anim->meta));
831                             x[1] = anim->to[0] - xCoord(r, metaData(TargetAnchor, anim->meta));
832                         }
833                         if ( anim->from[1] >= 0.0 && anim->to[1] >= 0.0 ) {
834                             y[0] = anim->from[1] - yCoord(r, metaData(SourceAnchor, anim->meta));
835                             y[1] = anim->to[1] - yCoord(r, metaData(TargetAnchor, anim->meta));
836                         }
837                     }
838                     r = entry.key()->expandedGeometry();
839                     rects << r.translated(x[0], y[0]) << r.translated(x[1], y[1]);
840                     break;
841                 }
842                 case Clip:
843                     createRegion = true;
844                     break;
845                 case Size:
846                 case Scale: {
847                     createRegion = true;
848                     const QSize sz = entry.key()->frameGeometry().size();
849                     float fx = qMax(fixOvershoot(anim->from[0], *anim, 1), fixOvershoot(anim->to[0], *anim, 2));
850 //                     float fx = qMax(interpolated(*anim,0), anim->to[0]);
851                     if (fx >= 0.0) {
852                         if (anim->attribute == Size)
853                             fx /= sz.width();
854                         f[0] *= fx;
855                         t[0] += geometryCompensation( anim->meta & AnimationEffect::Horizontal, fx ) * sz.width();
856                     }
857 //                     float fy = qMax(interpolated(*anim,1), anim->to[1]);
858                     float fy = qMax(fixOvershoot(anim->from[1], *anim, 1), fixOvershoot(anim->to[1], *anim, 2));
859                     if (fy >= 0.0) {
860                         if (anim->attribute == Size)
861                             fy /= sz.height();
862                         if (!anim->isOneDimensional()) {
863                             f[1] *= fy;
864                             t[1] += geometryCompensation( anim->meta & AnimationEffect::Vertical, fy ) * sz.height();
865                         } else if ( ((anim->meta & AnimationEffect::Vertical)>>1) != (anim->meta & AnimationEffect::Horizontal) ) {
866                             f[1] *= fx;
867                             t[1] += geometryCompensation( anim->meta & AnimationEffect::Vertical, fx ) * sz.height();
868                         }
869                     }
870                     break;
871                 }
872             }
873         }
874 region_creation:
875         if (createRegion) {
876             const QRect geo = entry.key()->expandedGeometry();
877             if (rects.isEmpty())
878                 rects << geo;
879             QList<QRect>::const_iterator r, rEnd = rects.constEnd();
880             for ( r = rects.constBegin(); r != rEnd; ++r) { // transform
881                 const_cast<QRect*>(&(*r))->setSize(QSize(qRound(r->width()*f[0]), qRound(r->height()*f[1])));
882                 const_cast<QRect*>(&(*r))->translate(t[0], t[1]); // "const_cast" - don't do that at home, kids ;-)
883             }
884             QRect rect = rects.at(0);
885             if (rects.count() > 1) {
886                 for ( r = rects.constBegin() + 1; r != rEnd; ++r) // unite
887                     rect |= *r;
888                 const int dx = 110*(rect.width() - geo.width())/100 + 1 - rect.width() + geo.width();
889                 const int dy = 110*(rect.height() - geo.height())/100 + 1 - rect.height() + geo.height();
890                 rect.adjust(-dx,-dy,dx,dy); // fix pot. overshoot
891             }
892             *layerRect = rect;
893         }
894     }
895 }
896 
_windowExpandedGeometryChanged(KWin::EffectWindow * w)897 void AnimationEffect::_windowExpandedGeometryChanged(KWin::EffectWindow *w)
898 {
899     Q_D(AnimationEffect);
900     AniMap::const_iterator entry = d->m_animations.constFind(w);
901     if (entry != d->m_animations.constEnd()) {
902         *const_cast<QRect*>(&(entry->second)) = QRect();
903         updateLayerRepaints();
904         if (!entry->second.isNull()) // actually got updated, ie. is in use - ensure it get's a repaint
905             w->addLayerRepaint(entry->second);
906     }
907 }
908 
_windowClosed(EffectWindow * w)909 void AnimationEffect::_windowClosed( EffectWindow* w )
910 {
911     Q_D(AnimationEffect);
912 
913     auto it = d->m_animations.find(w);
914     if (it == d->m_animations.end()) {
915         return;
916     }
917 
918     KeepAliveLockPtr keepAliveLock;
919 
920     QList<AniData> &animations = (*it).first;
921     for (auto animationIt = animations.begin();
922             animationIt != animations.end();
923             ++animationIt) {
924         if (!(*animationIt).keepAlive) {
925             continue;
926         }
927 
928         if (keepAliveLock.isNull()) {
929             keepAliveLock = KeepAliveLockPtr::create(w);
930         }
931 
932         (*animationIt).keepAliveLock = keepAliveLock;
933     }
934 }
935 
_windowDeleted(EffectWindow * w)936 void AnimationEffect::_windowDeleted( EffectWindow* w )
937 {
938     Q_D(AnimationEffect);
939     d->m_animations.remove( w );
940 }
941 
942 
debug(const QString &) const943 QString AnimationEffect::debug(const QString &/*parameter*/) const
944 {
945     Q_D(const AnimationEffect);
946     QString dbg;
947     if (d->m_animations.isEmpty())
948         dbg = QStringLiteral("No window is animated");
949     else {
950         AniMap::const_iterator entry = d->m_animations.constBegin(), mapEnd = d->m_animations.constEnd();
951         for (; entry != mapEnd; ++entry) {
952             QString caption = entry.key()->isDeleted() ? QStringLiteral("[Deleted]") : entry.key()->caption();
953             if (caption.isEmpty())
954                 caption = QStringLiteral("[Untitled]");
955             dbg += QLatin1String("Animating window: ") + caption + QLatin1Char('\n');
956             QList<AniData>::const_iterator anim = entry->first.constBegin(), animEnd = entry->first.constEnd();
957             for (; anim != animEnd; ++anim)
958                 dbg += anim->debugInfo();
959         }
960     }
961     return dbg;
962 }
963 
state() const964 AnimationEffect::AniMap AnimationEffect::state() const
965 {
966     Q_D(const AnimationEffect);
967     return d->m_animations;
968 }
969 
970 } // namespace KWin
971 
972 #include "moc_kwinanimationeffect.cpp"
973