Commit bc15b72e authored by Eric Edlund's avatar Eric Edlund
Browse files

Add new gestures and improve naming clarity in gesture.h

I've added VerticalAxis, HorizontalAxis, DirectionlessSwipe and BiDirectionalPinch gestures directions.
These are all combinations of other gesture directions that semantically work well together.
I've implemented these gestures as well as changed some labels and improved documentation,

Also,
Add vector signal to SwipeGesture
parent 634182d4
......@@ -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);
......
......@@ -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<SwipeGesture *>(g)->minimumDeltaReached(delta)) {
if (static_cast<SwipeGesture *>(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);
......
......@@ -17,16 +17,17 @@
#include <QPointF>
#include <QSet>
#include <QSizeF>
#include <QVector2D>
#include <QVector>
namespace KWin
{
static const QSet<uint> 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<uint> 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;
Q_SIGNALS:
/**
* The progress of the gesture if a minimumDelta is set.
* The progress is reported in [0.0,1.0]
* 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
*/
void progress(qreal);
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:
/**
* Summative pixel delta from where the gesture
* started to where it is now.
*/
void pixelDelta(const QSizeF &delta, GestureDirections);
/**
* A 2d coordinate giving the semantic axis delta
* [-1, 1] on both horizontal and vertical axes.
*/
void semanticDelta(const QSizeF &delta, GestureDirections);
/**
* The progress in actual pixel distance traveled by the fingers
* GIves a 2d vector of pointing from
* where the gesture started to where
* it is now.
*/
void deltaProgress(const QSizeF &delta);
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,
......
......@@ -24,6 +24,8 @@
#include <signal.h>
#include <variant>
#include <iostream>
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<void(qreal)> progressCallback)
void GlobalShortcutsManager::registerGesture(GestureDeviceType device, GestureDirections direction, uint fingerCount, QAction *onUp, std::function<void(qreal)> progressCallback)
{
// Create and setup the GestureShortcut
GestureShortcut shortcut{device, direction};
if (isSwipeDirection(direction)) {
std::unique_ptr<SwipeGesture> gesture = std::make_unique<SwipeGesture>();
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<PinchGesture> gesture = std::make_unique<PinchGesture>();
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<SwipeGesture> gesture = std::make_unique<SwipeGesture>();
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));
}
......
......@@ -15,6 +15,7 @@
#include <QKeySequence>
#include <memory>
#include <vector>
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<void(qreal)> progressCallback = nullptr);
void registerGesture(GestureDeviceType device, GestureDirections direction, uint fingerCount, QAction *onUp, std::function<void(qreal)> progressCallback = nullptr);
void forceRegisterTouchscreenSwipe(QAction *action, std::function<void(qreal)> progressCallback, GestureDirection direction, uint fingerCount);
......
......@@ -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)
......
......@@ -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());
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment