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