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