File indexing completed on 2024-11-10 04:57:10

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2009 Michael Zanetti <michael_zanetti@gmx.net>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "slideback.h"
0011 #include "effect/effecthandler.h"
0012 
0013 namespace KWin
0014 {
0015 
0016 SlideBackEffect::SlideBackEffect()
0017 {
0018     m_tabboxActive = 0;
0019     m_justMapped = m_upmostWindow = nullptr;
0020     connect(effects, &EffectsHandler::windowAdded, this, &SlideBackEffect::slotWindowAdded);
0021     connect(effects, &EffectsHandler::windowDeleted, this, &SlideBackEffect::slotWindowDeleted);
0022     connect(effects, &EffectsHandler::tabBoxAdded, this, &SlideBackEffect::slotTabBoxAdded);
0023     connect(effects, &EffectsHandler::stackingOrderChanged, this, &SlideBackEffect::slotStackingOrderChanged);
0024     connect(effects, &EffectsHandler::tabBoxClosed, this, &SlideBackEffect::slotTabBoxClosed);
0025 
0026     const auto windows = effects->stackingOrder();
0027     for (EffectWindow *window : windows) {
0028         slotWindowAdded(window);
0029     }
0030 }
0031 
0032 void SlideBackEffect::slotStackingOrderChanged()
0033 {
0034     if (effects->activeFullScreenEffect() || m_tabboxActive) {
0035         oldStackingOrder = effects->stackingOrder();
0036         usableOldStackingOrder = usableWindows(oldStackingOrder);
0037         return;
0038     }
0039 
0040     QList<EffectWindow *> newStackingOrder = effects->stackingOrder(),
0041                           usableNewStackingOrder = usableWindows(newStackingOrder);
0042     if (usableNewStackingOrder == usableOldStackingOrder || usableNewStackingOrder.isEmpty()) {
0043         oldStackingOrder = newStackingOrder;
0044         usableOldStackingOrder = usableNewStackingOrder;
0045         return;
0046     }
0047 
0048     m_upmostWindow = usableNewStackingOrder.last();
0049 
0050     if (m_upmostWindow == m_justMapped) { // a window was added, got on top, stacking changed. Nothing impressive
0051         m_justMapped = nullptr;
0052     } else if (!usableOldStackingOrder.isEmpty() && m_upmostWindow != usableOldStackingOrder.last()) {
0053         windowRaised(m_upmostWindow);
0054     }
0055 
0056     oldStackingOrder = newStackingOrder;
0057     usableOldStackingOrder = usableNewStackingOrder;
0058 }
0059 
0060 void SlideBackEffect::windowRaised(EffectWindow *w)
0061 {
0062     // Determine all windows on top of the activated one
0063     bool currentFound = false;
0064     for (EffectWindow *tmp : std::as_const(oldStackingOrder)) {
0065         if (!currentFound) {
0066             if (tmp == w) {
0067                 currentFound = true;
0068             }
0069         } else {
0070             if (isWindowUsable(tmp) && tmp->isOnCurrentDesktop() && w->isOnCurrentDesktop()) {
0071                 // Do we have to move it?
0072                 if (intersects(w, tmp->frameGeometry().toRect())) {
0073                     QRect slideRect;
0074                     slideRect = getSlideDestination(getModalGroupGeometry(w), tmp->frameGeometry().toRect());
0075                     effects->setElevatedWindow(tmp, true);
0076                     elevatedList.append(tmp);
0077                     motionManager.manage(tmp);
0078                     motionManager.moveWindow(tmp, slideRect);
0079                     destinationList.insert(tmp, slideRect);
0080                     coveringWindows.append(tmp);
0081                 } else {
0082                     // Does it intersect with a moved (elevated) window and do we have to elevate it too?
0083                     for (EffectWindow *elevatedWindow : std::as_const(elevatedList)) {
0084                         if (tmp->frameGeometry().intersects(elevatedWindow->frameGeometry())) {
0085                             effects->setElevatedWindow(tmp, true);
0086                             elevatedList.append(tmp);
0087                             break;
0088                         }
0089                     }
0090                 }
0091             }
0092             if (tmp->isDock() || tmp->keepAbove()) {
0093                 effects->setElevatedWindow(tmp, true);
0094                 elevatedList.append(tmp);
0095             }
0096         }
0097     }
0098     // If a window is minimized it could happen that the panels stay elevated without any windows sliding.
0099     // clear all elevation settings
0100     if (!motionManager.managingWindows()) {
0101         for (EffectWindow *tmp : std::as_const(elevatedList)) {
0102             effects->setElevatedWindow(tmp, false);
0103         }
0104     }
0105 }
0106 
0107 QRect SlideBackEffect::getSlideDestination(const QRect &windowUnderGeometry, const QRect &windowOverGeometry)
0108 {
0109     // Determine the shortest way:
0110     int leftSlide = windowUnderGeometry.left() - windowOverGeometry.right() - 20;
0111     int rightSlide = windowUnderGeometry.right() - windowOverGeometry.left() + 20;
0112     int upSlide = windowUnderGeometry.top() - windowOverGeometry.bottom() - 20;
0113     int downSlide = windowUnderGeometry.bottom() - windowOverGeometry.top() + 20;
0114 
0115     int horizSlide = leftSlide;
0116     if (std::abs(horizSlide) > std::abs(rightSlide)) {
0117         horizSlide = rightSlide;
0118     }
0119     int vertSlide = upSlide;
0120     if (std::abs(vertSlide) > std::abs(downSlide)) {
0121         vertSlide = downSlide;
0122     }
0123 
0124     QRect slideRect = windowOverGeometry;
0125     if (std::abs(horizSlide) < std::abs(vertSlide)) {
0126         slideRect.moveLeft(slideRect.x() + horizSlide);
0127     } else {
0128         slideRect.moveTop(slideRect.y() + vertSlide);
0129     }
0130     return slideRect;
0131 }
0132 
0133 void SlideBackEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
0134 {
0135     int time = 0;
0136     if (m_lastPresentTime.count()) {
0137         time = (presentTime - m_lastPresentTime).count();
0138     }
0139     m_lastPresentTime = presentTime;
0140 
0141     if (motionManager.managingWindows()) {
0142         motionManager.calculate(time);
0143         data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
0144     }
0145 
0146     const QList<EffectWindow *> windows = effects->stackingOrder();
0147     for (auto *w : windows) {
0148         w->setData(WindowForceBlurRole, QVariant(true));
0149     }
0150 
0151     effects->prePaintScreen(data, presentTime);
0152 }
0153 
0154 void SlideBackEffect::postPaintScreen()
0155 {
0156     if (motionManager.areWindowsMoving()) {
0157         effects->addRepaintFull();
0158     }
0159 
0160     for (auto &w : effects->stackingOrder()) {
0161         w->setData(WindowForceBlurRole, QVariant());
0162     }
0163 
0164     effects->postPaintScreen();
0165 }
0166 
0167 void SlideBackEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
0168 {
0169     if (motionManager.isManaging(w)) {
0170         data.setTransformed();
0171     }
0172 
0173     effects->prePaintWindow(w, data, presentTime);
0174 }
0175 
0176 void SlideBackEffect::paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
0177 {
0178     if (motionManager.isManaging(w)) {
0179         motionManager.apply(w, data);
0180     }
0181     for (const QRegion &r : std::as_const(clippedRegions)) {
0182         region = region.intersected(r);
0183     }
0184     effects->paintWindow(renderTarget, viewport, w, mask, region, data);
0185     clippedRegions.clear();
0186 }
0187 
0188 void SlideBackEffect::postPaintWindow(EffectWindow *w)
0189 {
0190     if (motionManager.isManaging(w)) {
0191         if (destinationList.contains(w)) {
0192             if (!motionManager.isWindowMoving(w)) { // has window reched its destination?
0193                 // If we are still intersecting with the upmostWindow it is moving. slide to somewhere else
0194                 // restore the stacking order of all windows not intersecting any more except panels
0195                 if (coveringWindows.contains(w)) {
0196                     QList<EffectWindow *> tmpList;
0197                     for (EffectWindow *tmp : std::as_const(elevatedList)) {
0198                         QRect elevatedGeometry = tmp->frameGeometry().toRect();
0199                         if (motionManager.isManaging(tmp)) {
0200                             elevatedGeometry = motionManager.transformedGeometry(tmp).toAlignedRect();
0201                         }
0202                         if (m_upmostWindow && !tmp->isDock() && !tmp->keepAbove() && m_upmostWindow->frameGeometry().intersects(elevatedGeometry)) {
0203                             QRect newDestination;
0204                             newDestination = getSlideDestination(getModalGroupGeometry(m_upmostWindow), elevatedGeometry);
0205                             if (!motionManager.isManaging(tmp)) {
0206                                 motionManager.manage(tmp);
0207                             }
0208                             motionManager.moveWindow(tmp, newDestination);
0209                             destinationList[tmp] = newDestination;
0210                         } else {
0211                             if (!tmp->isDock()) {
0212                                 bool keepElevated = false;
0213                                 for (EffectWindow *elevatedWindow : std::as_const(tmpList)) {
0214                                     if (tmp->frameGeometry().intersects(elevatedWindow->frameGeometry())) {
0215                                         keepElevated = true;
0216                                     }
0217                                 }
0218                                 if (!keepElevated) {
0219                                     effects->setElevatedWindow(tmp, false);
0220                                     elevatedList.removeAll(tmp);
0221                                 }
0222                             }
0223                         }
0224                         tmpList.append(tmp);
0225                     }
0226                 } else {
0227                     // Move the window back where it belongs
0228                     motionManager.moveWindow(w, w->frameGeometry().toRect());
0229                     destinationList.remove(w);
0230                 }
0231             }
0232         } else {
0233             // is window back at its original position?
0234             if (!motionManager.isWindowMoving(w)) {
0235                 motionManager.unmanage(w);
0236                 effects->addRepaintFull();
0237             }
0238         }
0239         if (coveringWindows.contains(w)) {
0240             // It could happen that there is no aciveWindow() here if the user clicks the close-button on an inactive window.
0241             // Just skip... the window will be removed in windowDeleted() later
0242             if (m_upmostWindow && !intersects(m_upmostWindow, motionManager.transformedGeometry(w).toAlignedRect())) {
0243                 coveringWindows.removeAll(w);
0244                 if (coveringWindows.isEmpty()) {
0245                     // Restore correct stacking order
0246                     for (EffectWindow *tmp : std::as_const(elevatedList)) {
0247                         effects->setElevatedWindow(tmp, false);
0248                     }
0249                     elevatedList.clear();
0250                 }
0251             }
0252         }
0253     }
0254     if (!isActive()) {
0255         m_lastPresentTime = std::chrono::milliseconds::zero();
0256     }
0257     effects->postPaintWindow(w);
0258 }
0259 
0260 void SlideBackEffect::slotWindowDeleted(EffectWindow *w)
0261 {
0262     if (w == m_upmostWindow) {
0263         m_upmostWindow = nullptr;
0264     }
0265     if (w == m_justMapped) {
0266         m_justMapped = nullptr;
0267     }
0268     usableOldStackingOrder.removeAll(w);
0269     oldStackingOrder.removeAll(w);
0270     coveringWindows.removeAll(w);
0271     elevatedList.removeAll(w);
0272     if (motionManager.isManaging(w)) {
0273         motionManager.unmanage(w);
0274     }
0275 }
0276 
0277 void SlideBackEffect::slotWindowAdded(EffectWindow *w)
0278 {
0279     m_justMapped = w;
0280 
0281     connect(w, &EffectWindow::minimizedChanged, this, [this, w]() {
0282         if (!w->isMinimized()) {
0283             slotWindowUnminimized(w);
0284         }
0285     });
0286 }
0287 
0288 void SlideBackEffect::slotWindowUnminimized(EffectWindow *w)
0289 {
0290     // SlideBack should not be triggered on an unminimized window. For this we need to store the last unminimized window.
0291     m_justMapped = w;
0292     // the stackingOrderChanged() signal came before the window turned an effect window
0293     // usually this is no problem as the change shall not be caught anyway, but
0294     // the window may have changed its stack position, bug #353745
0295     slotStackingOrderChanged();
0296 }
0297 
0298 void SlideBackEffect::slotTabBoxAdded()
0299 {
0300     ++m_tabboxActive;
0301 }
0302 
0303 void SlideBackEffect::slotTabBoxClosed()
0304 {
0305     m_tabboxActive = std::max(m_tabboxActive - 1, 0);
0306 }
0307 
0308 bool SlideBackEffect::isWindowUsable(EffectWindow *w)
0309 {
0310     return w && (w->isNormalWindow() || w->isDialog()) && !w->keepAbove() && !w->isDeleted() && !w->isMinimized();
0311 }
0312 
0313 bool SlideBackEffect::intersects(EffectWindow *windowUnder, const QRect &windowOverGeometry)
0314 {
0315     QRect windowUnderGeometry = getModalGroupGeometry(windowUnder);
0316     return windowUnderGeometry.intersects(windowOverGeometry);
0317 }
0318 
0319 QList<EffectWindow *> SlideBackEffect::usableWindows(const QList<EffectWindow *> &allWindows)
0320 {
0321     QList<EffectWindow *> retList;
0322     auto isWindowVisible = [](const EffectWindow *window) {
0323         return window && effects->virtualScreenGeometry().intersects(window->frameGeometry().toAlignedRect());
0324     };
0325     for (EffectWindow *tmp : std::as_const(allWindows)) {
0326         if (isWindowUsable(tmp) && isWindowVisible(tmp)) {
0327             retList.append(tmp);
0328         }
0329     }
0330     return retList;
0331 }
0332 
0333 QRect SlideBackEffect::getModalGroupGeometry(EffectWindow *w)
0334 {
0335     QRect modalGroupGeometry = w->frameGeometry().toRect();
0336     if (w->isModal()) {
0337         const auto mainWindows = w->mainWindows();
0338         for (EffectWindow *modalWindow : mainWindows) {
0339             modalGroupGeometry = modalGroupGeometry.united(getModalGroupGeometry(modalWindow));
0340         }
0341     }
0342     return modalGroupGeometry;
0343 }
0344 
0345 bool SlideBackEffect::isActive() const
0346 {
0347     return motionManager.managingWindows();
0348 }
0349 
0350 } // Namespace
0351 
0352 #include "moc_slideback.cpp"