File indexing completed on 2024-05-19 16:34:30

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