diff --git a/autotests/test_virtual_desktops.cpp b/autotests/test_virtual_desktops.cpp index 795acf8a456d2b17b2f7d77967b4c9e0a0a01dd4..0f33bff64a13a871f457918931193824971d50ed 100644 --- a/autotests/test_virtual_desktops.cpp +++ b/autotests/test_virtual_desktops.cpp @@ -35,8 +35,16 @@ void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, Poi void InputRedirection::registerTouchpadSwipeShortcut(SwipeDirection, uint fingerCount, QAction*) { + Q_UNUSED(fingerCount) } +void InputRedirection::registerRealtimeTouchpadSwipeShortcut(SwipeDirection direction, uint fingerCount, QAction *action, std::function cb) +{ + Q_UNUSED(direction) + Q_UNUSED(fingerCount) + Q_UNUSED(action) + Q_UNUSED(cb) +} } Q_DECLARE_METATYPE(Qt::Orientation) diff --git a/src/effects.cpp b/src/effects.cpp index 75255bca24dee2325a3ed53376dd38c228b9a20a..07271838bf1e70a4025932d4b071ebb88d74157b 100644 --- a/src/effects.cpp +++ b/src/effects.cpp @@ -155,6 +155,16 @@ EffectsHandlerImpl::EffectsHandlerImpl(Compositor *compositor, Scene *scene) } } ); + connect(ws, &Workspace::currentDesktopChanging, this, + [this](uint currentDesktop, QPointF offset, KWin::AbstractClient* c){ + Q_EMIT desktopChanging(currentDesktop, offset, c ? c->effectWindow() : nullptr); + } + ); + connect(ws, &Workspace::currentDesktopChangingCancelled, this, + [this](){ + Q_EMIT desktopChangingCancelled(); + } + ); connect(ws, &Workspace::desktopPresenceChanged, this, [this](AbstractClient *c, int old) { if (!c->effectWindow()) { diff --git a/src/effects/slide/slide.cpp b/src/effects/slide/slide.cpp index c9cc15c9161d1b70e3161ea07fcf1bd9ebaea27a..33c0680b9bca2a3466b547e103ae686ac94c1140 100644 --- a/src/effects/slide/slide.cpp +++ b/src/effects/slide/slide.cpp @@ -15,6 +15,9 @@ // KConfigSkeleton #include "slideconfig.h" +#include +#include + namespace KWin { @@ -27,21 +30,27 @@ SlideEffect::SlideEffect() connect(effects, QOverload::of(&EffectsHandler::desktopChanged), this, &SlideEffect::desktopChanged); + connect(effects, QOverload::of(&EffectsHandler::desktopChanging), + this, &SlideEffect::desktopChanging); + connect(effects, QOverload<>::of(&EffectsHandler::desktopChangingCancelled), + this, &SlideEffect::desktopChangingCancelled); connect(effects, &EffectsHandler::windowAdded, this, &SlideEffect::windowAdded); connect(effects, &EffectsHandler::windowDeleted, this, &SlideEffect::windowDeleted); connect(effects, &EffectsHandler::numberDesktopsChanged, - this, &SlideEffect::stop); + this, &SlideEffect::finishedSwitching); connect(effects, &EffectsHandler::screenAdded, - this, &SlideEffect::stop); + this, &SlideEffect::finishedSwitching); connect(effects, &EffectsHandler::screenRemoved, - this, &SlideEffect::stop); + this, &SlideEffect::finishedSwitching); + + m_currentPosition = effects->desktopCoords(effects->currentDesktop()); } SlideEffect::~SlideEffect() { - stop(); + finishedSwitching(); } bool SlideEffect::supported() @@ -53,8 +62,8 @@ void SlideEffect::reconfigure(ReconfigureFlags) { SlideConfig::self()->read(); - m_timeLine.setDuration( - std::chrono::milliseconds(animationTime(500))); + m_animationDuration = animationTime(500); + m_timeLine.setDuration(std::chrono::milliseconds(m_animationDuration)); m_hGap = SlideConfig::horizontalGap(); m_vGap = SlideConfig::verticalGap(); @@ -64,98 +73,69 @@ void SlideEffect::reconfigure(ReconfigureFlags) void SlideEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) { - std::chrono::milliseconds delta = std::chrono::milliseconds::zero(); - if (m_lastPresentTime.count()) { - delta = presentTime - m_lastPresentTime; - } - m_lastPresentTime = presentTime; - - m_timeLine.update(delta); - - data.mask |= PAINT_SCREEN_TRANSFORMED - | PAINT_SCREEN_BACKGROUND_FIRST; - - effects->prePaintScreen(data, presentTime); -} - -/** - * Wrap vector @p diff around grid @p w x @p h. - * - * Wrapping is done in such a way that magnitude of x and y component of vector - * @p diff is less than half of @p w and half of @p h, respectively. This will - * result in having the "shortest" path between two points. - * - * @param diff Vector between two points - * @param w Width of the desktop grid - * @param h Height of the desktop grid - */ -inline void wrapDiff(QPoint &diff, int w, int h) -{ - if (diff.x() > w/2) { - diff.setX(diff.x() - w); - } else if (diff.x() < -w/2) { - diff.setX(diff.x() + w); - } + std::chrono::milliseconds timeDelta = std::chrono::milliseconds::zero(); + if (m_lastPresentTime.count()) { + timeDelta = presentTime - m_lastPresentTime; + } + m_lastPresentTime = presentTime; - if (diff.y() > h/2) { - diff.setY(diff.y() - h); - } else if (diff.y() < -h/2) { - diff.setY(diff.y() + h); - } -} + m_timeLine.update(timeDelta); -inline QRegion buildClipRegion(const QPoint &pos, int w, int h) -{ - const QSize screenSize = effects->virtualScreenSize(); - QRegion r = QRect(pos, screenSize); - if (effects->optionRollOverDesktops()) { - r |= (r & QRect(-w, 0, w, h)).translated(w, 0); // W - r |= (r & QRect(w, 0, w, h)).translated(-w, 0); // E + if (!m_gestureActive) { // When animating + m_currentPosition = m_startPos + (m_endPos - m_startPos) * m_timeLine.value(); + } - r |= (r & QRect(0, -h, w, h)).translated(0, h); // N - r |= (r & QRect(0, h, w, h)).translated(0, -h); // S + data.mask |= PAINT_SCREEN_TRANSFORMED + | PAINT_SCREEN_BACKGROUND_FIRST; - r |= (r & QRect(-w, -h, w, h)).translated(w, h); // NW - r |= (r & QRect(w, -h, w, h)).translated(-w, h); // NE - r |= (r & QRect(w, h, w, h)).translated(-w, -h); // SE - r |= (r & QRect(-w, h, w, h)).translated(w, -h); // SW - } - return r; + effects->prePaintScreen(data, presentTime); } void SlideEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data) { const bool wrap = effects->optionRollOverDesktops(); - const int w = workspaceWidth(); - const int h = workspaceHeight(); + const int w = effects->desktopGridWidth(); + const int h = effects->desktopGridHeight(); + bool wrappingX = false, wrappingY = false; - QPoint currentPos = m_startPos + m_diff * m_timeLine.value(); + QPointF drawPosition = forcePositivePosition(m_currentPosition); - // When "Desktop navigation wraps around" checkbox is checked, currentPos - // can be outside the rectangle Rect{x:-w, y:-h, width:2*w, height: 2*h}, - // so we map currentPos back to the rect. - if (wrap) { - currentPos.setX(currentPos.x() % w); - currentPos.setY(currentPos.y() % h); + if (wrap) { // + drawPosition = constrainToDrawableRange(drawPosition); } + //Clipping QVector visibleDesktops; visibleDesktops.reserve(4); // 4 - maximum number of visible desktops - const QRegion clipRegion = buildClipRegion(currentPos, w, h); for (int i = 1; i <= effects->numberOfDesktops(); i++) { - const QRect desktopGeo = desktopGeometry(i); - if (!clipRegion.contains(desktopGeo)) { - continue; + if (effects->desktopGridCoords(i).x() % w == (int)(m_currentPosition.x()) % w) { + visibleDesktops << i; + } else if (effects->desktopGridCoords(i).x() % w == ((int)(m_currentPosition.x()) + 1) % w) { + visibleDesktops << i; + } else if (effects->desktopGridCoords(i).y() % h == (int)(m_currentPosition.y()) % h) { + visibleDesktops << i; + } else if (effects->desktopGridCoords(i).y() % h == ((int)(m_currentPosition.y()) + 1) % h) { + visibleDesktops << i; } - visibleDesktops << i; } - // When we enter a virtual desktop that has a window in fullscreen mode, - // stacking order is fine. When we leave a virtual desktop that has - // a window in fullscreen mode, stacking order is no longer valid - // because panels are raised above the fullscreen window. Construct - // a list of fullscreen windows, so we can decide later whether - // docks should be visible on different virtual desktops. + //If we're wrapping, draw the desktop in the second position. + if (drawPosition.x() > w - 1) { + wrappingX = true; + } + + if (drawPosition.y() > h - 1) { + wrappingY = true; + } + + /* + * When we enter a virtual desktop that has a window in fullscreen mode, + * stacking order is fine. When we leave a virtual desktop that has + * a window in fullscreen mode, stacking order is no longer valid + * because panels are raised above the fullscreen window. Construct + * a list of fullscreen windows, so we can decide later whether + * docks should be visible on different virtual desktops. + */ if (m_slideDocks) { const auto windows = effects->stackingOrder(); m_paintCtx.fullscreenWindows.clear(); @@ -167,25 +147,41 @@ void SlideEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData & } } - // Screen is painted in several passes. Each painting pass paints - // a single virtual desktop. There could be either 2 or 4 painting - // passes, depending how an user moves between virtual desktops. - // Windows, such as docks or keep-above windows, are painted in - // the last pass so they are above other windows. + /* + * Screen is painted in several passes. Each painting pass paints + * a single virtual desktop. There could be either 2 or 4 painting + * passes, depending how an user moves between virtual desktops. + * Windows, such as docks or keep-above windows, are painted in + * the last pass so they are above other windows. + */ m_paintCtx.firstPass = true; const int lastDesktop = visibleDesktops.last(); for (int desktop : qAsConst(visibleDesktops)) { m_paintCtx.desktop = desktop; m_paintCtx.lastPass = (lastDesktop == desktop); - m_paintCtx.translation = desktopCoords(desktop) - currentPos; - if (wrap) { - wrapDiff(m_paintCtx.translation, w, h); + m_paintCtx.translation = QPointF(effects->desktopGridCoords(desktop)) - drawPosition;//TODO: verify + + // Decide if that first desktop should be drawn at 0 or the higher position used for wrapping. + if (effects->desktopGridCoords(desktop).x() == 0 && wrappingX) { + m_paintCtx.translation = QPointF(m_paintCtx.translation.x() + w, m_paintCtx.translation.y()); + } + + if (effects->desktopGridCoords(desktop).y() == 0 && wrappingY) { + m_paintCtx.translation = QPointF(m_paintCtx.translation.x(), m_paintCtx.translation.y() + h); } + effects->paintScreen(mask, region, data); m_paintCtx.firstPass = false; } } +QPoint SlideEffect::getDrawCoords(QPointF pos, EffectScreen *screen){ + QPoint c = QPoint(); + c.setX(pos.x() * (screen->geometry().width() + m_hGap)); + c.setY(pos.y() * (screen->geometry().height() + m_vGap)); + return c; +} + /** * Decide whether given window @p w should be transformed/translated. * @returns @c true if given window @p w should be transformed, otherwise @c false @@ -228,15 +224,19 @@ bool SlideEffect::isPainted(const EffectWindow *w) const return true; } if (w->isDesktop()) { - // If desktop background is not being slided, draw it only - // in the first pass. Otherwise, desktop backgrounds from - // follow-up virtual desktops will be drawn above windows - // from previous virtual desktops. + /* + * If desktop background is not being slided, draw it only + * in the first pass. Otherwise, desktop backgrounds from + * follow-up virtual desktops will be drawn above windows + * from previous virtual desktops. + */ return m_slideBackground || m_paintCtx.firstPass; } - // In order to make sure that 'keep above' windows are above - // other windows during transition to another virtual desktop, - // they should be painted in the last pass. + /* + * In order to make sure that 'keep above' windowscreen->geometry().x()s are above + * other windows during transition to another virtual desktop, + * they should be painted in the last pass. + */ if (w->keepAbove()) { return m_paintCtx.lastPass; } @@ -252,6 +252,7 @@ bool SlideEffect::isPainted(const EffectWindow *w) const void SlideEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) { const bool painted = isPainted(w); + if (painted) { w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } else { @@ -265,96 +266,74 @@ void SlideEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std: void SlideEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { - if (isTranslated(w)) { - data += m_paintCtx.translation; + for (EffectScreen *screen: effects->screens()) { + QPoint translation = getDrawCoords(m_paintCtx.translation, screen); + if (isTranslated(w)) { + data += translation; + } + + effects->paintWindow( + w, + mask, + // Only paint the region that intersects the current screen and desktop. + region.intersected(effects->clientArea(ScreenArea, w)).intersected(effects->clientArea(ScreenArea, screen, effects->currentDesktop())), + data); + + if (isTranslated(w)) { + // Undo the translation for the next screen. I know, it hurts me too. + data += QPoint(-translation.x(), -translation.y()); + } } - effects->paintWindow(w, mask, region, data); } void SlideEffect::postPaintScreen() { - if (m_timeLine.done()) { - stop(); + if (m_timeLine.done() && !m_gestureActive) { + finishedSwitching(); } effects->addRepaintFull(); effects->postPaintScreen(); } -/** - * Get position of the top-left corner of desktop @p id within desktop grid with gaps. - * @param id ID of a virtual desktop - */ -QPoint SlideEffect::desktopCoords(int id) const -{ - QPoint c = effects->desktopCoords(id); - const QPoint gridPos = effects->desktopGridCoords(id); - c.setX(c.x() + m_hGap * gridPos.x()); - c.setY(c.y() + m_vGap * gridPos.y()); - return c; -} - -/** - * Get geometry of desktop @p id within desktop grid with gaps. - * @param id ID of a virtual desktop - */ -QRect SlideEffect::desktopGeometry(int id) const -{ - QRect g = effects->virtualScreenGeometry(); - g.translate(desktopCoords(id)); - return g; -} - -/** - * Get width of a virtual desktop grid. - */ -int SlideEffect::workspaceWidth() const -{ - int w = effects->workspaceWidth(); - w += m_hGap * effects->desktopGridWidth(); - return w; -} - -/** - * Get height of a virtual desktop grid. +/* + * Negative desktop positions aren't allowed. */ -int SlideEffect::workspaceHeight() const +QPointF SlideEffect::forcePositivePosition(QPointF p) const { - int h = effects->workspaceHeight(); - h += m_vGap * effects->desktopGridHeight(); - return h; + while (p.x() < 0) { + p.setX(p.x() + effects->desktopGridWidth()); + } + while (p.y() < 0) { + p.setY(p.y() + effects->desktopGridHeight()); + } + return p; } bool SlideEffect::shouldElevate(const EffectWindow *w) const { - // Static docks(i.e. this effect doesn't slide docks) should be elevated - // so they can properly animate themselves when an user enters or leaves - // a virtual desktop with a window in fullscreen mode. + /* + * Static docks(i.e. this effect doesn't slide docks) should be elevated + * so they can properly animate themselves when an user enters or leaves + * a virtual desktop with a window in fullscreen mode. + */ return w->isDock() && !m_slideDocks; } -void SlideEffect::start(int old, int current, EffectWindow *movingWindow) +/* + * This function is called when the desktop changes. + * Called AFTER the gesture is released. + * Sets up animation to round off to the new current desktop. + */ +void SlideEffect::startAnimation(int old, int current, EffectWindow *movingWindow) { + Q_UNUSED(old) + m_movingWindow = movingWindow; const bool wrap = effects->optionRollOverDesktops(); - const int w = workspaceWidth(); - const int h = workspaceHeight(); - - if (m_active) { - QPoint passed = m_diff * m_timeLine.value(); - QPoint currentPos = m_startPos + passed; - QPoint delta = desktopCoords(current) - desktopCoords(old); - if (wrap) { - wrapDiff(delta, w, h); - } - m_diff += delta - passed; - m_startPos = currentPos; - // TODO: Figure out how to smooth movement. - m_timeLine.reset(); - return; - } + //Handle stacking order const auto windows = effects->stackingOrder(); for (EffectWindow *w : windows) { if (shouldElevate(w)) { @@ -365,18 +344,35 @@ void SlideEffect::start(int old, int current, EffectWindow *movingWindow) w->setData(WindowForceBlurRole, QVariant(true)); } - m_diff = desktopCoords(current) - desktopCoords(old); + // Set up animation + m_active = true; + m_timeLine.reset(); + + m_startPos = m_currentPosition; + m_endPos = effects->desktopGridCoords(current); if (wrap) { - wrapDiff(m_diff, w, h); + optimizePath(); } - m_startPos = desktopCoords(old); - m_timeLine.reset(); - m_active = true; + + // Find an apropriate duration + m_timeLine.setDuration(std::chrono::milliseconds(m_animationDuration)); + + QPointF distance = m_startPos - m_endPos; + distance.setX(std::abs(distance.x())); + distance.setY(std::abs(distance.y())); + if (distance.x() < 1 && distance.y() < 1) { + if (distance.x() > distance.y()) { + m_timeLine.setDuration(std::chrono::milliseconds((int)(m_animationDuration * distance.x()))); + } else { + m_timeLine.setDuration(std::chrono::milliseconds((int)(m_animationDuration * distance.y()))); + } + } + effects->setActiveFullScreenEffect(this); effects->addRepaintFull(); } -void SlideEffect::stop() +void SlideEffect::finishedSwitching() { if (!m_active) { return; @@ -397,14 +393,70 @@ void SlideEffect::stop() m_active = false; m_lastPresentTime = std::chrono::milliseconds::zero(); effects->setActiveFullScreenEffect(nullptr); + m_currentPosition = effects->desktopGridCoords(effects->currentDesktop()); } void SlideEffect::desktopChanged(int old, int current, EffectWindow *with) { - if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) { + if (effects->hasActiveFullScreenEffect() && effects->activeFullScreenEffect() != this) { + m_currentPosition = effects->desktopGridCoords(effects->currentDesktop()); + return; + } + + m_gestureActive = false; + startAnimation(old, current, with); +} + +void SlideEffect::desktopChanging(uint old, QPointF desktopOffset, EffectWindow *with) +{ + if (effects->hasActiveFullScreenEffect() && effects->activeFullScreenEffect() != this) { return; } - start(old, current, with); + + m_gestureActive = true; + m_movingWindow = with; + + const bool wrap = effects->optionRollOverDesktops(); + + // Find desktop position based on animationDelta + QPoint gridPos = effects->desktopGridCoords(old); + m_currentPosition.setX(gridPos.x() + desktopOffset.x()); + m_currentPosition.setY(gridPos.y() + desktopOffset.y()); + + m_currentPosition = forcePositivePosition(m_currentPosition); + + if (!wrap) { + m_currentPosition = moveInsideDesktopGrid(m_currentPosition); + } + + m_active = true; + effects->setActiveFullScreenEffect(this); + effects->addRepaintFull(); +} + +void SlideEffect::desktopChangingCancelled() +{ + if (effects->hasActiveFullScreenEffect() && effects->activeFullScreenEffect() != this) { + return; + } + + std::cout << "Cancelled" << std::endl; + m_gestureActive = false; + startAnimation(effects->currentDesktop(), effects->currentDesktop(), nullptr); +} + +QPointF SlideEffect::moveInsideDesktopGrid(QPointF p) +{ + if (p.x() < 0) { + p.setX(0); + } else if (p.y() < 0) { + p.setY(0); + } else if (p.x() > effects->desktopGridWidth() - 1) { + p.setX(effects->desktopGridWidth() - 1); + } else if (p.y() > effects->desktopGridHeight() - 1) { + p.setY(effects->desktopGridHeight() - 1); + } + return p; } void SlideEffect::windowAdded(EffectWindow *w) @@ -432,4 +484,76 @@ void SlideEffect::windowDeleted(EffectWindow *w) m_paintCtx.fullscreenWindows.removeAll(w); } +/* + * Find the fastest path between two desktops. + * This function decides when it's better to wrap around the grid or not. + * Only call if wrapping is enabled. + */ +void SlideEffect::optimizePath() +{ + int w = effects->desktopGridWidth(); + int h = effects->desktopGridHeight(); + + // Keep coordinates as low as possible + if(m_startPos.x() >= w && m_endPos.x() >= w) { + m_startPos.setX(fmod(m_startPos.x(), w)); + m_endPos.setX(fmod(m_endPos.x(), w)); + } + if(m_startPos.y() >= h && m_endPos.y() >= h) { + m_startPos.setY(fmod(m_startPos.y(), h)); + m_endPos.setY(fmod(m_endPos.y(), h)); + } + + /* + * Is there is a shorter possible route? + * If the x distance to be traveled is more than half the grid width, it's faster to wrap. + * To avoid negative coordinates, take the lower coordinate and raise. + */ + if (std::abs((m_startPos.x() - m_endPos.x())) > (double)w / (double)2) { + if (m_startPos.x() < m_endPos.x()) { + while (m_startPos.x() < m_endPos.x()) + m_startPos.setX(m_startPos.x() + w); + + } else { + while (m_endPos.x() < m_startPos.x()) + m_endPos.setX(m_endPos.x() + w); + } + // Keep coordinates as low as possible + if(m_startPos.x() >= w && m_endPos.x() >= w) { + m_startPos.setX(fmod(m_startPos.x(), w)); + m_endPos.setX(fmod(m_endPos.x(), w)); + } + } + + // Same for y + if (std::abs((m_endPos.y() - m_startPos.y())) > (double)h / (double)2) { + if (m_startPos.y() < m_endPos.y()) { + while (m_startPos.y() < m_endPos.y()) + m_startPos.setY(m_startPos.y() + h); + + } else { + while (m_endPos.y() < m_startPos.y()) + m_endPos.setY(m_endPos.y() + h); + } + // Keep coordinates as low as possible + if(m_startPos.y() >= h && m_endPos.y() >= h) { + m_startPos.setY(fmod(m_startPos.y(), h)); + m_endPos.setY(fmod(m_endPos.y(), h)); + } + } +} + +/* + * Takes the point and uses modulus to keep draw position within [0, desktopGridWidth] + * The render loop will draw the first desktop (0) after the last one (at position desktopGridWidth) for the wrap animation. + * This function finds the true fastest path, regardless of which direction the animation is already going; + * I was a little upset about this limitation until I realized that MacOS can't even wrap desktops :) + */ +QPointF SlideEffect::constrainToDrawableRange(QPointF p) +{ + p.setX(fmod(p.x(), effects->desktopGridWidth())); + p.setY(fmod(p.y(), effects->desktopGridHeight())); + return p; +} + } // namespace KWin diff --git a/src/effects/slide/slide.h b/src/effects/slide/slide.h index c7c7ef572ef6a5f4cd34491301cd0004244c664e..586b27d07c76587f6b4ee0394afe4628dad18853 100644 --- a/src/effects/slide/slide.h +++ b/src/effects/slide/slide.h @@ -18,6 +18,31 @@ namespace KWin { +/* + * How it Works: + * + * This effect doesn't change the current desktop, only recieves changes from the VirtualDesktopManager. + * The only visually aparent inputs are desktopChanged() and desktopChanging(). + * + * When responding to desktopChanging(), the draw position is only affected by what's recieved from there. + * After desktopChanging() is done, or without desktopChanging() having been called at all, desktopChanged() is called. + * The desktopChanged() function configures the m_startPos and m_endPos for the animation, and the duration. + * + * m_currentPosition and m_paintCtx.translation and everything else not labeled "drawCoordinate" uses desktops as a unit. + * Exmp: 1.2 means the dekstop at index 1 shifted over by .2 desktops. + * All coords must be positive. + * + * For the wrapping effect, the render loop has to handle desktop coordinates larger than the total grid's width. + * 1. It uses modulus to keep the desktop coords in the range [0, gridWidth]. + * 2. It will draw the desktop at index 0 at index gridWidth if it has to. + * I will not draw any thing farther outside the range than that. + * + * I've put an explanation of all the important private vars down at the bottom. + * + * Good luck :) + */ + + class SlideEffect : public Effect { Q_OBJECT @@ -58,40 +83,49 @@ public: private Q_SLOTS: void desktopChanged(int old, int current, EffectWindow *with); + void desktopChanging(uint old, QPointF desktopOffset, EffectWindow* with); + void desktopChangingCancelled(); void windowAdded(EffectWindow *w); void windowDeleted(EffectWindow *w); private: - QPoint desktopCoords(int id) const; - QRect desktopGeometry(int id) const; - int workspaceWidth() const; - int workspaceHeight() const; - + QPoint getDrawCoords(QPointF pos, EffectScreen *screen); bool isTranslated(const EffectWindow *w) const; bool isPainted(const EffectWindow *w) const; bool shouldElevate(const EffectWindow *w) const; + QPointF moveInsideDesktopGrid(QPointF p); + QPointF constrainToDrawableRange(QPointF p); + QPointF forcePositivePosition(QPointF p) const; + void optimizePath(); //Find the best path to target desktop - void start(int old, int current, EffectWindow *movingWindow = nullptr); - void stop(); + void startAnimation(int old, int current, EffectWindow *movingWindow = nullptr); + void finishedSwitching(); private: int m_hGap; int m_vGap; bool m_slideDocks; bool m_slideBackground; + int m_animationDuration; // Miliseconds for 1 complete desktop switch bool m_active = false; TimeLine m_timeLine; - QPoint m_startPos; - QPoint m_diff; + + // When the desktop isn't desktopChanging(), these two variables are used to control the animation path. + // They use desktops as a unit. + QPointF m_startPos; + QPointF m_endPos; + EffectWindow *m_movingWindow = nullptr; std::chrono::milliseconds m_lastPresentTime = std::chrono::milliseconds::zero(); + bool m_gestureActive = false; // If we're currently animating a gesture + QPointF m_currentPosition; // Should always be kept up to date with where on the grid we're seeing. struct { int desktop; bool firstPass; bool lastPass; - QPoint translation; + QPointF translation; //Uses desktops as units EffectWindowList fullscreenWindows; } m_paintCtx; @@ -101,7 +135,7 @@ private: inline int SlideEffect::duration() const { - return m_timeLine.duration().count(); + return m_animationDuration; } inline int SlideEffect::horizontalGap() const diff --git a/src/libkwineffects/kwineffects.h b/src/libkwineffects/kwineffects.h index 4bb5e2878e650c118deabc7a1b642be97278fd40..cc85970961771d1311cb118ab8ba2e11c11009fc 100644 --- a/src/libkwineffects/kwineffects.h +++ b/src/libkwineffects/kwineffects.h @@ -1468,6 +1468,17 @@ Q_SIGNALS: * @since 4.9 */ void desktopChanged(int oldDesktop, int newDesktop, KWin::EffectWindow *with); + + /** + * Signal emmitted while desktop is changing for animation. + * @param currentDesktop The current desktop untiotherwise. + * @param offset The current desktop offset. + * offset.x() = .6 means 60% of the way to the desktop to the right. + * Positive Values means Up and Right. + */ + void desktopChanging(uint currentDesktop, QPointF offset, KWin::EffectWindow *with); + void desktopChangingCancelled(); + /** * @since 4.7 * @deprecated diff --git a/src/virtualdesktops.cpp b/src/virtualdesktops.cpp index 5414afb830a59254c6be2a95af265bec7cf6c72f..d9d2be085f810bd71d6c84c0f58c8d0513b6c4a3 100644 --- a/src/virtualdesktops.cpp +++ b/src/virtualdesktops.cpp @@ -22,9 +22,11 @@ #include #include + namespace KWin { static bool s_loadingDesktopSettings = false; +static const double GESTURE_SWITCH_THRESHOLD = .25; static QString generateDesktopId() { @@ -535,7 +537,7 @@ VirtualDesktop *VirtualDesktopManager::currentDesktop() const bool VirtualDesktopManager::setCurrent(uint newDesktop) { - if (newDesktop < 1 || newDesktop > count() || newDesktop == current()) { + if (newDesktop < 1 || newDesktop > count()) { return false; } auto d = desktopForX11Id(newDesktop); @@ -800,9 +802,11 @@ void VirtualDesktopManager::initShortcuts() initSwitchToShortcuts(); QAction *nextAction = addAction(QStringLiteral("Switch to Next Desktop"), i18n("Switch to Next Desktop"), &VirtualDesktopManager::slotNext); - input()->registerTouchpadSwipeShortcut(SwipeDirection::Right, 4, nextAction); QAction *previousAction = addAction(QStringLiteral("Switch to Previous Desktop"), i18n("Switch to Previous Desktop"), &VirtualDesktopManager::slotPrevious); - input()->registerTouchpadSwipeShortcut(SwipeDirection::Left, 4, previousAction); + Q_UNUSED(nextAction) + Q_UNUSED(previousAction) + + //shortcuts QAction *slotRightAction = addAction(QStringLiteral("Switch One Desktop to the Right"), i18n("Switch One Desktop to the Right"), &VirtualDesktopManager::slotRight); KGlobalAccel::setGlobalShortcut(slotRightAction, QKeySequence(Qt::CTRL | Qt::META | Qt::Key_Right)); QAction *slotLeftAction = addAction(QStringLiteral("Switch One Desktop to the Left"), i18n("Switch One Desktop to the Left"), &VirtualDesktopManager::slotLeft); @@ -812,6 +816,39 @@ void VirtualDesktopManager::initShortcuts() QAction *slotDownAction = addAction(QStringLiteral("Switch One Desktop Down"), i18n("Switch One Desktop Down"), &VirtualDesktopManager::slotDown); KGlobalAccel::setGlobalShortcut(slotDownAction, QKeySequence(Qt::CTRL | Qt::META | Qt::Key_Down)); + // Gestures + // These connections decide which desktop to end on after gesture ends + connect(m_swipeGestureReleasedRight, &QAction::triggered, this, &VirtualDesktopManager::gestureReleasedRight); + connect(m_swipeGestureReleasedLeft, &QAction::triggered, this, &VirtualDesktopManager::gestureReleasedLeft); + connect(m_swipeGestureReleasedUp, &QAction::triggered, this, &VirtualDesktopManager::gestureReleasedUp); + connect(m_swipeGestureReleasedDown, &QAction::triggered, this, &VirtualDesktopManager::gestureReleasedDown); + + //These take the live feedback from a gesture + input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Left, 3, m_swipeGestureReleasedRight, [this](qreal cb) { + m_currentDesktopOffset.setX(cb); + Q_EMIT currentChanging(current(), m_currentDesktopOffset); + }); + input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Right, 3, m_swipeGestureReleasedLeft, [this](qreal cb) { + m_currentDesktopOffset.setX(-cb); + Q_EMIT currentChanging(current(), m_currentDesktopOffset); + }); + input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Left, 4, m_swipeGestureReleasedRight, [this](qreal cb) { + m_currentDesktopOffset.setX(cb); + Q_EMIT currentChanging(current(), m_currentDesktopOffset); + }); + input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Right, 4, m_swipeGestureReleasedLeft, [this](qreal cb) { + m_currentDesktopOffset.setX(-cb); + Q_EMIT currentChanging(current(), m_currentDesktopOffset); + }); + input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Down, 3, m_swipeGestureReleasedUp, [this](qreal cb) { + m_currentDesktopOffset.setY(-cb); + Q_EMIT currentChanging(current(), m_currentDesktopOffset); + }); + input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Up, 3, m_swipeGestureReleasedDown, [this](qreal cb) { + m_currentDesktopOffset.setY(cb); + Q_EMIT currentChanging(current(), m_currentDesktopOffset); + }); + // axis events input()->registerAxisShortcut(Qt::ControlModifier | Qt::AltModifier, PointerAxisDown, findChild(QStringLiteral("Switch to Next Desktop"))); @@ -819,6 +856,44 @@ void VirtualDesktopManager::initShortcuts() findChild(QStringLiteral("Switch to Previous Desktop"))); } +void VirtualDesktopManager::gestureReleasedUp() +{ + if (m_currentDesktopOffset.y() <= -GESTURE_SWITCH_THRESHOLD) { + slotUp(); + } else { + Q_EMIT currentChangingCancelled(); + } + m_currentDesktopOffset = QPointF(0, 0); +} +void VirtualDesktopManager::gestureReleasedDown() +{ + if (m_currentDesktopOffset.y() >= GESTURE_SWITCH_THRESHOLD) { + slotDown(); + } else { + Q_EMIT currentChangingCancelled(); + } + m_currentDesktopOffset = QPointF(0, 0); +} +void VirtualDesktopManager::gestureReleasedLeft() +{ + if (m_currentDesktopOffset.x() <= -GESTURE_SWITCH_THRESHOLD) { + slotLeft(); + } else { + Q_EMIT currentChangingCancelled(); + } + m_currentDesktopOffset = QPointF(0, 0); +} +void VirtualDesktopManager::gestureReleasedRight() +{ + if (m_currentDesktopOffset.x() >= GESTURE_SWITCH_THRESHOLD) { + slotRight(); + } else { + setCurrent(current()); + Q_EMIT currentChangingCancelled(); + } + m_currentDesktopOffset = QPointF(0, 0); +} + void VirtualDesktopManager::initSwitchToShortcuts() { const QString toDesktop = QStringLiteral("Switch to Desktop %1"); diff --git a/src/virtualdesktops.h b/src/virtualdesktops.h index 0a0cf051731ea6ae61243f34aaa8355c7345bec4..5246a0ebdd3d880437361ebcc642a75e3c05483f 100644 --- a/src/virtualdesktops.h +++ b/src/virtualdesktops.h @@ -16,6 +16,7 @@ #include #include #include +#include // KDE includes #include @@ -24,6 +25,7 @@ class KLocalizedString; class NETRootInfo; class QAction; +class Options; namespace KWaylandServer { @@ -390,6 +392,17 @@ Q_SIGNALS: * @param newDesktop The virtual desktop changed to */ void currentChanged(uint previousDesktop, uint newDesktop); + + /** + * Signal emmitted for realtime desktop switching animations. + * @param currentDesktop The current virtual desktop + * @param offset The current total change in desktop coordinate + * Offset x and y are negative if switching Left and Down. + * Example: x = 0.6 means 60% of the way to the desktop to the right. + */ + void currentChanging(uint currentDesktop, QPointF offset); + void currentChangingCancelled(); + /** * Signal emitted whenever the desktop layout changes. * @param columns The new number of columns in the layout @@ -433,6 +446,14 @@ private Q_SLOTS: */ void slotDown(); + /* For gestured desktopSwitching + * Called when gesture ended, the thing that actually switches the desktop. + */ + void gestureReleasedUp(); + void gestureReleasedDown(); + void gestureReleasedLeft(); + void gestureReleasedRight(); + private: /** * Generate a desktop layout from EWMH _NET_DESKTOP_LAYOUT property parameters. @@ -477,6 +498,12 @@ private: KWaylandServer::PlasmaVirtualDesktopManagementInterface *m_virtualDesktopManagement = nullptr; KSharedConfig::Ptr m_config; + QAction *m_swipeGestureReleasedUp = new QAction(); + QAction *m_swipeGestureReleasedDown = new QAction(); + QAction *m_swipeGestureReleasedLeft = new QAction(); + QAction *m_swipeGestureReleasedRight = new QAction(); + QPointF m_currentDesktopOffset = QPointF(0, 0); + KWIN_SINGLETON_VARIABLE(VirtualDesktopManager, s_manager) }; diff --git a/src/workspace.cpp b/src/workspace.cpp index 8d2eeadb5ab64525f2f3c9e373675466edc64a4c..d4c8699538b556c50f851c5316322c7805ab298b 100644 --- a/src/workspace.cpp +++ b/src/workspace.cpp @@ -236,6 +236,8 @@ void Workspace::init() connect(vds, &VirtualDesktopManager::desktopCreated, this, &Workspace::slotDesktopAdded); connect(vds, &VirtualDesktopManager::desktopRemoved, this, &Workspace::slotDesktopRemoved); connect(vds, &VirtualDesktopManager::currentChanged, this, &Workspace::slotCurrentDesktopChanged); + connect(vds, &VirtualDesktopManager::currentChanging, this, &Workspace::slotCurrentDesktopChanging); + connect(vds, &VirtualDesktopManager::currentChangingCancelled, this, &Workspace::slotCurrentDesktopChangingCancelled); vds->setNavigationWrappingAround(options->isRollOverDesktops()); connect(options, &Options::rollOverDesktopsChanged, vds, &VirtualDesktopManager::setNavigationWrappingAround); vds->setConfig(config); @@ -1022,6 +1024,17 @@ void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop) Q_EMIT currentDesktopChanged(oldDesktop, movingClient); } +void Workspace::slotCurrentDesktopChanging(uint currentDesktop, QPointF offset) +{ + closeActivePopup(); + Q_EMIT currentDesktopChanging(currentDesktop, offset, movingClient); +} + +void Workspace::slotCurrentDesktopChangingCancelled() +{ + Q_EMIT currentDesktopChangingCancelled(); +} + void Workspace::updateClientVisibilityOnDesktopChange(VirtualDesktop *newDesktop) { for (auto it = stacking_order.constBegin(); diff --git a/src/workspace.h b/src/workspace.h index ea232accdf4e650730a0dfc6789ddc80be30d99f..00ceb7a865cfb6169e7ea00500473cf514c78544 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -491,6 +491,8 @@ private Q_SLOTS: void updateCurrentActivity(const QString &new_activity); // virtual desktop handling void slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop); + void slotCurrentDesktopChanging(uint currentDesktop, QPointF delta); + void slotCurrentDesktopChangingCancelled(); void slotDesktopAdded(VirtualDesktop *desktop); void slotDesktopRemoved(VirtualDesktop *desktop); void slotOutputEnabled(AbstractOutput *output); @@ -508,6 +510,8 @@ Q_SIGNALS: void desktopPresenceChanged(KWin::AbstractClient*, int); void currentActivityChanged(); void currentDesktopChanged(int, KWin::AbstractClient*); + void currentDesktopChanging(uint currentDesktop, QPointF delta, KWin::AbstractClient*);//for realtime animations + void currentDesktopChangingCancelled(); void clientAdded(KWin::AbstractClient *); void clientRemoved(KWin::AbstractClient*); void clientActivated(KWin::AbstractClient*);