diff --git a/autotests/test_gestures.cpp b/autotests/test_gestures.cpp index dc02818fd3..ebcd4d68f4 100644 --- a/autotests/test_gestures.cpp +++ b/autotests/test_gestures.cpp @@ -343,11 +343,11 @@ void GestureTest::testNotEmitCallbacksBeforeDirectionDecided() recognizer.registerPinchGesture(&expand); recognizer.registerPinchGesture(&contract); - QSignalSpy upSpy(&up, &SwipeGesture::progress); - QSignalSpy downSpy(&down, &SwipeGesture::progress); - QSignalSpy rightSpy(&right, &SwipeGesture::progress); - QSignalSpy expandSpy(&expand, &PinchGesture::progress); - QSignalSpy contractSpy(&contract, &PinchGesture::progress); + QSignalSpy upSpy(&up, &SwipeGesture::triggerProgress); + QSignalSpy downSpy(&down, &SwipeGesture::triggerProgress); + QSignalSpy rightSpy(&right, &SwipeGesture::triggerProgress); + QSignalSpy expandSpy(&expand, &PinchGesture::triggerProgress); + QSignalSpy contractSpy(&contract, &PinchGesture::triggerProgress); // don't release callback until we know the direction of swipe gesture recognizer.startSwipeGesture(4); diff --git a/src/gestures.cpp b/src/gestures.cpp index 8af734f036..3dc4956b22 100644 --- a/src/gestures.cpp +++ b/src/gestures.cpp @@ -41,24 +41,28 @@ void SwipeGesture::setStartGeometry(const QRect &geometry) Q_ASSERT(m_maximumY >= m_minimumY); } -qreal SwipeGesture::deltaToProgress(const QSizeF &delta) const +qreal SwipeGesture::getTriggerProgress(const QSizeF &delta) const { - if (!m_minimumDeltaRelevant || m_minimumDelta.isNull()) { + if (!m_triggerDeltaRelevant || m_triggerDelta.isNull()) { return 1.0; } - if (m_direction & (GestureDirection::Up | GestureDirection::Down)) { - return std::min(std::abs(delta.height()) / std::abs(m_minimumDelta.height()), 1.0); - } else if (m_direction & (GestureDirection::Left | GestureDirection::Right)) { - return std::min(std::abs(delta.width()) / std::abs(m_minimumDelta.width()), 1.0); + if (m_direction.testFlag(GestureDirection::DirectionlessSwipe)) { + return std::min(std::hypot(delta.width(), delta.height()) / m_triggerDelta.width(), 1.0); + } + + if (m_direction & GestureDirection::VerticalAxis) { + return std::min(std::abs(delta.height()) / std::abs(m_triggerDelta.height()), 1.0); + } else if (m_direction & GestureDirection::HorizontalAxis) { + return std::min(std::abs(delta.width()) / std::abs(m_triggerDelta.width()), 1.0); } return 1.0; } -bool SwipeGesture::minimumDeltaReached(const QSizeF &delta) const +bool SwipeGesture::triggerDeltaReached(const QSizeF &delta) const { - return deltaToProgress(delta) >= 1.0; + return getTriggerProgress(delta) >= 1.0; } PinchGesture::PinchGesture(QObject *parent) @@ -68,14 +72,14 @@ PinchGesture::PinchGesture(QObject *parent) PinchGesture::~PinchGesture() = default; -qreal PinchGesture::scaleDeltaToProgress(const qreal &scaleDelta) const +qreal PinchGesture::getTriggerProgress(const qreal &scaleDelta) const { - return std::clamp(std::abs(scaleDelta - 1) / minimumScaleDelta(), 0.0, 1.0); + return std::clamp(std::abs(scaleDelta - 1) / triggerScaleDelta(), 0.0, 1.0); } -bool PinchGesture::minimumScaleDeltaReached(const qreal &scaleDelta) const +bool PinchGesture::triggerScaleDeltaReached(const qreal &scaleDelta) const { - return scaleDeltaToProgress(scaleDelta) >= 1.0; + return getTriggerProgress(scaleDelta) >= 1.0; } GestureRecognizer::GestureRecognizer(QObject *parent) @@ -163,11 +167,12 @@ int GestureRecognizer::startSwipeGesture(uint fingerCount, const QPointF &startP } // Only add gestures who's direction aligns with current swipe axis - if (gesture->direction() & (GestureDirection::Up | GestureDirection::Down)) { + if (gesture->direction().testFlag(GestureDirection::DirectionlessSwipe)) { + } else if (gesture->direction() & GestureDirection::VerticalAxis) { if (m_currentSwipeAxis == Axis::Horizontal) { continue; } - } else if (gesture->direction() & (GestureDirection::Left | GestureDirection::Right)) { + } else if (gesture->direction() & GestureDirection::HorizontalAxis) { if (m_currentSwipeAxis == Axis::Vertical) { continue; } @@ -242,11 +247,39 @@ void GestureRecognizer::updateSwipeGesture(const QSizeF &delta) // Send progress update for (SwipeGesture *g : std::as_const(m_activeSwipeGestures)) { - Q_EMIT g->progress(g->deltaToProgress(m_currentDelta)); - Q_EMIT g->deltaProgress(m_currentDelta); + Q_EMIT g->triggerProgress(g->getTriggerProgress(m_currentDelta)); + Q_EMIT g->semanticProgress(g->getSemanticProgress(m_currentDelta), g->direction()); + Q_EMIT g->pixelDelta(m_currentDelta, g->direction()); + Q_EMIT g->semanticDelta(g->getSemanticDelta(m_currentDelta), g->direction()); + if (!g->direction().testFlag(GestureDirection::DirectionlessSwipe)) { + Q_EMIT g->semanticProgressAxis(g->getSemanticAxisProgress(m_currentDelta), g->direction()); + } + Q_EMIT g->swipePixelVector(QVector2D(m_currentDelta.width(), m_currentDelta.height())); } } +bool GestureRecognizer::mutuallyExclusive(GestureDirections currentDir, GestureDirections gestureDir) +{ + if (currentDir == gestureDir) { + return false; + } + if (gestureDir.testFlag(GestureDirection::DirectionlessSwipe)) { + return false; + } + + if (currentDir & GestureDirection::VerticalAxis) { + if (gestureDir.testFlag(GestureDirection::VerticalAxis)) { + return false; + } + } else if (currentDir & GestureDirection::HorizontalAxis) { + if (gestureDir.testFlag(GestureDirection::HorizontalAxis)) { + return false; + } + } + + return true; +} + void GestureRecognizer::cancelActiveGestures() { for (auto g : qAsConst(m_activeSwipeGestures)) { @@ -274,7 +307,7 @@ void GestureRecognizer::endSwipeGesture() { const QSizeF delta = m_currentDelta; for (auto g : qAsConst(m_activeSwipeGestures)) { - if (static_cast(g)->minimumDeltaReached(delta)) { + if (static_cast(g)->triggerDeltaReached(delta)) { Q_EMIT g->triggered(); } else { Q_EMIT g->cancelled(); @@ -339,7 +372,9 @@ void GestureRecognizer::updatePinchGesture(qreal scale, qreal angleDelta, const } for (PinchGesture *g : std::as_const(m_activePinchGestures)) { - Q_EMIT g->progress(g->scaleDeltaToProgress(scale)); + Q_EMIT g->triggerProgress(g->getTriggerProgress(scale)); + Q_EMIT g->semanticProgress(g->getSemanticProgress(scale), g->direction()); + Q_EMIT g->semanticProgressAxis(g->getSemanticAxisProgress(scale), g->direction()); } } @@ -354,7 +389,7 @@ void GestureRecognizer::cancelPinchGesture() void GestureRecognizer::endPinchGesture() // because fingers up { for (auto g : qAsConst(m_activePinchGestures)) { - if (g->minimumScaleDeltaReached(m_currentScale)) { + if (g->triggerScaleDeltaReached(m_currentScale)) { Q_EMIT g->triggered(); } else { Q_EMIT g->cancelled(); @@ -431,36 +466,36 @@ bool SwipeGesture::maximumYIsRelevant() const return m_maximumYRelevant; } -QSizeF SwipeGesture::minimumDelta() const +QSizeF SwipeGesture::triggerDelta() const { - return m_minimumDelta; + return m_triggerDelta; } -void SwipeGesture::setMinimumDelta(const QSizeF &delta) +void SwipeGesture::setTriggerDelta(const QSizeF &delta) { - m_minimumDelta = delta; - m_minimumDeltaRelevant = true; + m_triggerDelta = delta; + m_triggerDeltaRelevant = true; } -bool SwipeGesture::isMinimumDeltaRelevant() const +bool SwipeGesture::isTriggerDeltaRelevant() const { - return m_minimumDeltaRelevant; + return m_triggerDeltaRelevant; } -qreal PinchGesture::minimumScaleDelta() const +qreal PinchGesture::triggerScaleDelta() const { - return m_minimumScaleDelta; + return m_triggerScaleDelta; } -void PinchGesture::setMinimumScaleDelta(const qreal &scaleDelta) +void PinchGesture::setTriggerScaleDelta(const qreal &scaleDelta) { - m_minimumScaleDelta = scaleDelta; - m_minimumScaleDeltaRelevant = true; + m_triggerScaleDelta = scaleDelta; + m_triggerScaleDeltaRelevant = true; } -bool PinchGesture::isMinimumScaleDeltaRelevant() const +bool PinchGesture::isTriggerScaleDeltaRelevant() const { - return m_minimumScaleDeltaRelevant; + return m_triggerScaleDeltaRelevant; } int GestureRecognizer::startSwipeGesture(uint fingerCount) @@ -468,6 +503,48 @@ int GestureRecognizer::startSwipeGesture(uint fingerCount) return startSwipeGesture(fingerCount, QPointF(), StartPositionBehavior::Irrelevant); } +QSizeF SwipeGesture::getSemanticDelta(const QSizeF &delta) const +{ + QSizeF d = QSizeF(); + d.setWidth(delta.width() / m_unitDelta); + d.setHeight(delta.height() / m_unitDelta); + return d; +} + +qreal SwipeGesture::getSemanticProgress(const QSizeF &delta) const +{ + if (m_direction.testFlag(GestureDirection::DirectionlessSwipe)) { + return std::hypot(delta.width(), delta.height()) / m_unitDelta; + } else if (m_direction & GestureDirection::VerticalAxis) { + return std::abs(delta.height()) / m_unitDelta; + } else if (m_direction & GestureDirection::HorizontalAxis) { + return std::abs(delta.width()) / m_unitDelta; + } + + return 1.0; +} + +qreal PinchGesture::getSemanticProgress(const qreal scale) const +{ + return std::max(std::abs(1 - scale) / m_unitScaleDelta, 0.0); +} + +qreal SwipeGesture::getSemanticAxisProgress(const QSizeF &delta) const +{ + if (m_direction & GestureDirection::VerticalAxis) { + return delta.height() / m_unitDelta; + } else if (m_direction & GestureDirection::HorizontalAxis) { + return delta.width() / m_unitDelta; + } + + return 1.0; +} + +qreal PinchGesture::getSemanticAxisProgress(const qreal scale) const +{ + return (scale - 1) / m_unitScaleDelta; +} + int GestureRecognizer::startSwipeGesture(const QPointF &startPos) { return startSwipeGesture(1, startPos, StartPositionBehavior::Relevant); diff --git a/src/gestures.h b/src/gestures.h index d70b42277e..2070585e5e 100644 --- a/src/gestures.h +++ b/src/gestures.h @@ -17,16 +17,17 @@ #include #include #include +#include #include namespace KWin { static const QSet DEFAULT_VALID_FINGER_COUNTS = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; -/* - * Everytime the scale of the gesture changes by this much, the callback changes by 1. +/** * This is the amount of change for 1 unit of change, like switch by 1 desktop. - * */ + */ +static const qreal DEFAULT_UNIT_DELTA = 400; // Pixels static const qreal DEFAULT_UNIT_SCALE_DELTA = .2; // 20% class Gesture : public QObject @@ -72,6 +73,24 @@ Q_SIGNALS: * This Gesture no longer matches. */ void cancelled(); + /** + * Progress towards the minimum threshold to trigger + */ + void triggerProgress(qreal); + /** + * The progress of the gesture if a minimumDelta is set. + * The progress is reported in [0.0,1.0+] + * Progress is always positive + * It can be more than 1, indicating an action should happen more than once. + */ + void semanticProgress(qreal, GestureDirections); + /** + * Like semantic progress except [-1, 1] and + * it captures both of something + * example: Up and Down (VerticalAxis), Contracting and Expanding (BiDirectionalPinch) + * Positive values are Up, Right and Expanding + */ + void semanticProgressAxis(qreal, GestureDirections); private: QSet m_validFingerCounts = DEFAULT_VALID_FINGER_COUNTS; @@ -99,24 +118,51 @@ public: bool maximumYIsRelevant() const; void setStartGeometry(const QRect &geometry); - QSizeF minimumDelta() const; - void setMinimumDelta(const QSizeF &delta); - bool isMinimumDeltaRelevant() const; + QSizeF triggerDelta() const; + void setTriggerDelta(const QSizeF &delta); + bool isTriggerDeltaRelevant() const; - qreal deltaToProgress(const QSizeF &delta) const; - bool minimumDeltaReached(const QSizeF &delta) const; + qreal getTriggerProgress(const QSizeF &delta) const; + bool triggerDeltaReached(const QSizeF &delta) const; + + /** + * Take the given pixel delta and + * map it to a simple [0, 1+] semantic scale. + * 0 = no progress + * 1 = complete something once + * The value can be greater than 1, indicating + * that the action should be done more times. + */ + qreal getSemanticProgress(const QSizeF &delta) const; + /** + * Like the last one, except [-1, 1] + * Positive values are Up and Right + */ + qreal getSemanticAxisProgress(const QSizeF &delta) const; + /** + * A two dimensional semantic delta. + * [-1, 1] on each axis. + * Positive is Up and Right + */ + QSizeF getSemanticDelta(const QSizeF &delta) const; Q_SIGNALS: /** - * The progress of the gesture if a minimumDelta is set. - * The progress is reported in [0.0,1.0] + * Summative pixel delta from where the gesture + * started to where it is now. */ - void progress(qreal); - + void pixelDelta(const QSizeF &delta, GestureDirections); /** - * The progress in actual pixel distance traveled by the fingers + * A 2d coordinate giving the semantic axis delta + * [-1, 1] on both horizontal and vertical axes. */ - void deltaProgress(const QSizeF &delta); + void semanticDelta(const QSizeF &delta, GestureDirections); + /** + * GIves a 2d vector of pointing from + * where the gesture started to where + * it is now. + */ + void swipePixelVector(const QVector2D &vector); private: bool m_minimumXRelevant = false; @@ -127,8 +173,9 @@ private: int m_maximumX = 0; bool m_maximumYRelevant = false; int m_maximumY = 0; - bool m_minimumDeltaRelevant = false; - QSizeF m_minimumDelta; + bool m_triggerDeltaRelevant = false; + QSizeF m_triggerDelta; + qreal m_unitDelta = DEFAULT_UNIT_DELTA; }; class PinchGesture : public Gesture @@ -138,28 +185,44 @@ public: explicit PinchGesture(QObject *parent = nullptr); ~PinchGesture() override; - qreal minimumScaleDelta() const; + qreal triggerScaleDelta() const; /** * scaleDelta is the % scale difference needed to trigger * 0.25 will trigger when scale reaches 0.75 or 1.25 */ - void setMinimumScaleDelta(const qreal &scaleDelta); - bool isMinimumScaleDeltaRelevant() const; + void setTriggerScaleDelta(const qreal &scaleDelta); + bool isTriggerScaleDeltaRelevant() const; - qreal scaleDeltaToProgress(const qreal &scaleDelta) const; - bool minimumScaleDeltaReached(const qreal &scaleDelta) const; + qreal getTriggerProgress(const qreal &scaleDelta) const; + bool triggerScaleDeltaReached(const qreal &scaleDelta) const; + + /** + * Take the given pixel delta and + * map it to a simple [0, 1+] semantic scale. + * 0 = no progress + * 1 = complete something once + * The value can be greater than 1, indicating + * that the action should be done more times. + */ + qreal getSemanticProgress(const qreal scale) const; + /** + * Like the last one, except [-1, 1] + * Positive is expanding. + * Positive values are Expanding + */ + qreal getSemanticAxisProgress(const qreal scale) const; Q_SIGNALS: /** - * The progress of the gesture if a minimumDelta is set. * The progress is reported in [0.0,1.0] */ - void progress(qreal); + void triggerProgress(qreal); private: - bool m_minimumScaleDeltaRelevant = false; - qreal m_minimumScaleDelta = DEFAULT_UNIT_SCALE_DELTA; + bool m_triggerScaleDeltaRelevant = false; + qreal m_triggerScaleDelta = .2; + qreal m_unitScaleDelta = DEFAULT_UNIT_SCALE_DELTA; }; class KWIN_EXPORT GestureRecognizer : public QObject @@ -188,6 +251,7 @@ public: private: void cancelActiveGestures(); + bool mutuallyExclusive(GestureDirections d, GestureDirections gestureDir); enum class StartPositionBehavior { Relevant, Irrelevant, diff --git a/src/globalshortcuts.cpp b/src/globalshortcuts.cpp index 19767fa683..273d0c77c9 100644 --- a/src/globalshortcuts.cpp +++ b/src/globalshortcuts.cpp @@ -24,6 +24,8 @@ #include #include +#include + namespace KWin { GlobalShortcut::GlobalShortcut(Shortcut &&sc, QAction *action) @@ -121,22 +123,22 @@ void GlobalShortcutsManager::registerAxisShortcut(QAction *action, Qt::KeyboardM addIfNotExists(GlobalShortcut(PointerAxisShortcut{modifiers, axis}, action)); } -void GlobalShortcutsManager::registerGesture(GestureDeviceType device, GestureDirection direction, uint fingerCount, QAction *onUp, std::function progressCallback) +void GlobalShortcutsManager::registerGesture(GestureDeviceType device, GestureDirections direction, uint fingerCount, QAction *onUp, std::function progressCallback) { // Create and setup the GestureShortcut GestureShortcut shortcut{device, direction}; if (isSwipeDirection(direction)) { std::unique_ptr gesture = std::make_unique(); gesture->addFingerCount(fingerCount); - gesture->setMinimumDelta(QSizeF(200, 200)); - connect(gesture.get(), &SwipeGesture::progress, progressCallback); + gesture->setTriggerDelta(QSizeF(200, 200)); + connect(gesture.get(), &SwipeGesture::triggerProgress, progressCallback); connect(gesture.get(), &Gesture::triggered, onUp, &QAction::trigger, Qt::QueuedConnection); connect(gesture.get(), &Gesture::cancelled, onUp, &QAction::trigger, Qt::QueuedConnection); shortcut.swipeGesture = std::move(gesture); } else if (isPinchDirection(direction)) { std::unique_ptr gesture = std::make_unique(); gesture->addFingerCount(fingerCount); - connect(gesture.get(), &PinchGesture::progress, progressCallback); + connect(gesture.get(), &PinchGesture::triggerProgress, progressCallback); connect(gesture.get(), &Gesture::triggered, onUp, &QAction::trigger, Qt::QueuedConnection); connect(gesture.get(), &Gesture::cancelled, onUp, &QAction::trigger, Qt::QueuedConnection); shortcut.pinchGesture = std::move(gesture); @@ -151,19 +153,21 @@ void GlobalShortcutsManager::forceRegisterTouchscreenSwipe(QAction *onUp, std::f std::unique_ptr gesture = std::make_unique(); gesture->addFingerCount(fingerCount); gesture->setDirection(direction); - gesture->setMinimumDelta(QSizeF(200, 200)); - connect(gesture.get(), &SwipeGesture::progress, progressCallback); + gesture->setTriggerDelta(QSizeF(200, 200)); + connect(gesture.get(), &SwipeGesture::triggerProgress, progressCallback); connect(gesture.get(), &Gesture::triggered, onUp, &QAction::trigger, Qt::QueuedConnection); connect(gesture.get(), &Gesture::cancelled, onUp, &QAction::trigger, Qt::QueuedConnection); + GestureShortcut gestureShortcut{GestureDeviceType::Touchscreen, direction}; + gestureShortcut.swipeGesture = std::move(gesture); + m_touchscreenGestureRecognizer->registerSwipeGesture(gestureShortcut.swipeGesture.get()); - GlobalShortcut shortcut{GestureShortcut{GestureDeviceType::Touchscreen, direction, std::move(gesture)}, onUp}; + GlobalShortcut shortcut{std::move(gestureShortcut), onUp}; const auto it = std::find_if(m_shortcuts.begin(), m_shortcuts.end(), [&shortcut](const auto &s) { return shortcut.shortcut() == s.shortcut(); }); if (it != m_shortcuts.end()) { m_shortcuts.erase(it); } - m_touchscreenGestureRecognizer->registerSwipeGesture(gesture.get()); connect(shortcut.action(), &QAction::destroyed, this, &GlobalShortcutsManager::objectDeleted); m_shortcuts.push_back(std::move(shortcut)); } diff --git a/src/globalshortcuts.h b/src/globalshortcuts.h index ad2138726e..5f6b432fae 100644 --- a/src/globalshortcuts.h +++ b/src/globalshortcuts.h @@ -15,6 +15,7 @@ #include #include +#include class QAction; class KGlobalAccelD; @@ -58,7 +59,7 @@ public: */ void registerAxisShortcut(QAction *action, Qt::KeyboardModifiers modifiers, PointerAxisDirection axis); - void registerGesture(GestureDeviceType device, GestureDirection direction, uint fingerCount, QAction *onUp, std::function progressCallback = nullptr); + void registerGesture(GestureDeviceType device, GestureDirections direction, uint fingerCount, QAction *onUp, std::function progressCallback = nullptr); void forceRegisterTouchscreenSwipe(QAction *action, std::function progressCallback, GestureDirection direction, uint fingerCount); diff --git a/src/libkwineffects/kwinglobals.h b/src/libkwineffects/kwinglobals.h index 10e56d5c44..89133d4cb5 100644 --- a/src/libkwineffects/kwinglobals.h +++ b/src/libkwineffects/kwinglobals.h @@ -232,6 +232,10 @@ enum class GestureDirection { Right = 1 << 4, Expanding = 1 << 5, Contracting = 1 << 6, + VerticalAxis = Up | Down, // Up is positive values + HorizontalAxis = Left | Right, // Right is positive + DirectionlessSwipe = Left | Right | Up | Down, // Positive is Up/Right + BiDirectionalPinch = Expanding | Contracting, // Positive is Expanding }; Q_DECLARE_FLAGS(GestureDirections, GestureDirection) diff --git a/src/screenedge.cpp b/src/screenedge.cpp index 21d552caa5..000531ac4a 100644 --- a/src/screenedge.cpp +++ b/src/screenedge.cpp @@ -121,14 +121,14 @@ Edge::Edge(ScreenEdges *parent) handleTouchCallback(); } }); - connect(m_gesture, &SwipeGesture::progress, this, [this](qreal progress) { + connect(m_gesture, &SwipeGesture::triggerProgress, this, [this](qreal progress) { int factor = progress * 256.0f; if (m_lastApproachingFactor != factor) { m_lastApproachingFactor = factor; Q_EMIT approaching(border(), m_lastApproachingFactor / 256.0f, m_approachGeometry); } }); - connect(m_gesture, &SwipeGesture::deltaProgress, this, [this](const QSizeF &progressDelta) { + connect(m_gesture, &SwipeGesture::pixelDelta, this, [this](const QSizeF &progressDelta) { if (!m_touchCallbacks.isEmpty()) { m_touchCallbacks.constFirst().progressCallback(border(), progressDelta, m_output); } @@ -569,7 +569,7 @@ void Edge::setGeometry(const QRect &geometry) if (isScreenEdge()) { const Output *output = workspace()->outputAt(m_geometry.center()); m_gesture->setStartGeometry(m_geometry); - m_gesture->setMinimumDelta(QSizeF(MINIMUM_DELTA, MINIMUM_DELTA) / output->scale()); + m_gesture->setTriggerDelta(QSizeF(MINIMUM_DELTA, MINIMUM_DELTA) / output->scale()); } }