Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/libinputactions/config/parsers/triggers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ void NodeParser<std::unique_ptr<SwipeTriggerCore>>::parse(const Node *node, std:
loadSetter(swipeTriggerCore.get(), &SwipeTriggerCore::setBidirectional, node->at("bidirectional"));
result = std::move(swipeTriggerCore);
}
loadSetter(result.get(), &SwipeTriggerCore::setLockDirection, node->at("lock_direction"));
}

void finalizeTrigger(const Node *node, Trigger *trigger)
Expand Down
5 changes: 4 additions & 1 deletion src/libinputactions/triggers/Trigger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ bool Trigger::canActivate(const TriggerActivationEvent &event) const

bool Trigger::canUpdate(const TriggerUpdateEvent &event) const
{
return m_core->canUpdate(event);
return m_updatedDuringGesture ? m_core->canContinue(event) : m_core->canUpdate(event);
}

bool Trigger::endIfCannotUpdate() const
Expand Down Expand Up @@ -84,6 +84,7 @@ void Trigger::update(const TriggerUpdateEvent &event)
}
}

m_updatedDuringGesture = true;
setLastTrigger();
m_core->updateActions(m_actions, event);
}
Expand All @@ -101,6 +102,7 @@ void Trigger::end(bool allowResuming)
}

if (allowResuming && m_resumeTimeout.count()) {
m_updatedDuringGesture = false;
m_resumeTimeoutTimer.start(m_resumeTimeout.count());
return;
}
Expand Down Expand Up @@ -198,6 +200,7 @@ void Trigger::setLastTrigger()
void Trigger::reset()
{
m_started = false;
m_updatedDuringGesture = false;
m_absoluteAccumulatedDelta = 0;
m_withinThreshold = false;
m_tickTimer.stop();
Expand Down
5 changes: 5 additions & 0 deletions src/libinputactions/triggers/Trigger.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ private slots:

std::vector<std::unique_ptr<TriggerAction>> m_actions;
bool m_started = false;
/**
* Whether the trigger has been updated during the current gesture. Unlike m_started, cleared while waiting for the gesture to be resumed, so that the
* resumed gesture is checked by canUpdate as if it were new.
*/
bool m_updatedDuringGesture = false;
QTimer m_tickTimer;

bool m_withinThreshold = false;
Expand Down
5 changes: 5 additions & 0 deletions src/libinputactions/triggers/core/SwipeTriggerCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ bool SwipeTriggerCore::canUpdate(const TriggerUpdateEvent &event) const
return matchesAngleRange(angle, range.min, range.max) || (m_bidirectional && matchesOppositeAngleRange(angle, range.min, range.max));
}

bool SwipeTriggerCore::canContinue(const TriggerUpdateEvent &event) const
{
return m_lockDirection ? MotionTriggerCore::canUpdate(event) : canUpdate(event);
}

void SwipeTriggerCore::updateActions(const std::vector<std::unique_ptr<TriggerAction>> &actions, const TriggerUpdateEvent &event) const
{
auto newEvent = dynamic_cast<const SwipeTriggerUpdateEvent &>(event);
Expand Down
11 changes: 10 additions & 1 deletion src/libinputactions/triggers/core/SwipeTriggerCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ enum class SwipeDirection
* in DeviceProperties. Touchpads have 3 different thresholds, depending on the finger count.
*
* If swipe triggers are active and the motion angle changes, but none of the active triggers acccept it, they are cancelled and all swipe triggers are
* activated again, allowing for chaining multiple triggers together.
* activated again, allowing for chaining multiple triggers together. Started triggers with lockDirection set are not cancelled by angle changes.
*/
class SwipeTriggerCore : public MotionTriggerCore
{
Expand All @@ -115,7 +115,15 @@ class SwipeTriggerCore : public MotionTriggerCore
bool bidirectional() const { return m_bidirectional; }
void setBidirectional(bool value) { m_bidirectional = value; }

/**
* Whether the angle is only checked until the trigger starts. A started trigger then stays active regardless of direction changes, preventing chaining.
* Other conditions, such as speed, are still checked. A resumed gesture must match the angle again.
*/
bool lockDirection() const { return m_lockDirection; }
void setLockDirection(bool value) { m_lockDirection = value; }

bool canUpdate(const TriggerUpdateEvent &event) const override;
bool canContinue(const TriggerUpdateEvent &event) const override;
void updateActions(const std::vector<std::unique_ptr<TriggerAction>> &actions, const TriggerUpdateEvent &event) const override;

private:
Expand All @@ -139,6 +147,7 @@ class SwipeTriggerCore : public MotionTriggerCore
qreal m_minAngle{};
qreal m_maxAngle{};
bool m_bidirectional{};
bool m_lockDirection{};

friend class TestSwipeTriggerCore;
};
Expand Down
5 changes: 5 additions & 0 deletions src/libinputactions/triggers/core/TriggerCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ bool TriggerCore::canUpdate(const TriggerUpdateEvent &event) const
return true;
}

