1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2007 Lubos Lunak <l.lunak@kde.org>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "fallapart.h"
11 // KConfigSkeleton
12 #include "fallapartconfig.h"
13 
14 #include <cmath>
15 
16 namespace KWin
17 {
18 
supported()19 bool FallApartEffect::supported()
20 {
21     return DeformEffect::supported() && effects->animationsSupported();
22 }
23 
FallApartEffect()24 FallApartEffect::FallApartEffect()
25 {
26     initConfig<FallApartConfig>();
27     reconfigure(ReconfigureAll);
28     connect(effects, &EffectsHandler::windowClosed, this, &FallApartEffect::slotWindowClosed);
29     connect(effects, &EffectsHandler::windowDeleted, this, &FallApartEffect::slotWindowDeleted);
30     connect(effects, &EffectsHandler::windowDataChanged, this, &FallApartEffect::slotWindowDataChanged);
31 }
32 
reconfigure(ReconfigureFlags)33 void FallApartEffect::reconfigure(ReconfigureFlags)
34 {
35     FallApartConfig::self()->read();
36     blockSize = FallApartConfig::blockSize();
37 }
38 
prePaintScreen(ScreenPrePaintData & data,std::chrono::milliseconds presentTime)39 void FallApartEffect::prePaintScreen(ScreenPrePaintData& data, std::chrono::milliseconds presentTime)
40 {
41     if (!windows.isEmpty())
42         data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
43     effects->prePaintScreen(data, presentTime);
44 }
45 
prePaintWindow(EffectWindow * w,WindowPrePaintData & data,std::chrono::milliseconds presentTime)46 void FallApartEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, std::chrono::milliseconds presentTime)
47 {
48     auto animationIt = windows.find(w);
49     if (animationIt != windows.end() && isRealWindow(w)) {
50         if (animationIt->progress < 1) {
51             int time = 0;
52             if (animationIt->lastPresentTime.count()) {
53                 time = (presentTime - animationIt->lastPresentTime).count();
54             }
55             animationIt->lastPresentTime = presentTime;
56 
57             animationIt->progress += time / animationTime(1000.);
58             data.setTransformed();
59             w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DELETE);
60         } else {
61             unredirect(w);
62             windows.remove(w);
63             w->unrefWindow();
64         }
65     }
66     effects->prePaintWindow(w, data, presentTime);
67 }
68 
deform(EffectWindow * w,int mask,WindowPaintData & data,WindowQuadList & quads)69 void FallApartEffect::deform(EffectWindow *w, int mask, WindowPaintData &data, WindowQuadList &quads)
70 {
71     Q_UNUSED(w)
72     Q_UNUSED(mask)
73     auto animationIt = windows.constFind(w);
74     if (animationIt != windows.constEnd() && isRealWindow(w)) {
75         const qreal t = animationIt->progress;
76         // Request the window to be divided into cells
77         quads = quads.makeGrid(blockSize);
78         int cnt = 0;
79         for (WindowQuad &quad : quads) {
80             // make fragments move in various directions, based on where
81             // they are (left pieces generally move to the left, etc.)
82             QPointF p1(quad[ 0 ].x(), quad[ 0 ].y());
83             double xdiff = 0;
84             if (p1.x() < w->width() / 2)
85                 xdiff = -(w->width() / 2 - p1.x()) / w->width() * 100;
86             if (p1.x() > w->width() / 2)
87                 xdiff = (p1.x() - w->width() / 2) / w->width() * 100;
88             double ydiff = 0;
89             if (p1.y() < w->height() / 2)
90                 ydiff = -(w->height() / 2 - p1.y()) / w->height() * 100;
91             if (p1.y() > w->height() / 2)
92                 ydiff = (p1.y() - w->height() / 2) / w->height() * 100;
93             double modif = t * t * 64;
94             srandom(cnt);   // change direction randomly but consistently
95             xdiff += (rand() % 21 - 10);
96             ydiff += (rand() % 21 - 10);
97             for (int j = 0;
98                     j < 4;
99                     ++j) {
100                 quad[ j ].move(quad[ j ].x() + xdiff * modif, quad[ j ].y() + ydiff * modif);
101             }
102             // also make the fragments rotate around their center
103             QPointF center((quad[ 0 ].x() + quad[ 1 ].x() + quad[ 2 ].x() + quad[ 3 ].x()) / 4,
104                            (quad[ 0 ].y() + quad[ 1 ].y() + quad[ 2 ].y() + quad[ 3 ].y()) / 4);
105             double adiff = (rand() % 720 - 360) / 360. * 2 * M_PI;   // spin randomly
106             for (int j = 0;
107                     j < 4;
108                     ++j) {
109                 double x = quad[ j ].x() - center.x();
110                 double y = quad[ j ].y() - center.y();
111                 double angle = atan2(y, x);
112                 angle += animationIt->progress * adiff;
113                 double dist = sqrt(x * x + y * y);
114                 x = dist * cos(angle);
115                 y = dist * sin(angle);
116                 quad[ j ].move(center.x() + x, center.y() + y);
117             }
118             ++cnt;
119         }
120         data.multiplyOpacity(interpolate(1.0, 0.0, t));
121     }
122 }
123 
postPaintScreen()124 void FallApartEffect::postPaintScreen()
125 {
126     if (!windows.isEmpty())
127         effects->addRepaintFull();
128     effects->postPaintScreen();
129 }
130 
isRealWindow(EffectWindow * w)131 bool FallApartEffect::isRealWindow(EffectWindow* w)
132 {
133     // TODO: isSpecialWindow is rather generic, maybe tell windowtypes separately?
134     /*
135     qCDebug(KWINEFFECTS) << "--" << w->caption() << "--------------------------------";
136     qCDebug(KWINEFFECTS) << "Tooltip:" << w->isTooltip();
137     qCDebug(KWINEFFECTS) << "Toolbar:" << w->isToolbar();
138     qCDebug(KWINEFFECTS) << "Desktop:" << w->isDesktop();
139     qCDebug(KWINEFFECTS) << "Special:" << w->isSpecialWindow();
140     qCDebug(KWINEFFECTS) << "TopMenu:" << w->isTopMenu();
141     qCDebug(KWINEFFECTS) << "Notific:" << w->isNotification();
142     qCDebug(KWINEFFECTS) << "Splash:" << w->isSplash();
143     qCDebug(KWINEFFECTS) << "Normal:" << w->isNormalWindow();
144     */
145     if (w->isPopupWindow()) {
146         return false;
147     }
148     if (w->isX11Client() && !w->isManaged()) {
149         return false;
150     }
151     if (!w->isNormalWindow())
152         return false;
153     return true;
154 }
155 
slotWindowClosed(EffectWindow * c)156 void FallApartEffect::slotWindowClosed(EffectWindow* c)
157 {
158     if (!isRealWindow(c))
159         return;
160     if (!c->isVisible())
161         return;
162     const void* e = c->data(WindowClosedGrabRole).value<void*>();
163     if (e && e != this)
164         return;
165     c->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast<void*>(this)));
166     windows[ c ].progress = 0;
167     c->refWindow();
168     redirect(c);
169 }
170 
slotWindowDeleted(EffectWindow * c)171 void FallApartEffect::slotWindowDeleted(EffectWindow* c)
172 {
173     windows.remove(c);
174 }
175 
slotWindowDataChanged(EffectWindow * w,int role)176 void FallApartEffect::slotWindowDataChanged(EffectWindow* w, int role)
177 {
178     if (role != WindowClosedGrabRole) {
179         return;
180     }
181 
182     if (w->data(role).value<void*>() == this) {
183         return;
184     }
185 
186     auto it = windows.find(w);
187     if (it == windows.end()) {
188         return;
189     }
190 
191     unredirect(it.key());
192     it.key()->unrefWindow();
193     windows.erase(it);
194 }
195 
isActive() const196 bool FallApartEffect::isActive() const
197 {
198     return !windows.isEmpty();
199 }
200 
201 } // namespace
202