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