File indexing completed on 2024-11-10 04:56:42

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2011 Thomas Lübking <thomas.luebking@web.de>
0006     SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "effect/animationeffect.h"
0012 #include "effect/anidata_p.h"
0013 #include "effect/effecthandler.h"
0014 #include "opengl/glshader.h"
0015 #include "opengl/glshadermanager.h"
0016 
0017 #include <QDateTime>
0018 #include <QTimer>
0019 #include <QVector3D>
0020 #include <QtDebug>
0021 
0022 namespace KWin
0023 {
0024 
0025 QDebug operator<<(QDebug dbg, const KWin::FPx2 &fpx2)
0026 {
0027     dbg.nospace() << fpx2[0] << "," << fpx2[1] << QString(fpx2.isValid() ? QStringLiteral(" (valid)") : QStringLiteral(" (invalid)"));
0028     return dbg.space();
0029 }
0030 
0031 QElapsedTimer AnimationEffect::s_clock;
0032 
0033 class AnimationEffectPrivate
0034 {
0035 public:
0036     AnimationEffectPrivate()
0037     {
0038         m_animationsTouched = m_isInitialized = false;
0039         m_justEndedAnimation = 0;
0040     }
0041     AnimationEffect::AniMap m_animations;
0042     static quint64 m_animCounter;
0043     quint64 m_justEndedAnimation; // protect against cancel
0044     std::weak_ptr<FullScreenEffectLock> m_fullScreenEffectLock;
0045     bool m_needSceneRepaint, m_animationsTouched, m_isInitialized;
0046 };
0047 
0048 quint64 AnimationEffectPrivate::m_animCounter = 0;
0049 
0050 AnimationEffect::AnimationEffect()
0051     : CrossFadeEffect()
0052     , d_ptr(std::make_unique<AnimationEffectPrivate>())
0053 {
0054     if (!s_clock.isValid()) {
0055         s_clock.start();
0056     }
0057     /* this is the same as the QTimer::singleShot(0, SLOT(init())) kludge
0058      * defering the init and esp. the connection to the windowClosed slot */
0059     QMetaObject::invokeMethod(this, &AnimationEffect::init, Qt::QueuedConnection);
0060 }
0061 
0062 AnimationEffect::~AnimationEffect()
0063 {
0064     Q_D(AnimationEffect);
0065     if (d->m_isInitialized) {
0066         disconnect(effects, &EffectsHandler::windowDeleted, this, &AnimationEffect::_windowDeleted);
0067     }
0068     d->m_animations.clear();
0069 }
0070 
0071 void AnimationEffect::init()
0072 {
0073     Q_D(AnimationEffect);
0074     if (d->m_isInitialized) {
0075         return; // not more than once, please
0076     }
0077     d->m_isInitialized = true;
0078     /* by connecting the signal from a slot AFTER the inheriting class constructor had the chance to
0079      * connect it we can provide auto-referencing of animated and closed windows, since at the time
0080      * our slot will be called, the slot of the subclass has been (SIGNAL/SLOT connections are FIFO)
0081      * and has pot. started an animation so we have the window in our hash :) */
0082     connect(effects, &EffectsHandler::windowClosed, this, &AnimationEffect::_windowClosed);
0083     connect(effects, &EffectsHandler::windowDeleted, this, &AnimationEffect::_windowDeleted);
0084 }
0085 
0086 bool AnimationEffect::isActive() const
0087 {
0088     Q_D(const AnimationEffect);
0089     return !d->m_animations.isEmpty() && !effects->isScreenLocked();
0090 }
0091 
0092 #define RELATIVE_XY(_FIELD_) const bool relative[2] = {static_cast<bool>(metaData(Relative##_FIELD_##X, meta)), \
0093                                                        static_cast<bool>(metaData(Relative##_FIELD_##Y, meta))}
0094 
0095 void AnimationEffect::validate(Attribute a, uint &meta, FPx2 *from, FPx2 *to, const EffectWindow *w) const
0096 {
0097     if (a < NonFloatBase) {
0098         if (a == Scale) {
0099             QRectF area = effects->clientArea(ScreenArea, w);
0100             if (from && from->isValid()) {
0101                 RELATIVE_XY(Source);
0102                 from->set(relative[0] ? (*from)[0] * area.width() / w->width() : (*from)[0],
0103                           relative[1] ? (*from)[1] * area.height() / w->height() : (*from)[1]);
0104             }
0105             if (to && to->isValid()) {
0106                 RELATIVE_XY(Target);
0107                 to->set(relative[0] ? (*to)[0] * area.width() / w->width() : (*to)[0],
0108                         relative[1] ? (*to)[1] * area.height() / w->height() : (*to)[1]);
0109             }
0110         } else if (a == Rotation) {
0111             if (from && !from->isValid()) {
0112                 setMetaData(SourceAnchor, metaData(TargetAnchor, meta), meta);
0113                 from->set(0.0, 0.0);
0114             }
0115             if (to && !to->isValid()) {
0116                 setMetaData(TargetAnchor, metaData(SourceAnchor, meta), meta);
0117                 to->set(0.0, 0.0);
0118             }
0119         }
0120         if (from && !from->isValid()) {
0121             from->set(1.0, 1.0);
0122         }
0123         if (to && !to->isValid()) {
0124             to->set(1.0, 1.0);
0125         }
0126 
0127     } else if (a == Position) {
0128         QRectF area = effects->clientArea(ScreenArea, w);
0129         QPointF pt = w->frameGeometry().bottomRight(); // cannot be < 0 ;-)
0130         if (from) {
0131             if (from->isValid()) {
0132                 RELATIVE_XY(Source);
0133                 from->set(relative[0] ? area.x() + (*from)[0] * area.width() : (*from)[0],
0134                           relative[1] ? area.y() + (*from)[1] * area.height() : (*from)[1]);
0135             } else {
0136                 from->set(pt.x(), pt.y());
0137                 setMetaData(SourceAnchor, AnimationEffect::Bottom | AnimationEffect::Right, meta);
0138             }
0139         }
0140 
0141         if (to) {
0142             if (to->isValid()) {
0143                 RELATIVE_XY(Target);
0144                 to->set(relative[0] ? area.x() + (*to)[0] * area.width() : (*to)[0],
0145                         relative[1] ? area.y() + (*to)[1] * area.height() : (*to)[1]);
0146             } else {
0147                 to->set(pt.x(), pt.y());
0148                 setMetaData(TargetAnchor, AnimationEffect::Bottom | AnimationEffect::Right, meta);
0149             }
0150         }
0151 
0152     } else if (a == Size) {
0153         QRectF area = effects->clientArea(ScreenArea, w);
0154         if (from) {
0155             if (from->isValid()) {
0156                 RELATIVE_XY(Source);
0157                 from->set(relative[0] ? (*from)[0] * area.width() : (*from)[0],
0158                           relative[1] ? (*from)[1] * area.height() : (*from)[1]);
0159             } else {
0160                 from->set(w->width(), w->height());
0161             }
0162         }
0163 
0164         if (to) {
0165             if (to->isValid()) {
0166                 RELATIVE_XY(Target);
0167                 to->set(relative[0] ? (*to)[0] * area.width() : (*to)[0],
0168                         relative[1] ? (*to)[1] * area.height() : (*to)[1]);
0169             } else {
0170                 to->set(w->width(), w->height());
0171             }
0172         }
0173 
0174     } else if (a == Translation) {
0175         QRect area = w->rect().toRect();
0176         if (from) {
0177             if (from->isValid()) {
0178                 RELATIVE_XY(Source);
0179                 from->set(relative[0] ? (*from)[0] * area.width() : (*from)[0],
0180                           relative[1] ? (*from)[1] * area.height() : (*from)[1]);
0181             } else {
0182                 from->set(0.0, 0.0);
0183             }
0184         }
0185 
0186         if (to) {
0187             if (to->isValid()) {
0188                 RELATIVE_XY(Target);
0189                 to->set(relative[0] ? (*to)[0] * area.width() : (*to)[0],
0190                         relative[1] ? (*to)[1] * area.height() : (*to)[1]);
0191             } else {
0192                 to->set(0.0, 0.0);
0193             }
0194         }
0195 
0196     } else if (a == Clip) {
0197         if (from && !from->isValid()) {
0198             from->set(1.0, 1.0);
0199             setMetaData(SourceAnchor, metaData(TargetAnchor, meta), meta);
0200         }
0201         if (to && !to->isValid()) {
0202             to->set(1.0, 1.0);
0203             setMetaData(TargetAnchor, metaData(SourceAnchor, meta), meta);
0204         }
0205 
0206     } else if (a == CrossFadePrevious) {
0207         if (from && !from->isValid()) {
0208             from->set(0.0);
0209         }
0210         if (to && !to->isValid()) {
0211             to->set(1.0);
0212         }
0213     }
0214 }
0215 
0216 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, GLShader *shader)
0217 {
0218     const bool waitAtSource = from.isValid();
0219     validate(a, meta, &from, &to, w);
0220 
0221     Q_D(AnimationEffect);
0222     if (!d->m_isInitialized) {
0223         init(); // needs to ensure the window gets removed if deleted in the same event cycle
0224     }
0225     AniMap::iterator it = d->m_animations.find(w);
0226     if (it == d->m_animations.end()) {
0227         connect(w, &EffectWindow::windowExpandedGeometryChanged,
0228                 this, &AnimationEffect::_windowExpandedGeometryChanged);
0229         it = d->m_animations.insert(w, QPair<QList<AniData>, QRect>(QList<AniData>(), QRect()));
0230     }
0231 
0232     std::shared_ptr<FullScreenEffectLock> fullscreen;
0233     if (fullScreenEffect) {
0234         fullscreen = d->m_fullScreenEffectLock.lock();
0235         if (!fullscreen) {
0236             fullscreen = std::make_shared<FullScreenEffectLock>(this);
0237             d->m_fullScreenEffectLock = fullscreen;
0238         }
0239     }
0240 
0241     if (a == CrossFadePrevious) {
0242         CrossFadeEffect::redirect(w);
0243     }
0244 
0245     it->first.append(AniData(
0246         a, // Attribute
0247         meta, // Metadata
0248         to, // Target
0249         delay, // Delay
0250         from, // Source
0251         waitAtSource, // Whether the animation should be kept at source
0252         fullscreen, // Full screen effect lock
0253         keepAlive, // Keep alive flag
0254         shader));
0255 
0256     const quint64 ret_id = ++d->m_animCounter;
0257     AniData &animation = it->first.last();
0258     animation.id = ret_id;
0259 
0260     animation.visibleRef = EffectWindowVisibleRef(w, EffectWindow::PAINT_DISABLED_BY_MINIMIZE | EffectWindow::PAINT_DISABLED_BY_DESKTOP | EffectWindow::PAINT_DISABLED);
0261     animation.timeLine.setDirection(TimeLine::Forward);
0262     animation.timeLine.setDuration(std::chrono::milliseconds(ms));
0263     animation.timeLine.setEasingCurve(curve);
0264     animation.timeLine.setSourceRedirectMode(TimeLine::RedirectMode::Strict);
0265     animation.timeLine.setTargetRedirectMode(TimeLine::RedirectMode::Relaxed);
0266 
0267     animation.terminationFlags = TerminateAtSource;
0268     if (!keepAtTarget) {
0269         animation.terminationFlags |= TerminateAtTarget;
0270     }
0271 
0272     it->second = QRect();
0273 
0274     d->m_animationsTouched = true;
0275 
0276     if (delay > 0) {
0277         QTimer::singleShot(delay, this, &AnimationEffect::triggerRepaint);
0278         const QSize &s = effects->virtualScreenSize();
0279         if (waitAtSource) {
0280             w->addLayerRepaint(0, 0, s.width(), s.height());
0281         }
0282     } else {
0283         triggerRepaint();
0284     }
0285     if (shader) {
0286         CrossFadeEffect::redirect(w);
0287     }
0288     return ret_id;
0289 }
0290 
0291 bool AnimationEffect::retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime)
0292 {
0293     Q_D(AnimationEffect);
0294     if (animationId == d->m_justEndedAnimation) {
0295         return false; // this is just ending, do not try to retarget it
0296     }
0297     for (AniMap::iterator entry = d->m_animations.begin(),
0298                           mapEnd = d->m_animations.end();
0299          entry != mapEnd; ++entry) {
0300         for (QList<AniData>::iterator anim = entry->first.begin(),
0301                                       animEnd = entry->first.end();
0302              anim != animEnd; ++anim) {
0303             if (anim->id == animationId) {
0304                 anim->from.set(interpolated(*anim, 0), interpolated(*anim, 1));
0305                 validate(anim->attribute, anim->meta, nullptr, &newTarget, entry.key());
0306                 anim->to.set(newTarget[0], newTarget[1]);
0307 
0308                 anim->timeLine.setDirection(TimeLine::Forward);
0309                 anim->timeLine.setDuration(std::chrono::milliseconds(newRemainingTime));
0310                 anim->timeLine.reset();
0311 
0312                 if (anim->attribute == CrossFadePrevious) {
0313                     CrossFadeEffect::redirect(entry.key());
0314                 }
0315                 return true;
0316             }
0317         }
0318     }
0319     return false; // no animation found
0320 }
0321 
0322 bool AnimationEffect::freezeInTime(quint64 animationId, qint64 frozenTime)
0323 {
0324     Q_D(AnimationEffect);
0325 
0326     if (animationId == d->m_justEndedAnimation) {
0327         return false; // this is just ending, do not try to retarget it
0328     }
0329     for (AniMap::iterator entry = d->m_animations.begin(),
0330                           mapEnd = d->m_animations.end();
0331          entry != mapEnd; ++entry) {
0332         for (QList<AniData>::iterator anim = entry->first.begin(),
0333                                       animEnd = entry->first.end();
0334              anim != animEnd; ++anim) {
0335             if (anim->id == animationId) {
0336                 if (frozenTime >= 0) {
0337                     anim->timeLine.setElapsed(std::chrono::milliseconds(frozenTime));
0338                 }
0339                 anim->frozenTime = frozenTime;
0340                 return true;
0341             }
0342         }
0343     }
0344     return false; // no animation found
0345 }
0346 
0347 bool AnimationEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags)
0348 {
0349     Q_D(AnimationEffect);
0350     if (animationId == d->m_justEndedAnimation) {
0351         return false;
0352     }
0353 
0354     for (auto entryIt = d->m_animations.begin(); entryIt != d->m_animations.end(); ++entryIt) {
0355         auto animIt = std::find_if(entryIt->first.begin(), entryIt->first.end(),
0356                                    [animationId](AniData &anim) {
0357                                        return anim.id == animationId;
0358                                    });
0359         if (animIt == entryIt->first.end()) {
0360             continue;
0361         }
0362 
0363         switch (direction) {
0364         case Backward:
0365             animIt->timeLine.setDirection(TimeLine::Backward);
0366             break;
0367 
0368         case Forward:
0369             animIt->timeLine.setDirection(TimeLine::Forward);
0370             break;
0371         }
0372 
0373         animIt->terminationFlags = terminationFlags & ~TerminateAtTarget;
0374 
0375         return true;
0376     }
0377 
0378     return false;
0379 }
0380 
0381 bool AnimationEffect::complete(quint64 animationId)
0382 {
0383     Q_D(AnimationEffect);
0384 
0385     if (animationId == d->m_justEndedAnimation) {
0386         return false;
0387     }
0388 
0389     for (auto entryIt = d->m_animations.begin(); entryIt != d->m_animations.end(); ++entryIt) {
0390         auto animIt = std::find_if(entryIt->first.begin(), entryIt->first.end(),
0391                                    [animationId](AniData &anim) {
0392                                        return anim.id == animationId;
0393                                    });
0394         if (animIt == entryIt->first.end()) {
0395             continue;
0396         }
0397 
0398         animIt->timeLine.setElapsed(animIt->timeLine.duration());
0399         unredirect(entryIt.key());
0400 
0401         return true;
0402     }
0403 
0404     return false;
0405 }
0406 
0407 bool AnimationEffect::cancel(quint64 animationId)
0408 {
0409     Q_D(AnimationEffect);
0410     if (animationId == d->m_justEndedAnimation) {
0411         return true; // this is just ending, do not try to cancel it but fake success
0412     }
0413     for (AniMap::iterator entry = d->m_animations.begin(), mapEnd = d->m_animations.end(); entry != mapEnd; ++entry) {
0414         for (QList<AniData>::iterator anim = entry->first.begin(), animEnd = entry->first.end(); anim != animEnd; ++anim) {
0415             if (anim->id == animationId) {
0416                 EffectWindowDeletedRef ref = std::move(anim->deletedRef); // delete window once we're done updating m_animations
0417                 if (anim->shader && std::none_of(entry->first.begin(), entry->first.end(), [animationId](const auto &anim) {
0418                         return anim.id != animationId && anim.shader;
0419                     })) {
0420                     unredirect(entry.key());
0421                 }
0422                 entry->first.erase(anim); // remove the animation
0423                 if (entry->first.isEmpty()) { // no other animations on the window, release it.
0424                     disconnect(entry.key(), &EffectWindow::windowExpandedGeometryChanged,
0425                                this, &AnimationEffect::_windowExpandedGeometryChanged);
0426                     d->m_animations.erase(entry);
0427                 }
0428                 d->m_animationsTouched = true; // could be called from animationEnded
0429                 return true;
0430             }
0431         }
0432     }
0433     return false;
0434 }
0435 
0436 void AnimationEffect::animationEnded(EffectWindow *w, Attribute a, uint meta)
0437 {
0438 }
0439 
0440 void AnimationEffect::genericAnimation(EffectWindow *w, WindowPaintData &data, float progress, uint meta)
0441 {
0442 }
0443 
0444 static qreal xCoord(const QRectF &r, int flag)
0445 {
0446     if (flag & AnimationEffect::Left) {
0447         return r.x();
0448     } else if (flag & AnimationEffect::Right) {
0449         return r.right();
0450     } else {
0451         return r.x() + r.width() / 2;
0452     }
0453 }
0454 
0455 static qreal yCoord(const QRectF &r, int flag)
0456 {
0457     if (flag & AnimationEffect::Top) {
0458         return r.y();
0459     } else if (flag & AnimationEffect::Bottom) {
0460         return r.bottom();
0461     } else {
0462         return r.y() + r.height() / 2;
0463     }
0464 }
0465 
0466 QRect AnimationEffect::clipRect(const QRect &geo, const AniData &anim) const
0467 {
0468     QRect clip = geo;
0469     FPx2 ratio = anim.from + progress(anim) * (anim.to - anim.from);
0470     if (anim.from[0] < 1.0 || anim.to[0] < 1.0) {
0471         clip.setWidth(clip.width() * ratio[0]);
0472     }
0473     if (anim.from[1] < 1.0 || anim.to[1] < 1.0) {
0474         clip.setHeight(clip.height() * ratio[1]);
0475     }
0476     const QRect center = geo.adjusted(clip.width() / 2, clip.height() / 2,
0477                                       -(clip.width() + 1) / 2, -(clip.height() + 1) / 2);
0478     const qreal x[2] = {xCoord(center, metaData(SourceAnchor, anim.meta)),
0479                         xCoord(center, metaData(TargetAnchor, anim.meta))};
0480     const qreal y[2] = {yCoord(center, metaData(SourceAnchor, anim.meta)),
0481                         yCoord(center, metaData(TargetAnchor, anim.meta))};
0482     const QPoint d(x[0] + ratio[0] * (x[1] - x[0]), y[0] + ratio[1] * (y[1] - y[0]));
0483     clip.moveTopLeft(QPoint(d.x() - clip.width() / 2, d.y() - clip.height() / 2));
0484     return clip;
0485 }
0486 
0487 void AnimationEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
0488 {
0489     Q_D(AnimationEffect);
0490     auto entry = d->m_animations.find(w);
0491     if (entry != d->m_animations.end()) {
0492         for (auto anim = entry->first.begin(); anim != entry->first.end(); ++anim) {
0493             if (anim->startTime > clock() && !anim->waitAtSource) {
0494                 continue;
0495             }
0496 
0497             if (anim->frozenTime < 0) {
0498                 anim->timeLine.advance(presentTime);
0499             }
0500 
0501             if (anim->attribute == Opacity || anim->attribute == CrossFadePrevious) {
0502                 data.setTranslucent();
0503             } else if (!(anim->attribute == Brightness || anim->attribute == Saturation)) {
0504                 data.setTransformed();
0505             }
0506         }
0507     }
0508     effects->prePaintWindow(w, data, presentTime);
0509 }
0510 
0511 static inline float geometryCompensation(int flags, float v)
0512 {
0513     if (flags & (AnimationEffect::Left | AnimationEffect::Top)) {
0514         return 0.0; // no compensation required
0515     }
0516     if (flags & (AnimationEffect::Right | AnimationEffect::Bottom)) {
0517         return 1.0 - v; // full compensation
0518     }
0519     return 0.5 * (1.0 - v); // half compensation
0520 }
0521 
0522 void AnimationEffect::paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
0523 {
0524     Q_D(AnimationEffect);
0525     AniMap::const_iterator entry = d->m_animations.constFind(w);
0526     auto finalRegion = region;
0527 
0528     if (entry != d->m_animations.constEnd()) {
0529         for (QList<AniData>::const_iterator anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim) {
0530 
0531             if (anim->startTime > clock() && !anim->waitAtSource) {
0532                 continue;
0533             }
0534 
0535             switch (anim->attribute) {
0536             case Opacity:
0537                 data.multiplyOpacity(interpolated(*anim));
0538                 break;
0539             case Brightness:
0540                 data.multiplyBrightness(interpolated(*anim));
0541                 break;
0542             case Saturation:
0543                 data.multiplySaturation(interpolated(*anim));
0544                 break;
0545             case Scale: {
0546                 const QSizeF sz = w->frameGeometry().size();
0547                 float f1(1.0), f2(0.0);
0548                 if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) { // scale x
0549                     f1 = interpolated(*anim, 0);
0550                     f2 = geometryCompensation(anim->meta & AnimationEffect::Horizontal, f1);
0551                     data.translate(f2 * sz.width());
0552                     data.setXScale(data.xScale() * f1);
0553                 }
0554                 if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) { // scale y
0555                     if (!anim->isOneDimensional()) {
0556                         f1 = interpolated(*anim, 1);
0557                         f2 = geometryCompensation(anim->meta & AnimationEffect::Vertical, f1);
0558                     } else if (((anim->meta & AnimationEffect::Vertical) >> 1) != (anim->meta & AnimationEffect::Horizontal)) {
0559                         f2 = geometryCompensation(anim->meta & AnimationEffect::Vertical, f1);
0560                     }
0561                     data.translate(0.0, f2 * sz.height());
0562                     data.setYScale(data.yScale() * f1);
0563                 }
0564                 break;
0565             }
0566             case Clip:
0567                 finalRegion = clipRect(w->expandedGeometry().toAlignedRect(), *anim);
0568                 break;
0569             case Translation:
0570                 data += QPointF(interpolated(*anim, 0), interpolated(*anim, 1));
0571                 break;
0572             case Size: {
0573                 FPx2 dest = anim->from + progress(*anim) * (anim->to - anim->from);
0574                 const QSizeF sz = w->frameGeometry().size();
0575                 float f;
0576                 if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) { // resize x
0577                     f = dest[0] / sz.width();
0578                     data.translate(geometryCompensation(anim->meta & AnimationEffect::Horizontal, f) * sz.width());
0579                     data.setXScale(data.xScale() * f);
0580                 }
0581                 if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) { // resize y
0582                     f = dest[1] / sz.height();
0583                     data.translate(0.0, geometryCompensation(anim->meta & AnimationEffect::Vertical, f) * sz.height());
0584                     data.setYScale(data.yScale() * f);
0585                 }
0586                 break;
0587             }
0588             case Position: {
0589                 const QRectF geo = w->frameGeometry();
0590                 const float prgrs = progress(*anim);
0591                 if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) {
0592                     float dest = interpolated(*anim, 0);
0593                     const qreal x[2] = {xCoord(geo, metaData(SourceAnchor, anim->meta)),
0594                                         xCoord(geo, metaData(TargetAnchor, anim->meta))};
0595                     data.translate(dest - (x[0] + prgrs * (x[1] - x[0])));
0596                 }
0597                 if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) {
0598                     float dest = interpolated(*anim, 1);
0599                     const qreal y[2] = {yCoord(geo, metaData(SourceAnchor, anim->meta)),
0600                                         yCoord(geo, metaData(TargetAnchor, anim->meta))};
0601                     data.translate(0.0, dest - (y[0] + prgrs * (y[1] - y[0])));
0602                 }
0603                 break;
0604             }
0605             case Rotation: {
0606                 data.setRotationAxis((Qt::Axis)metaData(Axis, anim->meta));
0607                 const float prgrs = progress(*anim);
0608                 data.setRotationAngle(anim->from[0] + prgrs * (anim->to[0] - anim->from[0]));
0609 
0610                 const QRect geo = w->rect().toRect();
0611                 const uint sAnchor = metaData(SourceAnchor, anim->meta),
0612                            tAnchor = metaData(TargetAnchor, anim->meta);
0613                 QPointF pt(xCoord(geo, sAnchor), yCoord(geo, sAnchor));
0614 
0615                 if (tAnchor != sAnchor) {
0616                     QPointF pt2(xCoord(geo, tAnchor), yCoord(geo, tAnchor));
0617                     pt += static_cast<qreal>(prgrs) * (pt2 - pt);
0618                 }
0619                 data.setRotationOrigin(QVector3D(pt));
0620                 break;
0621             }
0622             case Generic:
0623                 genericAnimation(w, data, progress(*anim), anim->meta);
0624                 break;
0625             case CrossFadePrevious:
0626                 data.setCrossFadeProgress(progress(*anim));
0627                 break;
0628             case Shader:
0629                 if (anim->shader && anim->shader->isValid()) {
0630                     ShaderBinder binder{anim->shader};
0631                     anim->shader->setUniform("animationProgress", progress(*anim));
0632                     setShader(w, anim->shader);
0633                 }
0634                 break;
0635             case ShaderUniform:
0636                 if (anim->shader && anim->shader->isValid()) {
0637                     ShaderBinder binder{anim->shader};
0638                     anim->shader->setUniform("animationProgress", progress(*anim));
0639                     anim->shader->setUniform(anim->meta, interpolated(*anim));
0640                     setShader(w, anim->shader);
0641                 }
0642                 break;
0643             default:
0644                 break;
0645             }
0646         }
0647     }
0648 
0649     effects->paintWindow(renderTarget, viewport, w, mask, region, data);
0650 }
0651 
0652 void AnimationEffect::postPaintScreen()
0653 {
0654     Q_D(AnimationEffect);
0655     d->m_animationsTouched = false;
0656     bool damageDirty = false;
0657     std::vector<EffectWindowDeletedRef> zombies;
0658 
0659     for (auto entry = d->m_animations.begin(); entry != d->m_animations.end();) {
0660         bool invalidateLayerRect = false;
0661         int animCounter = 0;
0662         for (auto anim = entry->first.begin(); anim != entry->first.end();) {
0663             if (anim->isActive() || (anim->startTime > clock() && !anim->waitAtSource)) {
0664                 ++anim;
0665                 ++animCounter;
0666                 continue;
0667             }
0668             EffectWindow *window = entry.key();
0669             d->m_justEndedAnimation = anim->id;
0670             if (anim->shader && std::none_of(entry->first.begin(), entry->first.end(), [anim](const auto &other) {
0671                     return anim->id != other.id && other.shader;
0672                 })) {
0673                 unredirect(window);
0674             }
0675             unredirect(window);
0676             animationEnded(window, anim->attribute, anim->meta);
0677             d->m_justEndedAnimation = 0;
0678             // NOTICE animationEnded is an external call and might have called "::animate"
0679             // as a result our iterators could now point random junk on the heap
0680             // so we've to restore the former states, ie. find our window list and animation
0681             if (d->m_animationsTouched) {
0682                 d->m_animationsTouched = false;
0683                 entry = d->m_animations.begin();
0684                 while (entry.key() != window && entry != d->m_animations.end()) {
0685                     ++entry;
0686                 }
0687                 Q_ASSERT(entry != d->m_animations.end()); // usercode should not delete animations from animationEnded (not even possible atm.)
0688                 anim = entry->first.begin();
0689                 Q_ASSERT(animCounter < entry->first.count());
0690                 for (int i = 0; i < animCounter; ++i) {
0691                     ++anim;
0692                 }
0693             }
0694             // If it's a closed window, keep it alive for a little bit longer until we're done
0695             // updating m_animations. Otherwise our windowDeleted slot can access m_animations
0696             // while we still modify it.
0697             if (!anim->deletedRef.isNull()) {
0698                 zombies.emplace_back(std::move(anim->deletedRef));
0699             }
0700             anim = entry->first.erase(anim);
0701             invalidateLayerRect = damageDirty = true;
0702         }
0703         if (entry->first.isEmpty()) {
0704             disconnect(entry.key(), &EffectWindow::windowExpandedGeometryChanged,
0705                        this, &AnimationEffect::_windowExpandedGeometryChanged);
0706             effects->addRepaint(entry->second);
0707             entry = d->m_animations.erase(entry);
0708         } else {
0709             if (invalidateLayerRect) {
0710                 *const_cast<QRect *>(&(entry->second)) = QRect(); // invalidate
0711             }
0712             ++entry;
0713         }
0714     }
0715 
0716     if (damageDirty) {
0717         updateLayerRepaints();
0718     }
0719     if (d->m_needSceneRepaint) {
0720         effects->addRepaintFull();
0721     } else {
0722         for (auto entry = d->m_animations.constBegin(); entry != d->m_animations.constEnd(); ++entry) {
0723             for (auto anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim) {
0724                 if (anim->startTime > clock()) {
0725                     continue;
0726                 }
0727                 if (!anim->timeLine.done()) {
0728                     entry.key()->addLayerRepaint(entry->second);
0729                     break;
0730                 }
0731             }
0732         }
0733     }
0734 
0735     effects->postPaintScreen();
0736 }
0737 
0738 float AnimationEffect::interpolated(const AniData &a, int i) const
0739 {
0740     return a.from[i] + a.timeLine.value() * (a.to[i] - a.from[i]);
0741 }
0742 
0743 float AnimationEffect::progress(const AniData &a) const
0744 {
0745     return a.startTime < clock() ? a.timeLine.value() : 0.0;
0746 }
0747 
0748 // TODO - get this out of the header - the functionpointer usage of QEasingCurve somehow sucks ;-)
0749 // qreal AnimationEffect::qecGaussian(qreal progress) // exp(-5*(2*x-1)^2)
0750 // {
0751 //     progress = 2*progress - 1;
0752 //     progress *= -5*progress;
0753 //     return qExp(progress);
0754 // }
0755 
0756 int AnimationEffect::metaData(MetaType type, uint meta)
0757 {
0758     switch (type) {
0759     case SourceAnchor:
0760         return ((meta >> 5) & 0x1f);
0761     case TargetAnchor:
0762         return (meta & 0x1f);
0763     case RelativeSourceX:
0764     case RelativeSourceY:
0765     case RelativeTargetX:
0766     case RelativeTargetY: {
0767         const int shift = 10 + type - RelativeSourceX;
0768         return ((meta >> shift) & 1);
0769     }
0770     case Axis:
0771         return ((meta >> 10) & 3);
0772     default:
0773         return 0;
0774     }
0775 }
0776 
0777 void AnimationEffect::setMetaData(MetaType type, uint value, uint &meta)
0778 {
0779     switch (type) {
0780     case SourceAnchor:
0781         meta &= ~(0x1f << 5);
0782         meta |= ((value & 0x1f) << 5);
0783         break;
0784     case TargetAnchor:
0785         meta &= ~(0x1f);
0786         meta |= (value & 0x1f);
0787         break;
0788     case RelativeSourceX:
0789     case RelativeSourceY:
0790     case RelativeTargetX:
0791     case RelativeTargetY: {
0792         const int shift = 10 + type - RelativeSourceX;
0793         if (value) {
0794             meta |= (1 << shift);
0795         } else {
0796             meta &= ~(1 << shift);
0797         }
0798         break;
0799     }
0800     case Axis:
0801         meta &= ~(3 << 10);
0802         meta |= ((value & 3) << 10);
0803         break;
0804     default:
0805         break;
0806     }
0807 }
0808 
0809 void AnimationEffect::triggerRepaint()
0810 {
0811     Q_D(AnimationEffect);
0812     for (AniMap::const_iterator entry = d->m_animations.constBegin(), mapEnd = d->m_animations.constEnd(); entry != mapEnd; ++entry) {
0813         *const_cast<QRect *>(&(entry->second)) = QRect();
0814     }
0815     updateLayerRepaints();
0816     if (d->m_needSceneRepaint) {
0817         effects->addRepaintFull();
0818     } else {
0819         AniMap::const_iterator it = d->m_animations.constBegin(), end = d->m_animations.constEnd();
0820         for (; it != end; ++it) {
0821             it.key()->addLayerRepaint(it->second);
0822         }
0823     }
0824 }
0825 
0826 static float fixOvershoot(float f, const AniData &d, short int dir, float s = 1.1)
0827 {
0828     switch (d.timeLine.easingCurve().type()) {
0829     case QEasingCurve::InOutElastic:
0830     case QEasingCurve::InOutBack:
0831         return f * s;
0832     case QEasingCurve::InElastic:
0833     case QEasingCurve::OutInElastic:
0834     case QEasingCurve::OutBack:
0835         return (dir & 2) ? f * s : f;
0836     case QEasingCurve::OutElastic:
0837     case QEasingCurve::InBack:
0838         return (dir & 1) ? f * s : f;
0839     default:
0840         return f;
0841     }
0842 }
0843 
0844 void AnimationEffect::updateLayerRepaints()
0845 {
0846     Q_D(AnimationEffect);
0847     d->m_needSceneRepaint = false;
0848     for (AniMap::const_iterator entry = d->m_animations.constBegin(), mapEnd = d->m_animations.constEnd(); entry != mapEnd; ++entry) {
0849         if (!entry->second.isNull()) {
0850             continue;
0851         }
0852         float f[2] = {1.0, 1.0};
0853         float t[2] = {0.0, 0.0};
0854         bool createRegion = false;
0855         QList<QRect> rects;
0856         QRect *layerRect = const_cast<QRect *>(&(entry->second));
0857         for (QList<AniData>::const_iterator anim = entry->first.constBegin(), animEnd = entry->first.constEnd(); anim != animEnd; ++anim) {
0858             if (anim->startTime > clock()) {
0859                 continue;
0860             }
0861             switch (anim->attribute) {
0862             case Opacity:
0863             case Brightness:
0864             case Saturation:
0865             case CrossFadePrevious:
0866             case Shader:
0867             case ShaderUniform:
0868                 createRegion = true;
0869                 break;
0870             case Rotation:
0871                 createRegion = false;
0872                 *layerRect = QRect(QPoint(0, 0), effects->virtualScreenSize());
0873                 goto region_creation; // sic! no need to do anything else
0874             case Generic:
0875                 d->m_needSceneRepaint = true; // we don't know whether this will change visual stacking order
0876                 return; // sic! no need to do anything else
0877             case Translation:
0878             case Position: {
0879                 createRegion = true;
0880                 QRect r(entry.key()->frameGeometry().toRect());
0881                 int x[2] = {0, 0};
0882                 int y[2] = {0, 0};
0883                 if (anim->attribute == Translation) {
0884                     x[0] = anim->from[0];
0885                     x[1] = anim->to[0];
0886                     y[0] = anim->from[1];
0887                     y[1] = anim->to[1];
0888                 } else {
0889                     if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) {
0890                         x[0] = anim->from[0] - xCoord(r, metaData(SourceAnchor, anim->meta));
0891                         x[1] = anim->to[0] - xCoord(r, metaData(TargetAnchor, anim->meta));
0892                     }
0893                     if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) {
0894                         y[0] = anim->from[1] - yCoord(r, metaData(SourceAnchor, anim->meta));
0895                         y[1] = anim->to[1] - yCoord(r, metaData(TargetAnchor, anim->meta));
0896                     }
0897                 }
0898                 r = entry.key()->expandedGeometry().toRect();
0899                 rects << r.translated(x[0], y[0]) << r.translated(x[1], y[1]);
0900                 break;
0901             }
0902             case Clip:
0903                 createRegion = true;
0904                 break;
0905             case Size:
0906             case Scale: {
0907                 createRegion = true;
0908                 const QSize sz = entry.key()->frameGeometry().size().toSize();
0909                 float fx = std::max(fixOvershoot(anim->from[0], *anim, 1), fixOvershoot(anim->to[0], *anim, 2));
0910                 //                     float fx = std::max(interpolated(*anim,0), anim->to[0]);
0911                 if (fx >= 0.0) {
0912                     if (anim->attribute == Size) {
0913                         fx /= sz.width();
0914                     }
0915                     f[0] *= fx;
0916                     t[0] += geometryCompensation(anim->meta & AnimationEffect::Horizontal, fx) * sz.width();
0917                 }
0918                 //                     float fy = std::max(interpolated(*anim,1), anim->to[1]);
0919                 float fy = std::max(fixOvershoot(anim->from[1], *anim, 1), fixOvershoot(anim->to[1], *anim, 2));
0920                 if (fy >= 0.0) {
0921                     if (anim->attribute == Size) {
0922                         fy /= sz.height();
0923                     }
0924                     if (!anim->isOneDimensional()) {
0925                         f[1] *= fy;
0926                         t[1] += geometryCompensation(anim->meta & AnimationEffect::Vertical, fy) * sz.height();
0927                     } else if (((anim->meta & AnimationEffect::Vertical) >> 1) != (anim->meta & AnimationEffect::Horizontal)) {
0928                         f[1] *= fx;
0929                         t[1] += geometryCompensation(anim->meta & AnimationEffect::Vertical, fx) * sz.height();
0930                     }
0931                 }
0932                 break;
0933             }
0934             }
0935         }
0936     region_creation:
0937         if (createRegion) {
0938             const QRect geo = entry.key()->expandedGeometry().toRect();
0939             if (rects.isEmpty()) {
0940                 rects << geo;
0941             }
0942             QList<QRect>::const_iterator r, rEnd = rects.constEnd();
0943             for (r = rects.constBegin(); r != rEnd; ++r) { // transform
0944                 const_cast<QRect *>(&(*r))->setSize(QSize(qRound(r->width() * f[0]), qRound(r->height() * f[1])));
0945                 const_cast<QRect *>(&(*r))->translate(t[0], t[1]); // "const_cast" - don't do that at home, kids ;-)
0946             }
0947             QRect rect = rects.at(0);
0948             if (rects.count() > 1) {
0949                 for (r = rects.constBegin() + 1; r != rEnd; ++r) { // unite
0950                     rect |= *r;
0951                 }
0952                 const int dx = 110 * (rect.width() - geo.width()) / 100 + 1 - rect.width() + geo.width();
0953                 const int dy = 110 * (rect.height() - geo.height()) / 100 + 1 - rect.height() + geo.height();
0954                 rect.adjust(-dx, -dy, dx, dy); // fix pot. overshoot
0955             }
0956             *layerRect = rect;
0957         }
0958     }
0959 }
0960 
0961 void AnimationEffect::_windowExpandedGeometryChanged(KWin::EffectWindow *w)
0962 {
0963     Q_D(AnimationEffect);
0964     AniMap::const_iterator entry = d->m_animations.constFind(w);
0965     if (entry != d->m_animations.constEnd()) {
0966         *const_cast<QRect *>(&(entry->second)) = QRect();
0967         updateLayerRepaints();
0968         if (!entry->second.isNull()) { // actually got updated, ie. is in use - ensure it get's a repaint
0969             w->addLayerRepaint(entry->second);
0970         }
0971     }
0972 }
0973 
0974 void AnimationEffect::_windowClosed(EffectWindow *w)
0975 {
0976     Q_D(AnimationEffect);
0977 
0978     auto it = d->m_animations.find(w);
0979     if (it == d->m_animations.end()) {
0980         return;
0981     }
0982 
0983     QList<AniData> &animations = (*it).first;
0984     for (auto animationIt = animations.begin(); animationIt != animations.end(); ++animationIt) {
0985         if (animationIt->keepAlive) {
0986             animationIt->deletedRef = EffectWindowDeletedRef(w);
0987         }
0988     }
0989 }
0990 
0991 void AnimationEffect::_windowDeleted(EffectWindow *w)
0992 {
0993     Q_D(AnimationEffect);
0994     d->m_animations.remove(w);
0995 }
0996 
0997 QString AnimationEffect::debug(const QString & /*parameter*/) const
0998 {
0999     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 
1020 AnimationEffect::AniMap AnimationEffect::state() const
1021 {
1022     Q_D(const AnimationEffect);
1023     return d->m_animations;
1024 }
1025 
1026 } // namespace KWin
1027 
1028 #include "moc_animationeffect.cpp"