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

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2007 Lubos Lunak <l.lunak@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "fallapart.h"
0011 #include "effect/effecthandler.h"
0012 // KConfigSkeleton
0013 #include "fallapartconfig.h"
0014 
0015 #include <QEasingCurve>
0016 
0017 #include <cmath>
0018 
0019 Q_LOGGING_CATEGORY(KWIN_FALLAPART, "kwin_effect_fallapart", QtWarningMsg)
0020 
0021 static const QSet<QString> s_blacklist{
0022     // Spectacle needs to be blacklisted in order to stay out of its own screenshots.
0023     QStringLiteral("spectacle spectacle"), // x11
0024     QStringLiteral("spectacle org.kde.spectacle"), // wayland
0025 };
0026 
0027 namespace KWin
0028 {
0029 
0030 bool FallApartEffect::supported()
0031 {
0032     return OffscreenEffect::supported() && effects->animationsSupported();
0033 }
0034 
0035 FallApartEffect::FallApartEffect()
0036 {
0037     FallApartConfig::instance(effects->config());
0038     reconfigure(ReconfigureAll);
0039     connect(effects, &EffectsHandler::windowClosed, this, &FallApartEffect::slotWindowClosed);
0040     connect(effects, &EffectsHandler::windowDataChanged, this, &FallApartEffect::slotWindowDataChanged);
0041 }
0042 
0043 void FallApartEffect::reconfigure(ReconfigureFlags)
0044 {
0045     FallApartConfig::self()->read();
0046     blockSize = FallApartConfig::blockSize();
0047 }
0048 
0049 void FallApartEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
0050 {
0051     if (!windows.isEmpty()) {
0052         data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
0053     }
0054     effects->prePaintScreen(data, presentTime);
0055 }
0056 
0057 void FallApartEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
0058 {
0059     auto animationIt = windows.find(w);
0060     if (animationIt != windows.end() && isRealWindow(w)) {
0061         int time = 0;
0062         if (animationIt->lastPresentTime.count()) {
0063             time = (presentTime - animationIt->lastPresentTime).count();
0064         }
0065         animationIt->lastPresentTime = presentTime;
0066 
0067         animationIt->progress += time / animationTime(1000.);
0068         data.setTransformed();
0069     }
0070     effects->prePaintWindow(w, data, presentTime);
0071 }
0072 
0073 void FallApartEffect::apply(EffectWindow *w, int mask, WindowPaintData &data, WindowQuadList &quads)
0074 {
0075     auto animationIt = windows.constFind(w);
0076     if (animationIt != windows.constEnd() && isRealWindow(w)) {
0077         QEasingCurve easing(QEasingCurve::InCubic);
0078         const qreal t = easing.valueForProgress(animationIt->progress);
0079         // Request the window to be divided into cells
0080         quads = quads.makeGrid(blockSize);
0081         int cnt = 0;
0082         for (WindowQuad &quad : quads) {
0083             // make fragments move in various directions, based on where
0084             // they are (left pieces generally move to the left, etc.)
0085             QPointF p1(quad[0].x(), quad[0].y());
0086             double xdiff = 0;
0087             if (p1.x() < w->width() / 2) {
0088                 xdiff = -(w->width() / 2 - p1.x()) / w->width() * 100;
0089             }
0090             if (p1.x() > w->width() / 2) {
0091                 xdiff = (p1.x() - w->width() / 2) / w->width() * 100;
0092             }
0093             double ydiff = 0;
0094             if (p1.y() < w->height() / 2) {
0095                 ydiff = -(w->height() / 2 - p1.y()) / w->height() * 100;
0096             }
0097             if (p1.y() > w->height() / 2) {
0098                 ydiff = (p1.y() - w->height() / 2) / w->height() * 100;
0099             }
0100             double modif = t * 64;
0101             srandom(cnt); // change direction randomly but consistently
0102             xdiff += (rand() % 21 - 10);
0103             ydiff += (rand() % 21 - 10);
0104             for (int j = 0;
0105                  j < 4;
0106                  ++j) {
0107                 quad[j].move(quad[j].x() + xdiff * modif, quad[j].y() + ydiff * modif);
0108             }
0109             // also make the fragments rotate around their center
0110             QPointF center((quad[0].x() + quad[1].x() + quad[2].x() + quad[3].x()) / 4,
0111                            (quad[0].y() + quad[1].y() + quad[2].y() + quad[3].y()) / 4);
0112             double adiff = (rand() % 720 - 360) / 360. * 2 * M_PI; // spin randomly
0113             for (int j = 0;
0114                  j < 4;
0115                  ++j) {
0116                 double x = quad[j].x() - center.x();
0117                 double y = quad[j].y() - center.y();
0118                 double angle = atan2(y, x);
0119                 angle += animationIt->progress * adiff;
0120                 double dist = sqrt(x * x + y * y);
0121                 x = dist * cos(angle);
0122                 y = dist * sin(angle);
0123                 quad[j].move(center.x() + x, center.y() + y);
0124             }
0125             ++cnt;
0126         }
0127         data.multiplyOpacity(interpolate(1.0, 0.0, t));
0128     }
0129 }
0130 
0131 void FallApartEffect::postPaintScreen()
0132 {
0133     for (auto it = windows.begin(); it != windows.end();) {
0134         if (it->progress < 1) {
0135             ++it;
0136         } else {
0137             unredirect(it.key());
0138             it = windows.erase(it);
0139         }
0140     }
0141 
0142     effects->addRepaintFull();
0143     effects->postPaintScreen();
0144 }
0145 
0146 bool FallApartEffect::isRealWindow(EffectWindow *w)
0147 {
0148     // TODO: isSpecialWindow is rather generic, maybe tell windowtypes separately?
0149     /*
0150     qCDebug(KWIN_FALLAPART) << "--" << w->caption() << "--------------------------------";
0151     qCDebug(KWIN_FALLAPART) << "Tooltip:" << w->isTooltip();
0152     qCDebug(KWIN_FALLAPART) << "Toolbar:" << w->isToolbar();
0153     qCDebug(KWIN_FALLAPART) << "Desktop:" << w->isDesktop();
0154     qCDebug(KWIN_FALLAPART) << "Special:" << w->isSpecialWindow();
0155     qCDebug(KWIN_FALLAPART) << "TopMenu:" << w->isTopMenu();
0156     qCDebug(KWIN_FALLAPART) << "Notific:" << w->isNotification();
0157     qCDebug(KWIN_FALLAPART) << "Splash:" << w->isSplash();
0158     qCDebug(KWIN_FALLAPART) << "Normal:" << w->isNormalWindow();
0159     */
0160     if (w->isPopupWindow()) {
0161         return false;
0162     }
0163     if (w->isX11Client() && !w->isManaged()) {
0164         return false;
0165     }
0166     if (!w->isNormalWindow()) {
0167         return false;
0168     }
0169     return true;
0170 }
0171 
0172 void FallApartEffect::slotWindowClosed(EffectWindow *c)
0173 {
0174     if (effects->activeFullScreenEffect()) {
0175         return;
0176     }
0177     if (!isRealWindow(c)) {
0178         return;
0179     }
0180     if (!c->isVisible()) {
0181         return;
0182     }
0183     if (s_blacklist.contains(c->windowClass())) {
0184         return;
0185     }
0186     const void *e = c->data(WindowClosedGrabRole).value<void *>();
0187     if (e && e != this) {
0188         return;
0189     }
0190     c->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast<void *>(this)));
0191 
0192     FallApartAnimation &animation = windows[c];
0193     animation.progress = 0;
0194     animation.deletedRef = EffectWindowDeletedRef(c);
0195 
0196     redirect(c);
0197 }
0198 
0199 void FallApartEffect::slotWindowDataChanged(EffectWindow *w, int role)
0200 {
0201     if (role != WindowClosedGrabRole) {
0202         return;
0203     }
0204 
0205     if (w->data(role).value<void *>() == this) {
0206         return;
0207     }
0208 
0209     auto it = windows.find(w);
0210     if (it != windows.end()) {
0211         unredirect(it.key());
0212         windows.erase(it);
0213     }
0214 }
0215 
0216 bool FallApartEffect::isActive() const
0217 {
0218     return !windows.isEmpty();
0219 }
0220 
0221 } // namespace
0222 
0223 #include "moc_fallapart.cpp"