File indexing completed on 2024-11-10 04:57:09
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2007 Lubos Lunak <l.lunak@kde.org> 0006 SPDX-FileCopyrightText: 2008 Lucas Murray <lmurray@undefinedfire.com> 0007 SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 // own 0013 #include "slide.h" 0014 #include "core/output.h" 0015 #include "effect/effecthandler.h" 0016 0017 // KConfigSkeleton 0018 #include "slideconfig.h" 0019 0020 #include <cmath> 0021 0022 namespace KWin 0023 { 0024 0025 SlideEffect::SlideEffect() 0026 { 0027 SlideConfig::instance(effects->config()); 0028 reconfigure(ReconfigureAll); 0029 0030 connect(effects, &EffectsHandler::desktopChanged, 0031 this, &SlideEffect::desktopChanged); 0032 connect(effects, &EffectsHandler::desktopChanging, 0033 this, &SlideEffect::desktopChanging); 0034 connect(effects, QOverload<>::of(&EffectsHandler::desktopChangingCancelled), 0035 this, &SlideEffect::desktopChangingCancelled); 0036 connect(effects, &EffectsHandler::windowAdded, 0037 this, &SlideEffect::windowAdded); 0038 connect(effects, &EffectsHandler::windowDeleted, 0039 this, &SlideEffect::windowDeleted); 0040 connect(effects, &EffectsHandler::desktopAdded, 0041 this, &SlideEffect::finishedSwitching); 0042 connect(effects, &EffectsHandler::desktopRemoved, 0043 this, &SlideEffect::finishedSwitching); 0044 connect(effects, &EffectsHandler::screenAdded, 0045 this, &SlideEffect::finishedSwitching); 0046 connect(effects, &EffectsHandler::screenRemoved, 0047 this, &SlideEffect::finishedSwitching); 0048 0049 m_currentPosition = effects->desktopGridCoords(effects->currentDesktop()); 0050 } 0051 0052 SlideEffect::~SlideEffect() 0053 { 0054 finishedSwitching(); 0055 } 0056 0057 bool SlideEffect::supported() 0058 { 0059 return effects->animationsSupported(); 0060 } 0061 0062 void SlideEffect::reconfigure(ReconfigureFlags) 0063 { 0064 SlideConfig::self()->read(); 0065 0066 const qreal springConstant = 300.0 / effects->animationTimeFactor(); 0067 const qreal dampingRatio = 1.1; 0068 0069 m_motionX = SpringMotion(springConstant, dampingRatio); 0070 m_motionY = SpringMotion(springConstant, dampingRatio); 0071 0072 m_hGap = SlideConfig::horizontalGap(); 0073 m_vGap = SlideConfig::verticalGap(); 0074 m_slideBackground = SlideConfig::slideBackground(); 0075 } 0076 0077 inline QRegion buildClipRegion(const QPoint &pos, int w, int h) 0078 { 0079 const QSize screenSize = effects->virtualScreenSize(); 0080 QRegion r = QRect(pos, screenSize); 0081 if (effects->optionRollOverDesktops()) { 0082 r += (r & QRect(-w, 0, w, h)).translated(w, 0); // W 0083 r += (r & QRect(w, 0, w, h)).translated(-w, 0); // E 0084 0085 r += (r & QRect(0, -h, w, h)).translated(0, h); // N 0086 r += (r & QRect(0, h, w, h)).translated(0, -h); // S 0087 0088 r += (r & QRect(-w, -h, w, h)).translated(w, h); // NW 0089 r += (r & QRect(w, -h, w, h)).translated(-w, h); // NE 0090 r += (r & QRect(w, h, w, h)).translated(-w, -h); // SE 0091 r += (r & QRect(-w, h, w, h)).translated(w, -h); // SW 0092 } 0093 return r; 0094 } 0095 0096 void SlideEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) 0097 { 0098 std::chrono::milliseconds timeDelta = std::chrono::milliseconds::zero(); 0099 if (m_lastPresentTime.count()) { 0100 timeDelta = presentTime - m_lastPresentTime; 0101 } 0102 m_lastPresentTime = presentTime; 0103 0104 if (m_state == State::ActiveAnimation) { 0105 m_motionX.advance(timeDelta); 0106 m_motionY.advance(timeDelta); 0107 const QSize virtualSpaceSize = effects->virtualScreenSize(); 0108 m_currentPosition.setX(m_motionX.position() / virtualSpaceSize.width()); 0109 m_currentPosition.setY(m_motionY.position() / virtualSpaceSize.height()); 0110 } 0111 0112 const QList<VirtualDesktop *> desktops = effects->desktops(); 0113 const int w = effects->desktopGridWidth(); 0114 const int h = effects->desktopGridHeight(); 0115 0116 // Clipping 0117 m_paintCtx.visibleDesktops.clear(); 0118 m_paintCtx.visibleDesktops.reserve(4); // 4 - maximum number of visible desktops 0119 bool includedX = false, includedY = false; 0120 for (VirtualDesktop *desktop : desktops) { 0121 const QPoint coords = effects->desktopGridCoords(desktop); 0122 if (coords.x() % w == (int)(m_currentPosition.x()) % w) { 0123 includedX = true; 0124 } else if (coords.x() % w == ((int)(m_currentPosition.x()) + 1) % w) { 0125 includedX = true; 0126 } 0127 if (coords.y() % h == (int)(m_currentPosition.y()) % h) { 0128 includedY = true; 0129 } else if (coords.y() % h == ((int)(m_currentPosition.y()) + 1) % h) { 0130 includedY = true; 0131 } 0132 0133 if (includedX && includedY) { 0134 m_paintCtx.visibleDesktops << desktop; 0135 } 0136 } 0137 0138 data.mask |= PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; 0139 0140 effects->prePaintScreen(data, presentTime); 0141 } 0142 0143 void SlideEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, Output *screen) 0144 { 0145 m_paintCtx.wrap = effects->optionRollOverDesktops(); 0146 effects->paintScreen(renderTarget, viewport, mask, region, screen); 0147 } 0148 0149 QPoint SlideEffect::getDrawCoords(QPointF pos, Output *screen) 0150 { 0151 QPoint c = QPoint(); 0152 c.setX(pos.x() * (screen->geometry().width() + m_hGap)); 0153 c.setY(pos.y() * (screen->geometry().height() + m_vGap)); 0154 return c; 0155 } 0156 0157 /** 0158 * Decide whether given window @p w should be transformed/translated. 0159 * @returns @c true if given window @p w should be transformed, otherwise @c false 0160 */ 0161 bool SlideEffect::isTranslated(const EffectWindow *w) const 0162 { 0163 if (w->isOnAllDesktops()) { 0164 if (w->isDesktop()) { 0165 return m_slideBackground; 0166 } 0167 return false; 0168 } else if (w == m_movingWindow) { 0169 return false; 0170 } 0171 return true; 0172 } 0173 0174 /** 0175 * Will a window be painted during this frame? 0176 */ 0177 bool SlideEffect::willBePainted(const EffectWindow *w) const 0178 { 0179 if (w->isOnAllDesktops()) { 0180 return true; 0181 } 0182 if (w == m_movingWindow) { 0183 return true; 0184 } 0185 for (VirtualDesktop *desktop : std::as_const(m_paintCtx.visibleDesktops)) { 0186 if (w->isOnDesktop(desktop)) { 0187 return true; 0188 } 0189 } 0190 return false; 0191 } 0192 0193 void SlideEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) 0194 { 0195 data.setTransformed(); 0196 effects->prePaintWindow(w, data, presentTime); 0197 } 0198 0199 void SlideEffect::paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data) 0200 { 0201 if (!willBePainted(w)) { 0202 return; 0203 } 0204 0205 if (!isTranslated(w)) { 0206 effects->paintWindow(renderTarget, viewport, w, mask, region, data); 0207 return; 0208 } 0209 0210 const int gridWidth = effects->desktopGridWidth(); 0211 const int gridHeight = effects->desktopGridHeight(); 0212 0213 QPointF drawPosition = forcePositivePosition(m_currentPosition); 0214 drawPosition = m_paintCtx.wrap ? constrainToDrawableRange(drawPosition) : drawPosition; 0215 0216 // If we're wrapping, draw the desktop in the second position. 0217 const bool wrappingX = drawPosition.x() > gridWidth - 1; 0218 const bool wrappingY = drawPosition.y() > gridHeight - 1; 0219 0220 const auto screens = effects->screens(); 0221 0222 for (VirtualDesktop *desktop : std::as_const(m_paintCtx.visibleDesktops)) { 0223 if (!w->isOnDesktop(desktop)) { 0224 continue; 0225 } 0226 QPointF desktopTranslation = QPointF(effects->desktopGridCoords(desktop)) - drawPosition; 0227 // Decide if that first desktop should be drawn at 0 or the higher position used for wrapping. 0228 if (effects->desktopGridCoords(desktop).x() == 0 && wrappingX) { 0229 desktopTranslation = QPointF(desktopTranslation.x() + gridWidth, desktopTranslation.y()); 0230 } 0231 if (effects->desktopGridCoords(desktop).y() == 0 && wrappingY) { 0232 desktopTranslation = QPointF(desktopTranslation.x(), desktopTranslation.y() + gridHeight); 0233 } 0234 0235 for (Output *screen : screens) { 0236 QPoint drawTranslation = getDrawCoords(desktopTranslation, screen); 0237 data += drawTranslation; 0238 0239 const QRect screenArea = screen->geometry(); 0240 const QRect damage = screenArea.translated(drawTranslation).intersected(screenArea); 0241 0242 effects->paintWindow( 0243 renderTarget, viewport, w, mask, 0244 // Only paint the region that intersects the current screen and desktop. 0245 region.intersected(damage), 0246 data); 0247 0248 // Undo the translation for the next screen. I know, it hurts me too. 0249 data += QPoint(-drawTranslation.x(), -drawTranslation.y()); 0250 } 0251 } 0252 } 0253 0254 void SlideEffect::postPaintScreen() 0255 { 0256 if (m_state == State::ActiveAnimation && !m_motionX.isMoving() && !m_motionY.isMoving()) { 0257 finishedSwitching(); 0258 } 0259 0260 effects->addRepaintFull(); 0261 effects->postPaintScreen(); 0262 } 0263 0264 /* 0265 * Negative desktop positions aren't allowed. 0266 */ 0267 QPointF SlideEffect::forcePositivePosition(QPointF p) const 0268 { 0269 if (p.x() < 0) { 0270 p.setX(p.x() + std::ceil(-p.x() / effects->desktopGridWidth()) * effects->desktopGridWidth()); 0271 } 0272 if (p.y() < 0) { 0273 p.setY(p.y() + std::ceil(-p.y() / effects->desktopGridHeight()) * effects->desktopGridHeight()); 0274 } 0275 return p; 0276 } 0277 0278 bool SlideEffect::shouldElevate(const EffectWindow *w) const 0279 { 0280 // Static docks(i.e. this effect doesn't slide docks) should be elevated 0281 // so they can properly animate themselves when an user enters or leaves 0282 // a virtual desktop with a window in fullscreen mode. 0283 return w->isDock(); 0284 } 0285 0286 /* 0287 * This function is called when the desktop changes. 0288 * Called AFTER the gesture is released. 0289 * Sets up animation to round off to the new current desktop. 0290 */ 0291 void SlideEffect::startAnimation(VirtualDesktop *old, VirtualDesktop *current, EffectWindow *movingWindow) 0292 { 0293 if (m_state == State::Inactive) { 0294 prepareSwitching(); 0295 } 0296 0297 m_state = State::ActiveAnimation; 0298 m_movingWindow = movingWindow; 0299 0300 m_startPos = m_currentPosition; 0301 m_endPos = effects->desktopGridCoords(current); 0302 if (effects->optionRollOverDesktops()) { 0303 optimizePath(); 0304 } 0305 0306 const QSize virtualSpaceSize = effects->virtualScreenSize(); 0307 m_motionX.setAnchor(m_endPos.x() * virtualSpaceSize.width()); 0308 m_motionX.setPosition(m_startPos.x() * virtualSpaceSize.width()); 0309 m_motionY.setAnchor(m_endPos.y() * virtualSpaceSize.height()); 0310 m_motionY.setPosition(m_startPos.y() * virtualSpaceSize.height()); 0311 0312 effects->setActiveFullScreenEffect(this); 0313 effects->addRepaintFull(); 0314 } 0315 0316 void SlideEffect::prepareSwitching() 0317 { 0318 const auto windows = effects->stackingOrder(); 0319 m_windowData.reserve(windows.count()); 0320 0321 for (EffectWindow *w : windows) { 0322 m_windowData[w] = WindowData{ 0323 .visibilityRef = EffectWindowVisibleRef(w, EffectWindow::PAINT_DISABLED_BY_DESKTOP), 0324 }; 0325 0326 if (shouldElevate(w)) { 0327 effects->setElevatedWindow(w, true); 0328 m_elevatedWindows << w; 0329 } 0330 w->setData(WindowForceBackgroundContrastRole, QVariant(true)); 0331 w->setData(WindowForceBlurRole, QVariant(true)); 0332 } 0333 } 0334 0335 void SlideEffect::finishedSwitching() 0336 { 0337 if (m_state == State::Inactive) { 0338 return; 0339 } 0340 const QList<EffectWindow *> windows = effects->stackingOrder(); 0341 for (EffectWindow *w : windows) { 0342 w->setData(WindowForceBackgroundContrastRole, QVariant()); 0343 w->setData(WindowForceBlurRole, QVariant()); 0344 } 0345 0346 for (EffectWindow *w : std::as_const(m_elevatedWindows)) { 0347 effects->setElevatedWindow(w, false); 0348 } 0349 m_elevatedWindows.clear(); 0350 0351 m_windowData.clear(); 0352 m_movingWindow = nullptr; 0353 m_state = State::Inactive; 0354 m_lastPresentTime = std::chrono::milliseconds::zero(); 0355 effects->setActiveFullScreenEffect(nullptr); 0356 m_currentPosition = effects->desktopGridCoords(effects->currentDesktop()); 0357 } 0358 0359 void SlideEffect::desktopChanged(VirtualDesktop *old, VirtualDesktop *current, EffectWindow *with) 0360 { 0361 if (effects->hasActiveFullScreenEffect() && effects->activeFullScreenEffect() != this) { 0362 m_currentPosition = effects->desktopGridCoords(effects->currentDesktop()); 0363 return; 0364 } 0365 0366 startAnimation(old, current, with); 0367 } 0368 0369 void SlideEffect::desktopChanging(VirtualDesktop *old, QPointF desktopOffset, EffectWindow *with) 0370 { 0371 if (effects->hasActiveFullScreenEffect() && effects->activeFullScreenEffect() != this) { 0372 return; 0373 } 0374 0375 if (m_state == State::Inactive) { 0376 prepareSwitching(); 0377 } 0378 0379 m_state = State::ActiveGesture; 0380 m_movingWindow = with; 0381 0382 // Find desktop position based on animationDelta 0383 QPoint gridPos = effects->desktopGridCoords(old); 0384 m_currentPosition.setX(gridPos.x() + desktopOffset.x()); 0385 m_currentPosition.setY(gridPos.y() + desktopOffset.y()); 0386 0387 if (effects->optionRollOverDesktops()) { 0388 m_currentPosition = forcePositivePosition(m_currentPosition); 0389 } else { 0390 m_currentPosition = moveInsideDesktopGrid(m_currentPosition); 0391 } 0392 0393 effects->setActiveFullScreenEffect(this); 0394 effects->addRepaintFull(); 0395 } 0396 0397 void SlideEffect::desktopChangingCancelled() 0398 { 0399 // If the fingers have been lifted and the current desktop didn't change, start animation 0400 // to move back to the original virtual desktop. 0401 if (effects->activeFullScreenEffect() == this) { 0402 startAnimation(effects->currentDesktop(), effects->currentDesktop(), nullptr); 0403 } 0404 } 0405 0406 QPointF SlideEffect::moveInsideDesktopGrid(QPointF p) 0407 { 0408 if (p.x() < 0) { 0409 p.setX(0); 0410 } 0411 if (p.y() < 0) { 0412 p.setY(0); 0413 } 0414 if (p.x() > effects->desktopGridWidth() - 1) { 0415 p.setX(effects->desktopGridWidth() - 1); 0416 } 0417 if (p.y() > effects->desktopGridHeight() - 1) { 0418 p.setY(effects->desktopGridHeight() - 1); 0419 } 0420 return p; 0421 } 0422 0423 void SlideEffect::windowAdded(EffectWindow *w) 0424 { 0425 if (m_state == State::Inactive) { 0426 return; 0427 } 0428 if (shouldElevate(w)) { 0429 effects->setElevatedWindow(w, true); 0430 m_elevatedWindows << w; 0431 } 0432 w->setData(WindowForceBackgroundContrastRole, QVariant(true)); 0433 w->setData(WindowForceBlurRole, QVariant(true)); 0434 0435 m_windowData[w] = WindowData{ 0436 .visibilityRef = EffectWindowVisibleRef(w, EffectWindow::PAINT_DISABLED_BY_DESKTOP), 0437 }; 0438 } 0439 0440 void SlideEffect::windowDeleted(EffectWindow *w) 0441 { 0442 if (m_state == State::Inactive) { 0443 return; 0444 } 0445 if (w == m_movingWindow) { 0446 m_movingWindow = nullptr; 0447 } 0448 m_elevatedWindows.removeAll(w); 0449 m_windowData.remove(w); 0450 } 0451 0452 /* 0453 * Find the fastest path between two desktops. 0454 * This function decides when it's better to wrap around the grid or not. 0455 * Only call if wrapping is enabled. 0456 */ 0457 void SlideEffect::optimizePath() 0458 { 0459 int w = effects->desktopGridWidth(); 0460 int h = effects->desktopGridHeight(); 0461 0462 // Keep coordinates as low as possible 0463 if (m_startPos.x() >= w && m_endPos.x() >= w) { 0464 m_startPos.setX(fmod(m_startPos.x(), w)); 0465 m_endPos.setX(fmod(m_endPos.x(), w)); 0466 } 0467 if (m_startPos.y() >= h && m_endPos.y() >= h) { 0468 m_startPos.setY(fmod(m_startPos.y(), h)); 0469 m_endPos.setY(fmod(m_endPos.y(), h)); 0470 } 0471 0472 // Is there is a shorter possible route? 0473 // If the x distance to be traveled is more than half the grid width, it's faster to wrap. 0474 // To avoid negative coordinates, take the lower coordinate and raise. 0475 if (std::abs((m_startPos.x() - m_endPos.x())) > w / 2.0) { 0476 if (m_startPos.x() < m_endPos.x()) { 0477 while (m_startPos.x() < m_endPos.x()) { 0478 m_startPos.setX(m_startPos.x() + w); 0479 } 0480 } else { 0481 while (m_endPos.x() < m_startPos.x()) { 0482 m_endPos.setX(m_endPos.x() + w); 0483 } 0484 } 0485 // Keep coordinates as low as possible 0486 if (m_startPos.x() >= w && m_endPos.x() >= w) { 0487 m_startPos.setX(fmod(m_startPos.x(), w)); 0488 m_endPos.setX(fmod(m_endPos.x(), w)); 0489 } 0490 } 0491 0492 // Same for y 0493 if (std::abs((m_endPos.y() - m_startPos.y())) > (double)h / (double)2) { 0494 if (m_startPos.y() < m_endPos.y()) { 0495 while (m_startPos.y() < m_endPos.y()) { 0496 m_startPos.setY(m_startPos.y() + h); 0497 } 0498 } else { 0499 while (m_endPos.y() < m_startPos.y()) { 0500 m_endPos.setY(m_endPos.y() + h); 0501 } 0502 } 0503 // Keep coordinates as low as possible 0504 if (m_startPos.y() >= h && m_endPos.y() >= h) { 0505 m_startPos.setY(fmod(m_startPos.y(), h)); 0506 m_endPos.setY(fmod(m_endPos.y(), h)); 0507 } 0508 } 0509 } 0510 0511 /* 0512 * Takes the point and uses modulus to keep draw position within [0, desktopGridWidth] 0513 * The render loop will draw the first desktop (0) after the last one (at position desktopGridWidth) for the wrap animation. 0514 * This function finds the true fastest path, regardless of which direction the animation is already going; 0515 * I was a little upset about this limitation until I realized that MacOS can't even wrap desktops :) 0516 */ 0517 QPointF SlideEffect::constrainToDrawableRange(QPointF p) 0518 { 0519 p.setX(fmod(p.x(), effects->desktopGridWidth())); 0520 p.setY(fmod(p.y(), effects->desktopGridHeight())); 0521 return p; 0522 } 0523 0524 } // namespace KWin 0525 0526 #include "moc_slide.cpp"