File indexing completed on 2025-03-23 11:13:55
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"