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