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

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org>
0006     SPDX-FileCopyrightText: 2009 Lucas Murray <lmurray@undefinedfire.com>
0007     SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "kwineffects.h"
0013 
0014 #include "config-kwin.h"
0015 
0016 #include <QFontMetrics>
0017 #include <QMatrix4x4>
0018 #include <QPainter>
0019 #include <QPixmap>
0020 #include <QTimeLine>
0021 #include <QVariant>
0022 #include <QWindow>
0023 #include <QtMath>
0024 
0025 #include <kconfiggroup.h>
0026 #include <ksharedconfig.h>
0027 
0028 #include <optional>
0029 
0030 namespace KWin
0031 {
0032 
0033 void WindowPrePaintData::setTranslucent()
0034 {
0035     mask |= Effect::PAINT_WINDOW_TRANSLUCENT;
0036     mask &= ~Effect::PAINT_WINDOW_OPAQUE;
0037     opaque = QRegion(); // cannot clip, will be transparent
0038 }
0039 
0040 void WindowPrePaintData::setTransformed()
0041 {
0042     mask |= Effect::PAINT_WINDOW_TRANSFORMED;
0043 }
0044 
0045 class PaintDataPrivate
0046 {
0047 public:
0048     PaintDataPrivate()
0049         : scale(1., 1., 1.)
0050         , rotationAxis(0, 0, 1.)
0051         , rotationAngle(0.)
0052     {
0053     }
0054     QVector3D scale;
0055     QVector3D translation;
0056 
0057     QVector3D rotationAxis;
0058     QVector3D rotationOrigin;
0059     qreal rotationAngle;
0060 };
0061 
0062 PaintData::PaintData()
0063     : d(std::make_unique<PaintDataPrivate>())
0064 {
0065 }
0066 
0067 PaintData::~PaintData() = default;
0068 
0069 qreal PaintData::xScale() const
0070 {
0071     return d->scale.x();
0072 }
0073 
0074 qreal PaintData::yScale() const
0075 {
0076     return d->scale.y();
0077 }
0078 
0079 qreal PaintData::zScale() const
0080 {
0081     return d->scale.z();
0082 }
0083 
0084 void PaintData::setScale(const QVector2D &scale)
0085 {
0086     d->scale.setX(scale.x());
0087     d->scale.setY(scale.y());
0088 }
0089 
0090 void PaintData::setScale(const QVector3D &scale)
0091 {
0092     d->scale = scale;
0093 }
0094 void PaintData::setXScale(qreal scale)
0095 {
0096     d->scale.setX(scale);
0097 }
0098 
0099 void PaintData::setYScale(qreal scale)
0100 {
0101     d->scale.setY(scale);
0102 }
0103 
0104 void PaintData::setZScale(qreal scale)
0105 {
0106     d->scale.setZ(scale);
0107 }
0108 
0109 const QVector3D &PaintData::scale() const
0110 {
0111     return d->scale;
0112 }
0113 
0114 void PaintData::setXTranslation(qreal translate)
0115 {
0116     d->translation.setX(translate);
0117 }
0118 
0119 void PaintData::setYTranslation(qreal translate)
0120 {
0121     d->translation.setY(translate);
0122 }
0123 
0124 void PaintData::setZTranslation(qreal translate)
0125 {
0126     d->translation.setZ(translate);
0127 }
0128 
0129 void PaintData::translate(qreal x, qreal y, qreal z)
0130 {
0131     translate(QVector3D(x, y, z));
0132 }
0133 
0134 void PaintData::translate(const QVector3D &t)
0135 {
0136     d->translation += t;
0137 }
0138 
0139 qreal PaintData::xTranslation() const
0140 {
0141     return d->translation.x();
0142 }
0143 
0144 qreal PaintData::yTranslation() const
0145 {
0146     return d->translation.y();
0147 }
0148 
0149 qreal PaintData::zTranslation() const
0150 {
0151     return d->translation.z();
0152 }
0153 
0154 const QVector3D &PaintData::translation() const
0155 {
0156     return d->translation;
0157 }
0158 
0159 qreal PaintData::rotationAngle() const
0160 {
0161     return d->rotationAngle;
0162 }
0163 
0164 QVector3D PaintData::rotationAxis() const
0165 {
0166     return d->rotationAxis;
0167 }
0168 
0169 QVector3D PaintData::rotationOrigin() const
0170 {
0171     return d->rotationOrigin;
0172 }
0173 
0174 void PaintData::setRotationAngle(qreal angle)
0175 {
0176     d->rotationAngle = angle;
0177 }
0178 
0179 void PaintData::setRotationAxis(Qt::Axis axis)
0180 {
0181     switch (axis) {
0182     case Qt::XAxis:
0183         setRotationAxis(QVector3D(1, 0, 0));
0184         break;
0185     case Qt::YAxis:
0186         setRotationAxis(QVector3D(0, 1, 0));
0187         break;
0188     case Qt::ZAxis:
0189         setRotationAxis(QVector3D(0, 0, 1));
0190         break;
0191     }
0192 }
0193 
0194 void PaintData::setRotationAxis(const QVector3D &axis)
0195 {
0196     d->rotationAxis = axis;
0197 }
0198 
0199 void PaintData::setRotationOrigin(const QVector3D &origin)
0200 {
0201     d->rotationOrigin = origin;
0202 }
0203 
0204 QMatrix4x4 PaintData::toMatrix(qreal deviceScale) const
0205 {
0206     QMatrix4x4 ret;
0207     if (d->translation != QVector3D(0, 0, 0)) {
0208         ret.translate(d->translation * deviceScale);
0209     }
0210     if (d->scale != QVector3D(1, 1, 1)) {
0211         ret.scale(d->scale);
0212     }
0213 
0214     if (d->rotationAngle != 0) {
0215         ret.translate(d->rotationOrigin * deviceScale);
0216         ret.rotate(d->rotationAngle, d->rotationAxis);
0217         ret.translate(-d->rotationOrigin * deviceScale);
0218     }
0219 
0220     return ret;
0221 }
0222 
0223 class WindowPaintDataPrivate
0224 {
0225 public:
0226     qreal opacity;
0227     qreal saturation;
0228     qreal brightness;
0229     int screen;
0230     qreal crossFadeProgress;
0231     QMatrix4x4 projectionMatrix;
0232     std::optional<qreal> renderTargetScale = std::nullopt;
0233 };
0234 
0235 WindowPaintData::WindowPaintData()
0236     : WindowPaintData(QMatrix4x4())
0237 {
0238 }
0239 
0240 WindowPaintData::WindowPaintData(const QMatrix4x4 &projectionMatrix)
0241     : PaintData()
0242     , d(std::make_unique<WindowPaintDataPrivate>())
0243 {
0244     setProjectionMatrix(projectionMatrix);
0245     setOpacity(1.0);
0246     setSaturation(1.0);
0247     setBrightness(1.0);
0248     setScreen(0);
0249     setCrossFadeProgress(0.0);
0250 }
0251 
0252 WindowPaintData::WindowPaintData(const WindowPaintData &other)
0253     : PaintData()
0254     , d(std::make_unique<WindowPaintDataPrivate>())
0255 {
0256     setXScale(other.xScale());
0257     setYScale(other.yScale());
0258     setZScale(other.zScale());
0259     translate(other.translation());
0260     setRotationOrigin(other.rotationOrigin());
0261     setRotationAxis(other.rotationAxis());
0262     setRotationAngle(other.rotationAngle());
0263     setOpacity(other.opacity());
0264     setSaturation(other.saturation());
0265     setBrightness(other.brightness());
0266     setScreen(other.screen());
0267     setCrossFadeProgress(other.crossFadeProgress());
0268     setProjectionMatrix(other.projectionMatrix());
0269 }
0270 
0271 WindowPaintData::~WindowPaintData() = default;
0272 
0273 qreal WindowPaintData::opacity() const
0274 {
0275     return d->opacity;
0276 }
0277 
0278 qreal WindowPaintData::saturation() const
0279 {
0280     return d->saturation;
0281 }
0282 
0283 qreal WindowPaintData::brightness() const
0284 {
0285     return d->brightness;
0286 }
0287 
0288 int WindowPaintData::screen() const
0289 {
0290     return d->screen;
0291 }
0292 
0293 void WindowPaintData::setOpacity(qreal opacity)
0294 {
0295     d->opacity = opacity;
0296 }
0297 
0298 void WindowPaintData::setSaturation(qreal saturation) const
0299 {
0300     d->saturation = saturation;
0301 }
0302 
0303 void WindowPaintData::setBrightness(qreal brightness)
0304 {
0305     d->brightness = brightness;
0306 }
0307 
0308 void WindowPaintData::setScreen(int screen) const
0309 {
0310     d->screen = screen;
0311 }
0312 
0313 qreal WindowPaintData::crossFadeProgress() const
0314 {
0315     return d->crossFadeProgress;
0316 }
0317 
0318 void WindowPaintData::setCrossFadeProgress(qreal factor)
0319 {
0320     d->crossFadeProgress = std::clamp(factor, 0.0, 1.0);
0321 }
0322 
0323 qreal WindowPaintData::multiplyOpacity(qreal factor)
0324 {
0325     d->opacity *= factor;
0326     return d->opacity;
0327 }
0328 
0329 qreal WindowPaintData::multiplySaturation(qreal factor)
0330 {
0331     d->saturation *= factor;
0332     return d->saturation;
0333 }
0334 
0335 qreal WindowPaintData::multiplyBrightness(qreal factor)
0336 {
0337     d->brightness *= factor;
0338     return d->brightness;
0339 }
0340 
0341 void WindowPaintData::setProjectionMatrix(const QMatrix4x4 &matrix)
0342 {
0343     d->projectionMatrix = matrix;
0344 }
0345 
0346 QMatrix4x4 WindowPaintData::projectionMatrix() const
0347 {
0348     return d->projectionMatrix;
0349 }
0350 
0351 QMatrix4x4 &WindowPaintData::rprojectionMatrix()
0352 {
0353     return d->projectionMatrix;
0354 }
0355 
0356 WindowPaintData &WindowPaintData::operator*=(qreal scale)
0357 {
0358     this->setXScale(this->xScale() * scale);
0359     this->setYScale(this->yScale() * scale);
0360     this->setZScale(this->zScale() * scale);
0361     return *this;
0362 }
0363 
0364 WindowPaintData &WindowPaintData::operator*=(const QVector2D &scale)
0365 {
0366     this->setXScale(this->xScale() * scale.x());
0367     this->setYScale(this->yScale() * scale.y());
0368     return *this;
0369 }
0370 
0371 WindowPaintData &WindowPaintData::operator*=(const QVector3D &scale)
0372 {
0373     this->setXScale(this->xScale() * scale.x());
0374     this->setYScale(this->yScale() * scale.y());
0375     this->setZScale(this->zScale() * scale.z());
0376     return *this;
0377 }
0378 
0379 WindowPaintData &WindowPaintData::operator+=(const QPointF &translation)
0380 {
0381     return this->operator+=(QVector3D(translation));
0382 }
0383 
0384 WindowPaintData &WindowPaintData::operator+=(const QPoint &translation)
0385 {
0386     return this->operator+=(QVector3D(translation));
0387 }
0388 
0389 WindowPaintData &WindowPaintData::operator+=(const QVector2D &translation)
0390 {
0391     return this->operator+=(QVector3D(translation));
0392 }
0393 
0394 WindowPaintData &WindowPaintData::operator+=(const QVector3D &translation)
0395 {
0396     translate(translation);
0397     return *this;
0398 }
0399 
0400 std::optional<qreal> WindowPaintData::renderTargetScale() const
0401 {
0402     return d->renderTargetScale;
0403 }
0404 
0405 void WindowPaintData::setRenderTargetScale(qreal scale)
0406 {
0407     d->renderTargetScale = scale;
0408 }
0409 
0410 class ScreenPaintData::Private
0411 {
0412 public:
0413     QMatrix4x4 projectionMatrix;
0414     EffectScreen *screen = nullptr;
0415 };
0416 
0417 ScreenPaintData::ScreenPaintData()
0418     : d(new Private())
0419 {
0420 }
0421 
0422 ScreenPaintData::ScreenPaintData(const QMatrix4x4 &projectionMatrix, EffectScreen *screen)
0423     : d(new Private())
0424 {
0425     d->projectionMatrix = projectionMatrix;
0426     d->screen = screen;
0427 }
0428 
0429 ScreenPaintData::~ScreenPaintData() = default;
0430 
0431 ScreenPaintData::ScreenPaintData(const ScreenPaintData &other)
0432     : d(new Private())
0433 {
0434     d->projectionMatrix = other.d->projectionMatrix;
0435     d->screen = other.d->screen;
0436 }
0437 
0438 ScreenPaintData &ScreenPaintData::operator=(const ScreenPaintData &rhs)
0439 {
0440     d->projectionMatrix = rhs.d->projectionMatrix;
0441     d->screen = rhs.d->screen;
0442     return *this;
0443 }
0444 
0445 QMatrix4x4 ScreenPaintData::projectionMatrix() const
0446 {
0447     return d->projectionMatrix;
0448 }
0449 
0450 EffectScreen *ScreenPaintData::screen() const
0451 {
0452     return d->screen;
0453 }
0454 
0455 //****************************************
0456 // Effect
0457 //****************************************
0458 
0459 Effect::Effect(QObject *parent)
0460     : QObject(parent)
0461 {
0462 }
0463 
0464 Effect::~Effect()
0465 {
0466 }
0467 
0468 void Effect::reconfigure(ReconfigureFlags)
0469 {
0470 }
0471 
0472 void *Effect::proxy()
0473 {
0474     return nullptr;
0475 }
0476 
0477 void Effect::windowInputMouseEvent(QEvent *)
0478 {
0479 }
0480 
0481 void Effect::grabbedKeyboardEvent(QKeyEvent *)
0482 {
0483 }
0484 
0485 bool Effect::borderActivated(ElectricBorder)
0486 {
0487     return false;
0488 }
0489 
0490 void Effect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
0491 {
0492     effects->prePaintScreen(data, presentTime);
0493 }
0494 
0495 void Effect::paintScreen(int mask, const QRegion &region, ScreenPaintData &data)
0496 {
0497     effects->paintScreen(mask, region, data);
0498 }
0499 
0500 void Effect::postPaintScreen()
0501 {
0502     effects->postPaintScreen();
0503 }
0504 
0505 void Effect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
0506 {
0507     effects->prePaintWindow(w, data, presentTime);
0508 }
0509 
0510 void Effect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
0511 {
0512     effects->paintWindow(w, mask, region, data);
0513 }
0514 
0515 void Effect::postPaintWindow(EffectWindow *w)
0516 {
0517     effects->postPaintWindow(w);
0518 }
0519 
0520 bool Effect::provides(Feature)
0521 {
0522     return false;
0523 }
0524 
0525 bool Effect::isActive() const
0526 {
0527     return true;
0528 }
0529 
0530 QString Effect::debug(const QString &) const
0531 {
0532     return QString();
0533 }
0534 
0535 void Effect::drawWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
0536 {
0537     effects->drawWindow(w, mask, region, data);
0538 }
0539 
0540 void Effect::setPositionTransformations(WindowPaintData &data, QRect &region, EffectWindow *w,
0541                                         const QRect &r, Qt::AspectRatioMode aspect)
0542 {
0543     QSizeF size = w->size();
0544     size.scale(r.size(), aspect);
0545     data.setXScale(size.width() / double(w->width()));
0546     data.setYScale(size.height() / double(w->height()));
0547     int width = int(w->width() * data.xScale());
0548     int height = int(w->height() * data.yScale());
0549     int x = r.x() + (r.width() - width) / 2;
0550     int y = r.y() + (r.height() - height) / 2;
0551     region = QRect(x, y, width, height);
0552     data.setXTranslation(x - w->x());
0553     data.setYTranslation(y - w->y());
0554 }
0555 
0556 QPoint Effect::cursorPos()
0557 {
0558     return effects->cursorPos();
0559 }
0560 
0561 double Effect::animationTime(const KConfigGroup &cfg, const QString &key, int defaultTime)
0562 {
0563     int time = cfg.readEntry(key, 0);
0564     return time != 0 ? time : std::max(defaultTime * effects->animationTimeFactor(), 1.);
0565 }
0566 
0567 double Effect::animationTime(int defaultTime)
0568 {
0569     // at least 1ms, otherwise 0ms times can break some things
0570     return std::max(defaultTime * effects->animationTimeFactor(), 1.);
0571 }
0572 
0573 int Effect::requestedEffectChainPosition() const
0574 {
0575     return 0;
0576 }
0577 
0578 xcb_connection_t *Effect::xcbConnection() const
0579 {
0580     return effects->xcbConnection();
0581 }
0582 
0583 xcb_window_t Effect::x11RootWindow() const
0584 {
0585     return effects->x11RootWindow();
0586 }
0587 
0588 bool Effect::touchDown(qint32 id, const QPointF &pos, std::chrono::microseconds time)
0589 {
0590     return false;
0591 }
0592 
0593 bool Effect::touchMotion(qint32 id, const QPointF &pos, std::chrono::microseconds time)
0594 {
0595     return false;
0596 }
0597 
0598 bool Effect::touchUp(qint32 id, std::chrono::microseconds time)
0599 {
0600     return false;
0601 }
0602 
0603 bool Effect::perform(Feature feature, const QVariantList &arguments)
0604 {
0605     return false;
0606 }
0607 
0608 bool Effect::tabletToolEvent(QTabletEvent *event)
0609 {
0610     return false;
0611 }
0612 
0613 bool Effect::tabletToolButtonEvent(uint button, bool pressed, quint64 tabletToolId)
0614 {
0615     return false;
0616 }
0617 
0618 bool Effect::tabletPadButtonEvent(uint button, bool pressed, void *tabletPadId)
0619 {
0620     return false;
0621 }
0622 
0623 bool Effect::tabletPadStripEvent(int number, int position, bool isFinger, void *tabletPadId)
0624 {
0625     return false;
0626 }
0627 
0628 bool Effect::tabletPadRingEvent(int number, int position, bool isFinger, void *tabletPadId)
0629 {
0630     return false;
0631 }
0632 
0633 bool Effect::blocksDirectScanout() const
0634 {
0635     return true;
0636 }
0637 
0638 //****************************************
0639 // EffectFactory
0640 //****************************************
0641 EffectPluginFactory::EffectPluginFactory()
0642 {
0643 }
0644 
0645 EffectPluginFactory::~EffectPluginFactory()
0646 {
0647 }
0648 
0649 bool EffectPluginFactory::enabledByDefault() const
0650 {
0651     return true;
0652 }
0653 
0654 bool EffectPluginFactory::isSupported() const
0655 {
0656     return true;
0657 }
0658 
0659 //****************************************
0660 // EffectsHandler
0661 //****************************************
0662 
0663 EffectsHandler::EffectsHandler(CompositingType type)
0664     : compositing_type(type)
0665 {
0666     if (compositing_type == NoCompositing) {
0667         return;
0668     }
0669     KWin::effects = this;
0670     connect(this, QOverload<int, int>::of(&EffectsHandler::desktopChanged), this, &EffectsHandler::desktopChangedLegacy);
0671 }
0672 
0673 EffectsHandler::~EffectsHandler()
0674 {
0675     // All effects should already be unloaded by Impl dtor
0676     Q_ASSERT(loaded_effects.count() == 0);
0677     KWin::effects = nullptr;
0678 }
0679 
0680 CompositingType EffectsHandler::compositingType() const
0681 {
0682     return compositing_type;
0683 }
0684 
0685 bool EffectsHandler::isOpenGLCompositing() const
0686 {
0687     return compositing_type & OpenGLCompositing;
0688 }
0689 
0690 QRectF EffectsHandler::mapToRenderTarget(const QRectF &rect) const
0691 {
0692     const QRectF targetRect = renderTargetRect();
0693     const qreal targetScale = renderTargetScale();
0694 
0695     return QRectF((rect.x() - targetRect.x()) * targetScale,
0696                   (rect.y() - targetRect.y()) * targetScale,
0697                   rect.width() * targetScale,
0698                   rect.height() * targetScale);
0699 }
0700 
0701 QRegion EffectsHandler::mapToRenderTarget(const QRegion &region) const
0702 {
0703     QRegion result;
0704     for (const QRect &rect : region) {
0705         result += mapToRenderTarget(QRectF(rect)).toRect();
0706     }
0707     return result;
0708 }
0709 
0710 EffectsHandler *effects = nullptr;
0711 
0712 EffectScreen::EffectScreen(QObject *parent)
0713     : QObject(parent)
0714 {
0715 }
0716 
0717 QPointF EffectScreen::mapToGlobal(const QPointF &pos) const
0718 {
0719     return pos + geometry().topLeft();
0720 }
0721 
0722 QPointF EffectScreen::mapFromGlobal(const QPointF &pos) const
0723 {
0724     return pos - geometry().topLeft();
0725 }
0726 
0727 //****************************************
0728 // EffectWindow
0729 //****************************************
0730 
0731 class Q_DECL_HIDDEN EffectWindow::Private
0732 {
0733 public:
0734     Private(EffectWindow *q);
0735 
0736     EffectWindow *q;
0737 };
0738 
0739 EffectWindow::Private::Private(EffectWindow *q)
0740     : q(q)
0741 {
0742 }
0743 
0744 EffectWindow::EffectWindow()
0745     : d(new Private(this))
0746 {
0747 }
0748 
0749 EffectWindow::~EffectWindow()
0750 {
0751 }
0752 
0753 bool EffectWindow::isOnActivity(const QString &activity) const
0754 {
0755     const QStringList _activities = activities();
0756     return _activities.isEmpty() || _activities.contains(activity);
0757 }
0758 
0759 bool EffectWindow::isOnAllActivities() const
0760 {
0761     return activities().isEmpty();
0762 }
0763 
0764 void EffectWindow::setMinimized(bool min)
0765 {
0766     if (min) {
0767         minimize();
0768     } else {
0769         unminimize();
0770     }
0771 }
0772 
0773 bool EffectWindow::isOnCurrentActivity() const
0774 {
0775     return isOnActivity(effects->currentActivity());
0776 }
0777 
0778 bool EffectWindow::isOnCurrentDesktop() const
0779 {
0780     return isOnDesktop(effects->currentDesktop());
0781 }
0782 
0783 bool EffectWindow::isOnDesktop(int d) const
0784 {
0785     const QVector<uint> ds = desktops();
0786     return ds.isEmpty() || ds.contains(d);
0787 }
0788 
0789 bool EffectWindow::isOnAllDesktops() const
0790 {
0791     return desktops().isEmpty();
0792 }
0793 
0794 bool EffectWindow::hasDecoration() const
0795 {
0796     return contentsRect() != QRect(0, 0, width(), height());
0797 }
0798 
0799 bool EffectWindow::isVisible() const
0800 {
0801     return !isMinimized()
0802         && isOnCurrentDesktop()
0803         && isOnCurrentActivity();
0804 }
0805 
0806 //****************************************
0807 // EffectWindowGroup
0808 //****************************************
0809 
0810 EffectWindowGroup::~EffectWindowGroup()
0811 {
0812 }
0813 
0814 /***************************************************************
0815  WindowQuad
0816 ***************************************************************/
0817 
0818 WindowQuad WindowQuad::makeSubQuad(double x1, double y1, double x2, double y2) const
0819 {
0820     Q_ASSERT(x1 < x2 && y1 < y2 && x1 >= left() && x2 <= right() && y1 >= top() && y2 <= bottom());
0821     WindowQuad ret(*this);
0822     // vertices are clockwise starting from topleft
0823     ret.verts[0].px = x1;
0824     ret.verts[3].px = x1;
0825     ret.verts[1].px = x2;
0826     ret.verts[2].px = x2;
0827     ret.verts[0].py = y1;
0828     ret.verts[1].py = y1;
0829     ret.verts[2].py = y2;
0830     ret.verts[3].py = y2;
0831 
0832     const double xOrigin = left();
0833     const double yOrigin = top();
0834 
0835     const double widthReciprocal = 1 / (right() - xOrigin);
0836     const double heightReciprocal = 1 / (bottom() - yOrigin);
0837 
0838     for (int i = 0; i < 4; ++i) {
0839         const double w1 = (ret.verts[i].px - xOrigin) * widthReciprocal;
0840         const double w2 = (ret.verts[i].py - yOrigin) * heightReciprocal;
0841 
0842         // Use bilinear interpolation to compute the texture coords.
0843         ret.verts[i].tx = (1 - w1) * (1 - w2) * verts[0].tx + w1 * (1 - w2) * verts[1].tx + w1 * w2 * verts[2].tx + (1 - w1) * w2 * verts[3].tx;
0844         ret.verts[i].ty = (1 - w1) * (1 - w2) * verts[0].ty + w1 * (1 - w2) * verts[1].ty + w1 * w2 * verts[2].ty + (1 - w1) * w2 * verts[3].ty;
0845     }
0846 
0847     return ret;
0848 }
0849 
0850 /***************************************************************
0851  WindowQuadList
0852 ***************************************************************/
0853 
0854 WindowQuadList WindowQuadList::splitAtX(double x) const
0855 {
0856     WindowQuadList ret;
0857     ret.reserve(count());
0858     for (const WindowQuad &quad : *this) {
0859         bool wholeleft = true;
0860         bool wholeright = true;
0861         for (int i = 0; i < 4; ++i) {
0862             if (quad[i].x() < x) {
0863                 wholeright = false;
0864             }
0865             if (quad[i].x() > x) {
0866                 wholeleft = false;
0867             }
0868         }
0869         if (wholeleft || wholeright) { // is whole in one split part
0870             ret.append(quad);
0871             continue;
0872         }
0873         if (quad.top() == quad.bottom() || quad.left() == quad.right()) { // quad has no size
0874             ret.append(quad);
0875             continue;
0876         }
0877         ret.append(quad.makeSubQuad(quad.left(), quad.top(), x, quad.bottom()));
0878         ret.append(quad.makeSubQuad(x, quad.top(), quad.right(), quad.bottom()));
0879     }
0880     return ret;
0881 }
0882 
0883 WindowQuadList WindowQuadList::splitAtY(double y) const
0884 {
0885     WindowQuadList ret;
0886     ret.reserve(count());
0887     for (const WindowQuad &quad : *this) {
0888         bool wholetop = true;
0889         bool wholebottom = true;
0890         for (int i = 0; i < 4; ++i) {
0891             if (quad[i].y() < y) {
0892                 wholebottom = false;
0893             }
0894             if (quad[i].y() > y) {
0895                 wholetop = false;
0896             }
0897         }
0898         if (wholetop || wholebottom) { // is whole in one split part
0899             ret.append(quad);
0900             continue;
0901         }
0902         if (quad.top() == quad.bottom() || quad.left() == quad.right()) { // quad has no size
0903             ret.append(quad);
0904             continue;
0905         }
0906         ret.append(quad.makeSubQuad(quad.left(), quad.top(), quad.right(), y));
0907         ret.append(quad.makeSubQuad(quad.left(), y, quad.right(), quad.bottom()));
0908     }
0909     return ret;
0910 }
0911 
0912 WindowQuadList WindowQuadList::makeGrid(int maxQuadSize) const
0913 {
0914     if (empty()) {
0915         return *this;
0916     }
0917 
0918     // Find the bounding rectangle
0919     double left = first().left();
0920     double right = first().right();
0921     double top = first().top();
0922     double bottom = first().bottom();
0923 
0924     for (const WindowQuad &quad : std::as_const(*this)) {
0925         left = std::min(left, quad.left());
0926         right = std::max(right, quad.right());
0927         top = std::min(top, quad.top());
0928         bottom = std::max(bottom, quad.bottom());
0929     }
0930 
0931     WindowQuadList ret;
0932 
0933     for (const WindowQuad &quad : std::as_const(*this)) {
0934         const double quadLeft = quad.left();
0935         const double quadRight = quad.right();
0936         const double quadTop = quad.top();
0937         const double quadBottom = quad.bottom();
0938 
0939         // sanity check, see BUG 390953
0940         if (quadLeft == quadRight || quadTop == quadBottom) {
0941             ret.append(quad);
0942             continue;
0943         }
0944 
0945         // Compute the top-left corner of the first intersecting grid cell
0946         const double xBegin = left + qFloor((quadLeft - left) / maxQuadSize) * maxQuadSize;
0947         const double yBegin = top + qFloor((quadTop - top) / maxQuadSize) * maxQuadSize;
0948 
0949         // Loop over all intersecting cells and add sub-quads
0950         for (double y = yBegin; y < quadBottom; y += maxQuadSize) {
0951             const double y0 = std::max(y, quadTop);
0952             const double y1 = std::min(quadBottom, y + maxQuadSize);
0953 
0954             for (double x = xBegin; x < quadRight; x += maxQuadSize) {
0955                 const double x0 = std::max(x, quadLeft);
0956                 const double x1 = std::min(quadRight, x + maxQuadSize);
0957 
0958                 ret.append(quad.makeSubQuad(x0, y0, x1, y1));
0959             }
0960         }
0961     }
0962 
0963     return ret;
0964 }
0965 
0966 WindowQuadList WindowQuadList::makeRegularGrid(int xSubdivisions, int ySubdivisions) const
0967 {
0968     if (empty()) {
0969         return *this;
0970     }
0971 
0972     // Find the bounding rectangle
0973     double left = first().left();
0974     double right = first().right();
0975     double top = first().top();
0976     double bottom = first().bottom();
0977 
0978     for (const WindowQuad &quad : *this) {
0979         left = std::min(left, quad.left());
0980         right = std::max(right, quad.right());
0981         top = std::min(top, quad.top());
0982         bottom = std::max(bottom, quad.bottom());
0983     }
0984 
0985     double xIncrement = (right - left) / xSubdivisions;
0986     double yIncrement = (bottom - top) / ySubdivisions;
0987 
0988     WindowQuadList ret;
0989 
0990     for (const WindowQuad &quad : *this) {
0991         const double quadLeft = quad.left();
0992         const double quadRight = quad.right();
0993         const double quadTop = quad.top();
0994         const double quadBottom = quad.bottom();
0995 
0996         // sanity check, see BUG 390953
0997         if (quadLeft == quadRight || quadTop == quadBottom) {
0998             ret.append(quad);
0999             continue;
1000         }
1001 
1002         // Compute the top-left corner of the first intersecting grid cell
1003         const double xBegin = left + qFloor((quadLeft - left) / xIncrement) * xIncrement;
1004         const double yBegin = top + qFloor((quadTop - top) / yIncrement) * yIncrement;
1005 
1006         // Loop over all intersecting cells and add sub-quads
1007         for (double y = yBegin; y < quadBottom; y += yIncrement) {
1008             const double y0 = std::max(y, quadTop);
1009             const double y1 = std::min(quadBottom, y + yIncrement);
1010 
1011             for (double x = xBegin; x < quadRight; x += xIncrement) {
1012                 const double x0 = std::max(x, quadLeft);
1013                 const double x1 = std::min(quadRight, x + xIncrement);
1014 
1015                 ret.append(quad.makeSubQuad(x0, y0, x1, y1));
1016             }
1017         }
1018     }
1019 
1020     return ret;
1021 }
1022 
1023 void RenderGeometry::copy(std::span<GLVertex2D> destination)
1024 {
1025     Q_ASSERT(int(destination.size()) >= size());
1026     for (std::size_t i = 0; i < destination.size(); ++i) {
1027         destination[i] = at(i);
1028     }
1029 }
1030 
1031 void RenderGeometry::appendWindowVertex(const WindowVertex &windowVertex, qreal deviceScale)
1032 {
1033     GLVertex2D glVertex;
1034     switch (m_vertexSnappingMode) {
1035     case VertexSnappingMode::None:
1036         glVertex.position = QVector2D(windowVertex.x(), windowVertex.y()) * deviceScale;
1037         break;
1038     case VertexSnappingMode::Round:
1039         glVertex.position = roundVector(QVector2D(windowVertex.x(), windowVertex.y()) * deviceScale);
1040         break;
1041     }
1042     glVertex.texcoord = QVector2D(windowVertex.u(), windowVertex.v());
1043     append(glVertex);
1044 }
1045 
1046 void RenderGeometry::appendWindowQuad(const WindowQuad &quad, qreal deviceScale)
1047 {
1048     // Geometry assumes we're rendering triangles, so add the quad's
1049     // vertices as two triangles. Vertex order is top-left, bottom-left,
1050     // top-right followed by top-right, bottom-left, bottom-right.
1051     appendWindowVertex(quad[0], deviceScale);
1052     appendWindowVertex(quad[3], deviceScale);
1053     appendWindowVertex(quad[1], deviceScale);
1054 
1055     appendWindowVertex(quad[1], deviceScale);
1056     appendWindowVertex(quad[3], deviceScale);
1057     appendWindowVertex(quad[2], deviceScale);
1058 }
1059 
1060 void RenderGeometry::appendSubQuad(const WindowQuad &quad, const QRectF &subquad, qreal deviceScale)
1061 {
1062     std::array<GLVertex2D, 4> vertices;
1063     vertices[0].position = QVector2D(subquad.topLeft());
1064     vertices[1].position = QVector2D(subquad.topRight());
1065     vertices[2].position = QVector2D(subquad.bottomRight());
1066     vertices[3].position = QVector2D(subquad.bottomLeft());
1067 
1068     const auto deviceQuad = QRectF{QPointF(std::round(quad.left() * deviceScale), std::round(quad.top() * deviceScale)),
1069                                    QPointF(std::round(quad.right() * deviceScale), std::round(quad.bottom() * deviceScale))};
1070 
1071     const QPointF origin = deviceQuad.topLeft();
1072     const QSizeF size = deviceQuad.size();
1073 
1074 #pragma GCC unroll 4
1075     for (int i = 0; i < 4; ++i) {
1076         const double weight1 = (vertices[i].position.x() - origin.x()) / size.width();
1077         const double weight2 = (vertices[i].position.y() - origin.y()) / size.height();
1078         const double oneMinW1 = 1.0 - weight1;
1079         const double oneMinW2 = 1.0 - weight2;
1080 
1081         const float u = oneMinW1 * oneMinW2 * quad[0].u() + weight1 * oneMinW2 * quad[1].u()
1082             + weight1 * weight2 * quad[2].u() + oneMinW1 * weight2 * quad[3].u();
1083         const float v = oneMinW1 * oneMinW2 * quad[0].v() + weight1 * oneMinW2 * quad[1].v()
1084             + weight1 * weight2 * quad[2].v() + oneMinW1 * weight2 * quad[3].v();
1085         vertices[i].texcoord = QVector2D(u, v);
1086     }
1087 
1088     append(vertices[0]);
1089     append(vertices[3]);
1090     append(vertices[1]);
1091 
1092     append(vertices[1]);
1093     append(vertices[3]);
1094     append(vertices[2]);
1095 }
1096 
1097 void RenderGeometry::postProcessTextureCoordinates(const QMatrix4x4 &textureMatrix)
1098 {
1099     if (!textureMatrix.isIdentity()) {
1100         const QVector2D coeff(textureMatrix(0, 0), textureMatrix(1, 1));
1101         const QVector2D offset(textureMatrix(0, 3), textureMatrix(1, 3));
1102 
1103         for (auto &vertex : (*this)) {
1104             vertex.texcoord = vertex.texcoord * coeff + offset;
1105         }
1106     }
1107 }
1108 
1109 /***************************************************************
1110  Motion1D
1111 ***************************************************************/
1112 
1113 Motion1D::Motion1D(double initial, double strength, double smoothness)
1114     : Motion<double>(initial, strength, smoothness)
1115 {
1116 }
1117 
1118 Motion1D::Motion1D(const Motion1D &other)
1119     : Motion<double>(other)
1120 {
1121 }
1122 
1123 Motion1D::~Motion1D()
1124 {
1125 }
1126 
1127 /***************************************************************
1128  Motion2D
1129 ***************************************************************/
1130 
1131 Motion2D::Motion2D(QPointF initial, double strength, double smoothness)
1132     : Motion<QPointF>(initial, strength, smoothness)
1133 {
1134 }
1135 
1136 Motion2D::Motion2D(const Motion2D &other)
1137     : Motion<QPointF>(other)
1138 {
1139 }
1140 
1141 Motion2D::~Motion2D()
1142 {
1143 }
1144 
1145 /***************************************************************
1146  WindowMotionManager
1147 ***************************************************************/
1148 
1149 WindowMotionManager::WindowMotionManager(bool useGlobalAnimationModifier)
1150     : m_useGlobalAnimationModifier(useGlobalAnimationModifier)
1151 
1152 {
1153     // TODO: Allow developer to modify motion attributes
1154 } // TODO: What happens when the window moves by an external force?
1155 
1156 WindowMotionManager::~WindowMotionManager()
1157 {
1158 }
1159 
1160 void WindowMotionManager::manage(EffectWindow *w)
1161 {
1162     if (m_managedWindows.contains(w)) {
1163         return;
1164     }
1165 
1166     double strength = 0.08;
1167     double smoothness = 4.0;
1168     if (m_useGlobalAnimationModifier && effects->animationTimeFactor()) {
1169         // If the factor is == 0 then we just skip the calculation completely
1170         strength = 0.08 / effects->animationTimeFactor();
1171         smoothness = effects->animationTimeFactor() * 4.0;
1172     }
1173 
1174     WindowMotion &motion = m_managedWindows[w];
1175     motion.translation.setStrength(strength);
1176     motion.translation.setSmoothness(smoothness);
1177     motion.scale.setStrength(strength * 1.33);
1178     motion.scale.setSmoothness(smoothness / 2.0);
1179 
1180     motion.translation.setValue(w->pos());
1181     motion.scale.setValue(QPointF(1.0, 1.0));
1182 }
1183 
1184 void WindowMotionManager::unmanage(EffectWindow *w)
1185 {
1186     m_movingWindowsSet.remove(w);
1187     m_managedWindows.remove(w);
1188 }
1189 
1190 void WindowMotionManager::unmanageAll()
1191 {
1192     m_managedWindows.clear();
1193     m_movingWindowsSet.clear();
1194 }
1195 
1196 void WindowMotionManager::calculate(int time)
1197 {
1198     if (!effects->animationTimeFactor()) {
1199         // Just skip it completely if the user wants no animation
1200         m_movingWindowsSet.clear();
1201         QHash<EffectWindow *, WindowMotion>::iterator it = m_managedWindows.begin();
1202         for (; it != m_managedWindows.end(); ++it) {
1203             WindowMotion *motion = &it.value();
1204             motion->translation.finish();
1205             motion->scale.finish();
1206         }
1207     }
1208 
1209     QHash<EffectWindow *, WindowMotion>::iterator it = m_managedWindows.begin();
1210     for (; it != m_managedWindows.end(); ++it) {
1211         WindowMotion *motion = &it.value();
1212         int stopped = 0;
1213 
1214         // TODO: What happens when distance() == 0 but we are still moving fast?
1215         // TODO: Motion needs to be calculated from the window's center
1216 
1217         Motion2D *trans = &motion->translation;
1218         if (trans->distance().isNull()) {
1219             ++stopped;
1220         } else {
1221             // Still moving
1222             trans->calculate(time);
1223             const short fx = trans->target().x() <= trans->startValue().x() ? -1 : 1;
1224             const short fy = trans->target().y() <= trans->startValue().y() ? -1 : 1;
1225             if (trans->distance().x() * fx / 0.5 < 1.0 && trans->velocity().x() * fx / 0.2 < 1.0
1226                 && trans->distance().y() * fy / 0.5 < 1.0 && trans->velocity().y() * fy / 0.2 < 1.0) {
1227                 // Hide tiny oscillations
1228                 motion->translation.finish();
1229                 ++stopped;
1230             }
1231         }
1232 
1233         Motion2D *scale = &motion->scale;
1234         if (scale->distance().isNull()) {
1235             ++stopped;
1236         } else {
1237             // Still scaling
1238             scale->calculate(time);
1239             const short fx = scale->target().x() < 1.0 ? -1 : 1;
1240             const short fy = scale->target().y() < 1.0 ? -1 : 1;
1241             if (scale->distance().x() * fx / 0.001 < 1.0 && scale->velocity().x() * fx / 0.05 < 1.0
1242                 && scale->distance().y() * fy / 0.001 < 1.0 && scale->velocity().y() * fy / 0.05 < 1.0) {
1243                 // Hide tiny oscillations
1244                 motion->scale.finish();
1245                 ++stopped;
1246             }
1247         }
1248 
1249         // We just finished this window's motion
1250         if (stopped == 2) {
1251             m_movingWindowsSet.remove(it.key());
1252         }
1253     }
1254 }
1255 
1256 void WindowMotionManager::reset()
1257 {
1258     QHash<EffectWindow *, WindowMotion>::iterator it = m_managedWindows.begin();
1259     for (; it != m_managedWindows.end(); ++it) {
1260         WindowMotion *motion = &it.value();
1261         EffectWindow *window = it.key();
1262         motion->translation.setTarget(window->pos());
1263         motion->translation.finish();
1264         motion->scale.setTarget(QPointF(1.0, 1.0));
1265         motion->scale.finish();
1266     }
1267 }
1268 
1269 void WindowMotionManager::reset(EffectWindow *w)
1270 {
1271     QHash<EffectWindow *, WindowMotion>::iterator it = m_managedWindows.find(w);
1272     if (it == m_managedWindows.end()) {
1273         return;
1274     }
1275 
1276     WindowMotion *motion = &it.value();
1277     motion->translation.setTarget(w->pos());
1278     motion->translation.finish();
1279     motion->scale.setTarget(QPointF(1.0, 1.0));
1280     motion->scale.finish();
1281 }
1282 
1283 void WindowMotionManager::apply(EffectWindow *w, WindowPaintData &data)
1284 {
1285     QHash<EffectWindow *, WindowMotion>::iterator it = m_managedWindows.find(w);
1286     if (it == m_managedWindows.end()) {
1287         return;
1288     }
1289 
1290     // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid)
1291     WindowMotion *motion = &it.value();
1292     data += (motion->translation.value() - QPointF(w->x(), w->y()));
1293     data *= QVector2D(motion->scale.value());
1294 }
1295 
1296 void WindowMotionManager::moveWindow(EffectWindow *w, QPoint target, double scale, double yScale)
1297 {
1298     QHash<EffectWindow *, WindowMotion>::iterator it = m_managedWindows.find(w);
1299     Q_ASSERT(it != m_managedWindows.end()); // Notify the effect author that they did something wrong
1300 
1301     WindowMotion *motion = &it.value();
1302 
1303     if (yScale == 0.0) {
1304         yScale = scale;
1305     }
1306     QPointF scalePoint(scale, yScale);
1307 
1308     if (motion->translation.value() == target && motion->scale.value() == scalePoint) {
1309         return; // Window already at that position
1310     }
1311 
1312     motion->translation.setTarget(target);
1313     motion->scale.setTarget(scalePoint);
1314 
1315     m_movingWindowsSet << w;
1316 }
1317 
1318 QRectF WindowMotionManager::transformedGeometry(EffectWindow *w) const
1319 {
1320     QHash<EffectWindow *, WindowMotion>::const_iterator it = m_managedWindows.constFind(w);
1321     if (it == m_managedWindows.end()) {
1322         return w->frameGeometry();
1323     }
1324 
1325     const WindowMotion *motion = &it.value();
1326     QRectF geometry(w->frameGeometry());
1327 
1328     // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid)
1329     geometry.moveTo(motion->translation.value());
1330     geometry.setWidth(geometry.width() * motion->scale.value().x());
1331     geometry.setHeight(geometry.height() * motion->scale.value().y());
1332 
1333     return geometry;
1334 }
1335 
1336 void WindowMotionManager::setTransformedGeometry(EffectWindow *w, const QRectF &geometry)
1337 {
1338     QHash<EffectWindow *, WindowMotion>::iterator it = m_managedWindows.find(w);
1339     if (it == m_managedWindows.end()) {
1340         return;
1341     }
1342     WindowMotion *motion = &it.value();
1343     motion->translation.setValue(geometry.topLeft());
1344     motion->scale.setValue(QPointF(geometry.width() / qreal(w->width()), geometry.height() / qreal(w->height())));
1345 }
1346 
1347 QRectF WindowMotionManager::targetGeometry(EffectWindow *w) const
1348 {
1349     QHash<EffectWindow *, WindowMotion>::const_iterator it = m_managedWindows.constFind(w);
1350     if (it == m_managedWindows.end()) {
1351         return w->frameGeometry();
1352     }
1353 
1354     const WindowMotion *motion = &it.value();
1355     QRectF geometry(w->frameGeometry());
1356 
1357     // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid)
1358     geometry.moveTo(motion->translation.target());
1359     geometry.setWidth(geometry.width() * motion->scale.target().x());
1360     geometry.setHeight(geometry.height() * motion->scale.target().y());
1361 
1362     return geometry;
1363 }
1364 
1365 EffectWindow *WindowMotionManager::windowAtPoint(QPoint point, bool useStackingOrder) const
1366 {
1367     // TODO: Stacking order uses EffectsHandler::stackingOrder() then filters by m_managedWindows
1368     QHash<EffectWindow *, WindowMotion>::ConstIterator it = m_managedWindows.constBegin();
1369     while (it != m_managedWindows.constEnd()) {
1370         if (transformedGeometry(it.key()).contains(point)) {
1371             return it.key();
1372         }
1373         ++it;
1374     }
1375 
1376     return nullptr;
1377 }
1378 
1379 /***************************************************************
1380  EffectFramePrivate
1381 ***************************************************************/
1382 class EffectFramePrivate
1383 {
1384 public:
1385     EffectFramePrivate();
1386     ~EffectFramePrivate();
1387 
1388     bool crossFading;
1389     qreal crossFadeProgress;
1390     QMatrix4x4 screenProjectionMatrix;
1391 };
1392 
1393 EffectFramePrivate::EffectFramePrivate()
1394     : crossFading(false)
1395     , crossFadeProgress(1.0)
1396 {
1397 }
1398 
1399 EffectFramePrivate::~EffectFramePrivate()
1400 {
1401 }
1402 
1403 /***************************************************************
1404  EffectFrame
1405 ***************************************************************/
1406 EffectFrame::EffectFrame()
1407     : d(std::make_unique<EffectFramePrivate>())
1408 {
1409 }
1410 
1411 EffectFrame::~EffectFrame() = default;
1412 
1413 qreal EffectFrame::crossFadeProgress() const
1414 {
1415     return d->crossFadeProgress;
1416 }
1417 
1418 void EffectFrame::setCrossFadeProgress(qreal progress)
1419 {
1420     d->crossFadeProgress = progress;
1421 }
1422 
1423 bool EffectFrame::isCrossFade() const
1424 {
1425     return d->crossFading;
1426 }
1427 
1428 void EffectFrame::enableCrossFade(bool enable)
1429 {
1430     d->crossFading = enable;
1431 }
1432 
1433 /***************************************************************
1434  TimeLine
1435 ***************************************************************/
1436 
1437 class Q_DECL_HIDDEN TimeLine::Data : public QSharedData
1438 {
1439 public:
1440     std::chrono::milliseconds duration;
1441     Direction direction;
1442     QEasingCurve easingCurve;
1443 
1444     std::chrono::milliseconds elapsed = std::chrono::milliseconds::zero();
1445     std::optional<std::chrono::milliseconds> lastTimestamp = std::nullopt;
1446     bool done = false;
1447     RedirectMode sourceRedirectMode = RedirectMode::Relaxed;
1448     RedirectMode targetRedirectMode = RedirectMode::Strict;
1449 };
1450 
1451 TimeLine::TimeLine(std::chrono::milliseconds duration, Direction direction)
1452     : d(new Data)
1453 {
1454     Q_ASSERT(duration > std::chrono::milliseconds::zero());
1455     d->duration = duration;
1456     d->direction = direction;
1457 }
1458 
1459 TimeLine::TimeLine(const TimeLine &other)
1460     : d(other.d)
1461 {
1462 }
1463 
1464 TimeLine::~TimeLine() = default;
1465 
1466 qreal TimeLine::progress() const
1467 {
1468     return static_cast<qreal>(d->elapsed.count()) / d->duration.count();
1469 }
1470 
1471 qreal TimeLine::value() const
1472 {
1473     const qreal t = progress();
1474     return d->easingCurve.valueForProgress(
1475         d->direction == Backward ? 1.0 - t : t);
1476 }
1477 
1478 void TimeLine::advance(std::chrono::milliseconds timestamp)
1479 {
1480     if (d->done) {
1481         return;
1482     }
1483 
1484     std::chrono::milliseconds delta = std::chrono::milliseconds::zero();
1485     if (d->lastTimestamp.has_value()) {
1486         delta = timestamp - d->lastTimestamp.value();
1487     }
1488 
1489     Q_ASSERT(delta >= std::chrono::milliseconds::zero());
1490     d->lastTimestamp = timestamp;
1491 
1492     d->elapsed += delta;
1493     if (d->elapsed >= d->duration) {
1494         d->elapsed = d->duration;
1495         d->done = true;
1496         d->lastTimestamp = std::nullopt;
1497     }
1498 }
1499 
1500 std::chrono::milliseconds TimeLine::elapsed() const
1501 {
1502     return d->elapsed;
1503 }
1504 
1505 void TimeLine::setElapsed(std::chrono::milliseconds elapsed)
1506 {
1507     Q_ASSERT(elapsed >= std::chrono::milliseconds::zero());
1508     if (elapsed == d->elapsed) {
1509         return;
1510     }
1511 
1512     reset();
1513 
1514     d->elapsed = elapsed;
1515 
1516     if (d->elapsed >= d->duration) {
1517         d->elapsed = d->duration;
1518         d->done = true;
1519         d->lastTimestamp = std::nullopt;
1520     }
1521 }
1522 
1523 std::chrono::milliseconds TimeLine::duration() const
1524 {
1525     return d->duration;
1526 }
1527 
1528 void TimeLine::setDuration(std::chrono::milliseconds duration)
1529 {
1530     Q_ASSERT(duration > std::chrono::milliseconds::zero());
1531     if (duration == d->duration) {
1532         return;
1533     }
1534     d->elapsed = std::chrono::milliseconds(qRound(progress() * duration.count()));
1535     d->duration = duration;
1536     if (d->elapsed == d->duration) {
1537         d->done = true;
1538         d->lastTimestamp = std::nullopt;
1539     }
1540 }
1541 
1542 TimeLine::Direction TimeLine::direction() const
1543 {
1544     return d->direction;
1545 }
1546 
1547 void TimeLine::setDirection(TimeLine::Direction direction)
1548 {
1549     if (d->direction == direction) {
1550         return;
1551     }
1552 
1553     d->direction = direction;
1554 
1555     if (d->elapsed > std::chrono::milliseconds::zero()
1556         || d->sourceRedirectMode == RedirectMode::Strict) {
1557         d->elapsed = d->duration - d->elapsed;
1558     }
1559 
1560     if (d->done && d->targetRedirectMode == RedirectMode::Relaxed) {
1561         d->done = false;
1562     }
1563 
1564     if (d->elapsed >= d->duration) {
1565         d->done = true;
1566         d->lastTimestamp = std::nullopt;
1567     }
1568 }
1569 
1570 void TimeLine::toggleDirection()
1571 {
1572     setDirection(d->direction == Forward ? Backward : Forward);
1573 }
1574 
1575 QEasingCurve TimeLine::easingCurve() const
1576 {
1577     return d->easingCurve;
1578 }
1579 
1580 void TimeLine::setEasingCurve(const QEasingCurve &easingCurve)
1581 {
1582     d->easingCurve = easingCurve;
1583 }
1584 
1585 void TimeLine::setEasingCurve(QEasingCurve::Type type)
1586 {
1587     d->easingCurve.setType(type);
1588 }
1589 
1590 bool TimeLine::running() const
1591 {
1592     return d->elapsed != std::chrono::milliseconds::zero()
1593         && d->elapsed != d->duration;
1594 }
1595 
1596 bool TimeLine::done() const
1597 {
1598     return d->done;
1599 }
1600 
1601 void TimeLine::reset()
1602 {
1603     d->lastTimestamp = std::nullopt;
1604     d->elapsed = std::chrono::milliseconds::zero();
1605     d->done = false;
1606 }
1607 
1608 TimeLine::RedirectMode TimeLine::sourceRedirectMode() const
1609 {
1610     return d->sourceRedirectMode;
1611 }
1612 
1613 void TimeLine::setSourceRedirectMode(RedirectMode mode)
1614 {
1615     d->sourceRedirectMode = mode;
1616 }
1617 
1618 TimeLine::RedirectMode TimeLine::targetRedirectMode() const
1619 {
1620     return d->targetRedirectMode;
1621 }
1622 
1623 void TimeLine::setTargetRedirectMode(RedirectMode mode)
1624 {
1625     d->targetRedirectMode = mode;
1626 }
1627 
1628 TimeLine &TimeLine::operator=(const TimeLine &other)
1629 {
1630     d = other.d;
1631     return *this;
1632 }
1633 
1634 } // namespace
1635 
1636 #include "moc_kwineffects.cpp"
1637 #include "moc_kwinglobals.cpp"