bool TriggerCore::canContinue(const TriggerUpdateEvent &event) const
{
return canUpdate(event);
}

void TriggerCore::updateActions(const std::vector<std::unique_ptr<TriggerAction>> &actions, const TriggerUpdateEvent &event) const
{
doUpdateActions(actions, event);
Expand Down
4 changes: 4 additions & 0 deletions src/libinputactions/triggers/core/TriggerCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class TriggerCore
virtual ~TriggerCore() = default;

virtual bool canUpdate(const TriggerUpdateEvent &event) const;
/**
* Called instead of canUpdate once the trigger has been updated during the current gesture. A resumed gesture is checked by canUpdate again.
*/
virtual bool canContinue(const TriggerUpdateEvent &event) const;
/**
* Override to modify the event before passing it to actions. Must be called by the overriding method.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ private slots:
QCOMPARE(core->minAngle(), 30);
QCOMPARE(core->maxAngle(), 60);
QCOMPARE(core->bidirectional(), false);
QCOMPARE(core->lockDirection(), false);
}

void angle_lockDirection__parsesNodeCorrectly()
{
const auto node = Node::create(R"(
angle: 30-60
lock_direction: true
)");

const auto core = node->as<std::unique_ptr<SwipeTriggerCore>>();
QCOMPARE(core->lockDirection(), true);
}

void angle_bidirectional__parsesNodeCorrectly()
Expand Down
96 changes: 96 additions & 0 deletions tests/libinputactions/handlers/TestMotionTriggerHandler.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "Test.h"
#include "mocks/MockMotionTriggerHandler.h"
#include <libinputactions/actions/CustomAction.h>
#include <libinputactions/actions/TriggerAction.h>
#include <libinputactions/handlers/TriggerHandler.h>
#include <libinputactions/helpers/Math.h>
#include <libinputactions/input/devices/InputDevice.h>
Expand All @@ -20,6 +22,76 @@ private slots:
m_handler = std::make_unique<MockMotionTriggerHandler>();
m_mouse = std::make_unique<InputDevice>(InputDeviceType::Mouse);
m_mouse->properties().setMotionThreshold(10);
m_rightBegins = 0;
m_leftBegins = 0;
}

void handleMotion_swipe_directionChanged__chainsUnlessLocked_data()
{
QTest::addColumn<bool>("lockDirection");
QTest::addColumn<uint32_t>("rightBegins");
QTest::addColumn<uint32_t>("leftBegins");

QTest::newRow("not locked") << false << 2u << 1u;
QTest::newRow("locked") << true << 1u << 0u;
}

void handleMotion_swipe_directionChanged__chainsUnlessLocked()
{
QFETCH(bool, lockDirection);
QFETCH(uint32_t, rightBegins);
QFETCH(uint32_t, leftBegins);

addSwipeTrigger(SwipeDirection::Right, lockDirection, m_rightBegins);
addSwipeTrigger(SwipeDirection::Left, false, m_leftBegins);

m_handler->activateTriggers(TriggerType::Swipe, TriggerActivationEvent{});
m_handler->handleMotion(m_mouse.get(), PointDelta({20, 0}));
m_handler->handleMotion(m_mouse.get(), PointDelta({-20, 0}));
m_handler->handleMotion(m_mouse.get(), PointDelta({20, 0}));

QCOMPARE(m_rightBegins, rightBegins);
QCOMPARE(m_leftBegins, leftBegins);
m_handler->cancelTriggers(TriggerType::Swipe);
}

void handleMotion_swipe_lockDirection_belowThreshold_directionChanged__activatesMatchingTrigger()
{
addSwipeTrigger(SwipeDirection::Right, true, m_rightBegins, {}, Range<qreal>(30, {}));
addSwipeTrigger(SwipeDirection::Left, false, m_leftBegins, {}, Range<qreal>(30, {}));

m_handler->activateTriggers(TriggerType::Swipe, TriggerActivationEvent{});
m_handler->handleMotion(m_mouse.get(), PointDelta({20, 0}));
m_handler->handleMotion(m_mouse.get(), PointDelta({-40, 0}));

QCOMPARE(m_rightBegins, 0u);
QCOMPARE(m_leftBegins, 1u);
m_handler->cancelTriggers(TriggerType::Swipe);
}

void handleMotion_swipe_lockDirection_nextGestureInOppositeDirection__activatesOtherTrigger_data()
{
QTest::addColumn<std::chrono::milliseconds>("resumeTimeout");

QTest::newRow("new gesture") << std::chrono::milliseconds(0);
QTest::newRow("resumed gesture") << std::chrono::milliseconds(30000);
}

void handleMotion_swipe_lockDirection_nextGestureInOppositeDirection__activatesOtherTrigger()
{
QFETCH(std::chrono::milliseconds, resumeTimeout);
addSwipeTrigger(SwipeDirection::Right, true, m_rightBegins, resumeTimeout);
addSwipeTrigger(SwipeDirection::Left, false, m_leftBegins);

m_handler->activateTriggers(TriggerType::Swipe, TriggerActivationEvent{});
m_handler->handleMotion(m_mouse.get(), PointDelta({20, 0}));
m_handler->endTriggers(TriggerType::Swipe);
m_handler->activateTriggers(TriggerType::Swipe, TriggerActivationEvent{});
m_handler->handleMotion(m_mouse.get(), PointDelta({-20, 0}));

QCOMPARE(m_rightBegins, 1u);
QCOMPARE(m_leftBegins, 1u);
m_handler->cancelTriggers(TriggerType::Swipe);
}

void handleMotion_swipe__calculatesAnglesCorrectly_data()
Expand Down Expand Up @@ -153,8 +225,32 @@ private slots:
}

private:
/**
* Adds a swipe trigger whose begin action increments the counter.
*/
void addSwipeTrigger(SwipeDirection direction, bool lockDirection, uint32_t &begins, std::chrono::milliseconds resumeTimeout = {},
std::optional<Range<qreal>> threshold = {})
{
auto core = std::make_unique<SwipeTriggerCore>(direction);
core->setLockDirection(lockDirection);
auto trigger = std::make_unique<Trigger>(TriggerType::Swipe, std::move(core));
trigger->setResumeTimeout(resumeTimeout);
if (threshold) {
trigger->setThreshold(*threshold);
}

auto action = std::make_unique<TriggerAction>(std::make_unique<CustomAction>([&begins](const auto &) {
begins++;
}));
action->setOn(On::Begin);
trigger->addAction(std::move(action));
m_handler->addTrigger(std::move(trigger));
}

std::unique_ptr<MockMotionTriggerHandler> m_handler;
std::unique_ptr<InputDevice> m_mouse;
uint32_t m_rightBegins{};
uint32_t m_leftBegins{};
};

}
Expand Down
14 changes: 14 additions & 0 deletions tests/libinputactions/triggers/core/TestSwipeTriggerCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ class TestSwipeTriggerCore : public Test
Q_OBJECT

private slots:

void canContinue_lockDirection_speedDoesNotMatch__returnsFalse()
{
SwipeTriggerCore core(SwipeDirection::Right);
core.setLockDirection(true);
core.setSpeed(TriggerSpeed::Fast);

SwipeTriggerUpdateEvent event;
event.setAverageAngle(90);
event.setSpeed(TriggerSpeed::Slow);

QCOMPARE(core.canContinue(event), false);
}

void canUpdate_currentAngleDoesNotMatchRange_averageAngleMatchesRange__returnsTrue()
{
SwipeTriggerCore core(30, 60);
Expand Down