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