1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2009 Michael Zanetti <michael_zanetti@gmx.net>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "slideback.h"
11 
12 namespace KWin
13 {
14 
SlideBackEffect()15 SlideBackEffect::SlideBackEffect()
16 {
17     m_tabboxActive = 0;
18     m_justMapped = m_upmostWindow = nullptr;
19     connect(effects, &EffectsHandler::windowAdded, this, &SlideBackEffect::slotWindowAdded);
20     connect(effects, &EffectsHandler::windowDeleted, this, &SlideBackEffect::slotWindowDeleted);
21     connect(effects, &EffectsHandler::windowUnminimized, this, &SlideBackEffect::slotWindowUnminimized);
22     connect(effects, &EffectsHandler::tabBoxAdded, this, &SlideBackEffect::slotTabBoxAdded);
23     connect(effects, &EffectsHandler::stackingOrderChanged, this, &SlideBackEffect::slotStackingOrderChanged);
24     connect(effects, &EffectsHandler::tabBoxClosed, this, &SlideBackEffect::slotTabBoxClosed);
25 }
26 
slotStackingOrderChanged()27 void SlideBackEffect::slotStackingOrderChanged()
28 {
29     if (effects->activeFullScreenEffect() || m_tabboxActive) {
30         oldStackingOrder = effects->stackingOrder();
31         usableOldStackingOrder = usableWindows(oldStackingOrder);
32         return;
33     }
34 
35     EffectWindowList newStackingOrder = effects->stackingOrder(),
36                      usableNewStackingOrder = usableWindows(newStackingOrder);
37     if (usableNewStackingOrder == usableOldStackingOrder || usableNewStackingOrder.isEmpty()) {
38         oldStackingOrder = newStackingOrder;
39         usableOldStackingOrder = usableNewStackingOrder;
40         return;
41     }
42 
43     m_upmostWindow = usableNewStackingOrder.last();
44 
45     if (m_upmostWindow == m_justMapped ) // a window was added, got on top, stacking changed. Nothing impressive
46         m_justMapped = nullptr;
47     else if (!usableOldStackingOrder.isEmpty() && m_upmostWindow != usableOldStackingOrder.last())
48         windowRaised(m_upmostWindow);
49 
50     oldStackingOrder = newStackingOrder;
51     usableOldStackingOrder = usableNewStackingOrder;
52 
53 }
54 
windowRaised(EffectWindow * w)55 void SlideBackEffect::windowRaised(EffectWindow *w)
56 {
57     // Determine all windows on top of the activated one
58     bool currentFound = false;
59     Q_FOREACH (EffectWindow * tmp, oldStackingOrder) {
60         if (!currentFound) {
61             if (tmp == w) {
62                 currentFound = true;
63             }
64         } else {
65             if (isWindowUsable(tmp) && tmp->isOnCurrentDesktop() && w->isOnCurrentDesktop()) {
66                 // Do we have to move it?
67                 if (intersects(w, tmp->frameGeometry())) {
68                     QRect slideRect;
69                     slideRect = getSlideDestination(getModalGroupGeometry(w), tmp->frameGeometry());
70                     effects->setElevatedWindow(tmp, true);
71                     elevatedList.append(tmp);
72                     motionManager.manage(tmp);
73                     motionManager.moveWindow(tmp, slideRect);
74                     destinationList.insert(tmp, slideRect);
75                     coveringWindows.append(tmp);
76                 } else {
77                     //Does it intersect with a moved (elevated) window and do we have to elevate it too?
78                     Q_FOREACH (EffectWindow * elevatedWindow, elevatedList) {
79                         if (tmp->frameGeometry().intersects(elevatedWindow->frameGeometry())) {
80                             effects->setElevatedWindow(tmp, true);
81                             elevatedList.append(tmp);
82                             break;
83                         }
84                     }
85 
86                 }
87             }
88             if (tmp->isDock() || tmp->keepAbove()) {
89                 effects->setElevatedWindow(tmp, true);
90                 elevatedList.append(tmp);
91             }
92         }
93     }
94     // If a window is minimized it could happen that the panels stay elevated without any windows sliding.
95     // clear all elevation settings
96     if (!motionManager.managingWindows()) {
97         Q_FOREACH (EffectWindow * tmp, elevatedList) {
98             effects->setElevatedWindow(tmp, false);
99         }
100     }
101 }
102 
getSlideDestination(const QRect & windowUnderGeometry,const QRect & windowOverGeometry)103 QRect SlideBackEffect::getSlideDestination(const QRect &windowUnderGeometry, const QRect &windowOverGeometry)
104 {
105     // Determine the shortest way:
106     int leftSlide = windowUnderGeometry.left() - windowOverGeometry.right() - 20;
107     int rightSlide = windowUnderGeometry.right() - windowOverGeometry.left() + 20;
108     int upSlide = windowUnderGeometry.top() -  windowOverGeometry.bottom() - 20;
109     int downSlide = windowUnderGeometry.bottom() - windowOverGeometry.top() + 20;
110 
111     int horizSlide = leftSlide;
112     if (qAbs(horizSlide) > qAbs(rightSlide)) {
113         horizSlide = rightSlide;
114     }
115     int vertSlide = upSlide;
116     if (qAbs(vertSlide) > qAbs(downSlide)) {
117         vertSlide = downSlide;
118     }
119 
120     QRect slideRect = windowOverGeometry;
121     if (qAbs(horizSlide) < qAbs(vertSlide)) {
122         slideRect.moveLeft(slideRect.x() + horizSlide);
123     } else {
124         slideRect.moveTop(slideRect.y() + vertSlide);
125     }
126     return slideRect;
127 }
128 
prePaintScreen(ScreenPrePaintData & data,std::chrono::milliseconds presentTime)129 void SlideBackEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
130 {
131     int time = 0;
132     if (m_lastPresentTime.count()) {
133         time = (presentTime - m_lastPresentTime).count();
134     }
135     m_lastPresentTime = presentTime;
136 
137     if (motionManager.managingWindows()) {
138         motionManager.calculate(time);
139         data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
140     }
141 
142     const EffectWindowList windows = effects->stackingOrder();
143     for (auto *w : windows) {
144         w->setData(WindowForceBlurRole, QVariant(true));
145     }
146 
147     effects->prePaintScreen(data, presentTime);
148 }
149 
postPaintScreen()150 void SlideBackEffect::postPaintScreen()
151 {
152     if (motionManager.areWindowsMoving()) {
153         effects->addRepaintFull();
154     }
155 
156     for (auto &w : effects->stackingOrder()) {
157         w->setData(WindowForceBlurRole, QVariant());
158     }
159 
160     effects->postPaintScreen();
161 }
162 
prePaintWindow(EffectWindow * w,WindowPrePaintData & data,std::chrono::milliseconds presentTime)163 void SlideBackEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
164 {
165     if (motionManager.isManaging(w)) {
166         data.setTransformed();
167     }
168 
169     effects->prePaintWindow(w, data, presentTime);
170 }
171 
paintWindow(EffectWindow * w,int mask,QRegion region,WindowPaintData & data)172 void SlideBackEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
173 {
174     if (motionManager.isManaging(w)) {
175         motionManager.apply(w, data);
176     }
177     Q_FOREACH (const QRegion &r, clippedRegions) {
178         region = region.intersected(r);
179     }
180     effects->paintWindow(w, mask, region, data);
181     clippedRegions.clear();
182 }
183 
postPaintWindow(EffectWindow * w)184 void SlideBackEffect::postPaintWindow(EffectWindow* w)
185 {
186     if (motionManager.isManaging(w)) {
187         if (destinationList.contains(w)) {
188             if (!motionManager.isWindowMoving(w)) { // has window reched its destination?
189                 // If we are still intersecting with the upmostWindow it is moving. slide to somewhere else
190                 // restore the stacking order of all windows not intersecting any more except panels
191                 if (coveringWindows.contains(w)) {
192                     EffectWindowList tmpList;
193                     Q_FOREACH (EffectWindow * tmp, elevatedList) {
194                         QRect elevatedGeometry = tmp->frameGeometry();
195                         if (motionManager.isManaging(tmp)) {
196                             elevatedGeometry = motionManager.transformedGeometry(tmp).toAlignedRect();
197                         }
198                         if (m_upmostWindow && !tmp->isDock() && !tmp->keepAbove() && m_upmostWindow->frameGeometry().intersects(elevatedGeometry)) {
199                             QRect newDestination;
200                             newDestination = getSlideDestination(getModalGroupGeometry(m_upmostWindow), elevatedGeometry);
201                             if (!motionManager.isManaging(tmp)) {
202                                 motionManager.manage(tmp);
203                             }
204                             motionManager.moveWindow(tmp, newDestination);
205                             destinationList[tmp] = newDestination;
206                         } else {
207                             if (!tmp->isDock()) {
208                                 bool keepElevated = false;
209                                 Q_FOREACH (EffectWindow * elevatedWindow, tmpList) {
210                                     if (tmp->frameGeometry().intersects(elevatedWindow->frameGeometry())) {
211                                         keepElevated = true;
212                                     }
213                                 }
214                                 if (!keepElevated) {
215                                     effects->setElevatedWindow(tmp, false);
216                                     elevatedList.removeAll(tmp);
217                                 }
218                             }
219                         }
220                         tmpList.append(tmp);
221                     }
222                 } else {
223                     // Move the window back where it belongs
224                     motionManager.moveWindow(w, w->frameGeometry());
225                     destinationList.remove(w);
226                 }
227             }
228         } else {
229             // is window back at its original position?
230             if (!motionManager.isWindowMoving(w)) {
231                 motionManager.unmanage(w);
232                 effects->addRepaintFull();
233             }
234         }
235         if (coveringWindows.contains(w)) {
236             // It could happen that there is no aciveWindow() here if the user clicks the close-button on an inactive window.
237             // Just skip... the window will be removed in windowDeleted() later
238             if (m_upmostWindow && !intersects(m_upmostWindow, motionManager.transformedGeometry(w).toAlignedRect())) {
239                 coveringWindows.removeAll(w);
240                 if (coveringWindows.isEmpty()) {
241                     // Restore correct stacking order
242                     Q_FOREACH (EffectWindow * tmp, elevatedList) {
243                         effects->setElevatedWindow(tmp, false);
244                     }
245                     elevatedList.clear();
246                 }
247             }
248         }
249     }
250     if (!isActive()) {
251         m_lastPresentTime = std::chrono::milliseconds::zero();
252     }
253     effects->postPaintWindow(w);
254 }
255 
slotWindowDeleted(EffectWindow * w)256 void SlideBackEffect::slotWindowDeleted(EffectWindow* w)
257 {
258     if (w == m_upmostWindow)
259         m_upmostWindow = nullptr;
260     if (w == m_justMapped)
261         m_justMapped = nullptr;
262     usableOldStackingOrder.removeAll(w);
263     oldStackingOrder.removeAll(w);
264     coveringWindows.removeAll(w);
265     elevatedList.removeAll(w);
266     if (motionManager.isManaging(w)) {
267         motionManager.unmanage(w);
268     }
269 }
270 
slotWindowAdded(EffectWindow * w)271 void SlideBackEffect::slotWindowAdded(EffectWindow *w)
272 {
273     m_justMapped = w;
274 }
275 
slotWindowUnminimized(EffectWindow * w)276 void SlideBackEffect::slotWindowUnminimized(EffectWindow* w)
277 {
278     // SlideBack should not be triggered on an unminimized window. For this we need to store the last unminimized window.
279     m_justMapped = w;
280     // the stackingOrderChanged() signal came before the window turned an effect window
281     // usually this is no problem as the change shall not be caught anyway, but
282     // the window may have changed its stack position, bug #353745
283     slotStackingOrderChanged();
284 }
285 
slotTabBoxAdded()286 void SlideBackEffect::slotTabBoxAdded()
287 {
288     ++m_tabboxActive;
289 }
290 
slotTabBoxClosed()291 void SlideBackEffect::slotTabBoxClosed()
292 {
293     m_tabboxActive = qMax(m_tabboxActive-1, 0);
294 }
295 
isWindowUsable(EffectWindow * w)296 bool SlideBackEffect::isWindowUsable(EffectWindow* w)
297 {
298     return w && (w->isNormalWindow() || w->isDialog()) && !w->keepAbove() && !w->isDeleted() && !w->isMinimized()
299            && w->isPaintingEnabled();
300 }
301 
intersects(EffectWindow * windowUnder,const QRect & windowOverGeometry)302 bool SlideBackEffect::intersects(EffectWindow* windowUnder, const QRect &windowOverGeometry)
303 {
304     QRect windowUnderGeometry = getModalGroupGeometry(windowUnder);
305     return windowUnderGeometry.intersects(windowOverGeometry);
306 }
307 
usableWindows(const EffectWindowList & allWindows)308 EffectWindowList SlideBackEffect::usableWindows(const EffectWindowList & allWindows)
309 {
310     EffectWindowList retList;
311     auto isWindowVisible = [] (const EffectWindow *window) {
312         return window && effects->virtualScreenGeometry().intersects(window->frameGeometry());
313     };
314     Q_FOREACH (EffectWindow * tmp, allWindows) {
315         if (isWindowUsable(tmp) && isWindowVisible(tmp)) {
316             retList.append(tmp);
317         }
318     }
319     return retList;
320 }
321 
getModalGroupGeometry(EffectWindow * w)322 QRect SlideBackEffect::getModalGroupGeometry(EffectWindow *w)
323 {
324     QRect modalGroupGeometry = w->frameGeometry();
325     if (w->isModal()) {
326         Q_FOREACH (EffectWindow * modalWindow, w->mainWindows()) {
327             modalGroupGeometry = modalGroupGeometry.united(getModalGroupGeometry(modalWindow));
328         }
329     }
330     return modalGroupGeometry;
331 }
332 
isActive() const333 bool SlideBackEffect::isActive() const
334 {
335     return motionManager.managingWindows();
336 }
337 
338 } //Namespace
339