From b0ca81b273c7958feb47192b0f74f9f53c7729a2 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Mon, 27 Apr 2026 14:58:35 +0100 Subject: [PATCH 1/4] gestures: add scroll_move --- src/config/legacy/ConfigManager.cpp | 5 +- .../lua/bindings/LuaBindingsConfigRules.cpp | 7 +- src/config/values/ConfigValues.cpp | 2 +- .../tiled/scrolling/ScrollTapeController.cpp | 6 +- .../tiled/scrolling/ScrollingAlgorithm.cpp | 138 ++++++++++++++++++ .../tiled/scrolling/ScrollingAlgorithm.hpp | 10 ++ .../trackpad/gestures/ScrollMoveGesture.cpp | 122 ++++++++++++++++ .../trackpad/gestures/ScrollMoveGesture.hpp | 21 +++ 8 files changed, 304 insertions(+), 7 deletions(-) create mode 100644 src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp create mode 100644 src/managers/input/trackpad/gestures/ScrollMoveGesture.hpp diff --git a/src/config/legacy/ConfigManager.cpp b/src/config/legacy/ConfigManager.cpp index 2705c3959..12cb03974 100644 --- a/src/config/legacy/ConfigManager.cpp +++ b/src/config/legacy/ConfigManager.cpp @@ -57,6 +57,7 @@ #include "../../managers/input/trackpad/gestures/FloatGesture.hpp" #include "../../managers/input/trackpad/gestures/FullscreenGesture.hpp" #include "../../managers/input/trackpad/gestures/CursorZoomGesture.hpp" +#include "../../managers/input/trackpad/gestures/ScrollMoveGesture.hpp" #include "../../event/EventBus.hpp" @@ -1974,7 +1975,9 @@ std::optional CConfigManager::handleGesture(const std::string& comm else if (data[startDataIdx] == "cursorZoom") { result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}, std::string{data[startDataIdx + 2]}), fingerCount, direction, modMask, deltaScale, disableInhibit); - } else if (data[startDataIdx] == "unset") + } else if (data[startDataIdx] == "scrollMove") + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); + else if (data[startDataIdx] == "unset") result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale, disableInhibit); else return std::format("Invalid gesture: {}", data[startDataIdx]); diff --git a/src/config/lua/bindings/LuaBindingsConfigRules.cpp b/src/config/lua/bindings/LuaBindingsConfigRules.cpp index 14e172649..8889c407f 100644 --- a/src/config/lua/bindings/LuaBindingsConfigRules.cpp +++ b/src/config/lua/bindings/LuaBindingsConfigRules.cpp @@ -38,6 +38,7 @@ #include "../../../managers/input/trackpad/gestures/ResizeGesture.hpp" #include "../../../managers/input/trackpad/gestures/SpecialWorkspaceGesture.hpp" #include "../../../managers/input/trackpad/gestures/WorkspaceSwipeGesture.hpp" +#include "../../../managers/input/trackpad/gestures/ScrollMoveGesture.hpp" #include "../../../managers/permissions/DynamicPermissionManager.hpp" #include @@ -720,7 +721,7 @@ static int hlWorkspaceRule(lua_State* L) { static int hlGesture(lua_State* L) { if (!lua_istable(L, 1)) - return Internal::configError(L, "hl.gesture: expected a table, e.g. { fingers = 3, direction = \"horizontal\", action = \"workspace\" }"); + return Internal::configError(L, R"(hl.gesture: expected a table, e.g. { fingers = 3, direction = "horizontal", action = "workspace" })"); CLuaConfigInt fingersParser(0, 2, 9); auto fingersErr = Internal::parseTableField(L, 1, "fingers", fingersParser); @@ -841,8 +842,10 @@ static int hlGesture(lua_State* L) { result = g_pTrackpadGestures->addGesture(makeUnique(mode), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (action == "fullscreen") result = g_pTrackpadGestures->addGesture(makeUnique(mode), fingerCount, direction, modMask, deltaScale, disableInhibit); - else if (action == "cursorZoom") + else if (action == "cursor_zoom" || action == "cursorZoom") result = g_pTrackpadGestures->addGesture(makeUnique(zoomLevel, mode), fingerCount, direction, modMask, deltaScale, disableInhibit); + else if (action == "scroll_move") + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (action == "unset") result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale, disableInhibit); else diff --git a/src/config/values/ConfigValues.cpp b/src/config/values/ConfigValues.cpp index 0214ba86c..bd69781dc 100644 --- a/src/config/values/ConfigValues.cpp +++ b/src/config/values/ConfigValues.cpp @@ -376,7 +376,7 @@ std::vector> Values::getConfigValues() { MS("gestures:workspace_swipe_forever", "if enabled, swiping will not clamp at the neighboring workspaces but continue to the further ones.", false), MS("gestures:workspace_swipe_use_r", "if enabled, swiping will use the r prefix instead of the m prefix for finding workspaces.", false), MS("gestures:close_max_timeout", "Timeout for closing windows with the close gesture, in ms.", 1000, {.min = 10, .max = 2000}), - + MS("gestures:scrolling:move_snap_to_grid", "When releasing the scroll move gesture, whether it shoud try to snap to the grid.", true), /* * group: */ diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp index 657b65426..032c085f9 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp @@ -274,14 +274,14 @@ size_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool full if (m_strips.empty()) return 0; - const double usablePrimary = getPrimary(usableArea.size()); - double currentPos = m_offset; + const double viewportCenter = m_offset + getPrimary(usableArea.size()) / 2.0; + double currentPos = 0.0; for (size_t i = 0; i < m_strips.size(); ++i) { const double stripSize = calculateStripSize(i, usableArea, fullscreenOnOne); currentPos += stripSize; - if (currentPos >= usablePrimary / 2.0 - 2.0) + if (currentPos >= viewportCenter - 2.0) return i; } diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp index 28941109d..9e8f1d710 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp @@ -1497,6 +1497,144 @@ Config::ErrorResult CScrollingAlgorithm::layoutMsg(const std::string_view& sv) { return {}; } +void CScrollingAlgorithm::moveTape(float delta) { + if (delta == 0.F) + return; + + m_scrollingData->controller->adjustOffset(-delta); + m_scrollingData->recalculate(); +} + +void CScrollingAlgorithm::moveTapeNormalized(double delta) { + const double primary = primaryViewportSize(); + if (primary <= 0.0 || delta == 0.0) + return; + + moveTape(delta * primary); +} + +void CScrollingAlgorithm::snapToGrid() { + snapToProjectedOffset(normalizedTapeOffset()); +} + +SP CScrollingAlgorithm::snapToProjectedOffset(double projectedNormalizedOffset) { + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + static const auto PFITMETHOD = CConfigValue("scrolling:focus_fit_method"); + + const auto USABLE = usableArea(); + auto& controller = *m_scrollingData->controller; + + if (controller.stripCount() == 0) + return nullptr; + + const double usablePrimary = controller.isPrimaryHorizontal() ? USABLE.w : USABLE.h; + if (usablePrimary <= 0.0) + return nullptr; + + const double maxExtent = controller.calculateMaxExtent(USABLE, *PFSONONE); + if (maxExtent <= 0.0) + return nullptr; + + const double projectedOffset = projectedNormalizedOffset * usablePrimary; + + double bestOffset = 0.0; + double bestDelta = 0.0; + double bestCenterDelta = 0.0; + size_t bestIndex = 0; + bool foundSnap = false; + + auto centerOffsetFor = [&](size_t index) { + const double start = controller.calculateStripStart(index, USABLE, *PFSONONE); + const double size = controller.calculateStripSize(index, USABLE, *PFSONONE); + + return start - (usablePrimary - size) / 2.0; + }; + + auto fitOffsetFor = [&](size_t index) { + const double start = controller.calculateStripStart(index, USABLE, *PFSONONE); + const double size = controller.calculateStripSize(index, USABLE, *PFSONONE); + const double lo = start - usablePrimary + size; + const double hi = start; + + if (lo > hi) + return centerOffsetFor(index); + + return std::clamp(projectedOffset, lo, hi); + }; + + auto considerColumn = [&](size_t index) { + const double offset = *PFITMETHOD == 1 ? fitOffsetFor(index) : centerOffsetFor(index); + const double delta = std::abs(offset - projectedOffset); + const double start = controller.calculateStripStart(index, USABLE, *PFSONONE); + const double size = controller.calculateStripSize(index, USABLE, *PFSONONE); + const double centerDelta = std::abs((start + size / 2.0) - (projectedOffset + usablePrimary / 2.0)); + const bool betterFit = delta < bestDelta; + const bool betterTieBreak = delta == bestDelta && centerDelta < bestCenterDelta; + + if (!foundSnap || betterFit || betterTieBreak) { + bestOffset = offset; + bestDelta = delta; + bestCenterDelta = centerDelta; + bestIndex = index; + foundSnap = true; + } + }; + + for (size_t i = 0; i < controller.stripCount(); ++i) + considerColumn(i); + + if (!foundSnap) + return nullptr; + + controller.setOffset(bestOffset); + m_scrollingData->recalculate(); + + if (bestIndex < m_scrollingData->columns.size()) + return m_scrollingData->columns[bestIndex]; + + return nullptr; +} + +void CScrollingAlgorithm::focusColumn(SP column) { + if (!column || column->targetDatas.empty()) { + focusTargetUpdate(nullptr); + return; + } + + auto targetData = column->lastFocusedTarget.lock(); + + if (!targetData || targetData->column.lock() != column || !targetData->target || !Desktop::View::validMapped(targetData->target->window())) { + targetData = nullptr; + + for (const auto& candidate : column->targetDatas) { + if (candidate->target && Desktop::View::validMapped(candidate->target->window())) { + targetData = candidate; + break; + } + } + } + + focusTargetUpdate(targetData ? targetData->target.lock() : nullptr); +} + +SP CScrollingAlgorithm::getColumnAtViewportCenter() { + return m_scrollingData ? m_scrollingData->atCenter() : nullptr; +} + +double CScrollingAlgorithm::primaryViewportSize() { + const auto USABLE = usableArea(); + + return m_scrollingData->controller->isPrimaryHorizontal() ? USABLE.w : USABLE.h; +} + +double CScrollingAlgorithm::normalizedTapeOffset() { + const double primary = primaryViewportSize(); + if (primary <= 0.0) + return 0.0; + + return m_scrollingData->controller->getOffset() / primary; +} + std::optional CScrollingAlgorithm::predictSizeForNewTarget() { return std::nullopt; } diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp index 288d15a62..3d01effe9 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp @@ -109,6 +109,16 @@ namespace Layout::Tiled { virtual void swapTargets(SP a, SP b); virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + void moveTape(float delta); + void moveTapeNormalized(double delta); + void snapToGrid(); + SP snapToProjectedOffset(double projectedNormalizedOffset); + void focusColumn(SP column); + SP getColumnAtViewportCenter(); + + double primaryViewportSize(); + double normalizedTapeOffset(); + CBox usableArea(); SP dataFor(SP t); diff --git a/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp b/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp new file mode 100644 index 000000000..b4f8d8e9f --- /dev/null +++ b/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp @@ -0,0 +1,122 @@ +#include "ScrollMoveGesture.hpp" + +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../desktop/Workspace.hpp" +#include "../../../../helpers/Monitor.hpp" +#include "../../../../layout/LayoutManager.hpp" +#include "../../../../layout/algorithm/Algorithm.hpp" +#include "../../../../layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp" +#include "../../../../layout/space/Space.hpp" +#include "../../../../config/ConfigValue.hpp" + +#include +#include + +constexpr double SCROLL_GESTURE_VELOCITY_DECAY = 5.0; +constexpr double SCROLL_GESTURE_MAX_PROJECTION = 2.0; +constexpr double SCROLL_GESTURE_VELOCITY_SMOOTH = 0.35; + +static Layout::Tiled::CScrollingAlgorithm* currentScrollingLayout() { + const auto PMONITOR = Desktop::focusState()->monitor(); + if (!PMONITOR) + return nullptr; + + const auto PWORKSPACE = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; + if (!PWORKSPACE || !PWORKSPACE->m_space) + return nullptr; + + const auto ALGORITHM = PWORKSPACE->m_space->algorithm(); + if (!ALGORITHM || !ALGORITHM->tiledAlgo()) + return nullptr; + + return dynamic_cast(ALGORITHM->tiledAlgo().get()); +} + +static float deltaForUpdate(const ITrackpadGesture::STrackpadGestureUpdate& e) { + if (!e.swipe) + return 0.F; + + switch (e.direction) { + case TRACKPAD_GESTURE_DIR_LEFT: + case TRACKPAD_GESTURE_DIR_RIGHT: + case TRACKPAD_GESTURE_DIR_HORIZONTAL: return e.swipe->delta.x * e.scale; + case TRACKPAD_GESTURE_DIR_UP: + case TRACKPAD_GESTURE_DIR_DOWN: + case TRACKPAD_GESTURE_DIR_VERTICAL: return e.swipe->delta.y * e.scale; + default: return std::abs(e.swipe->delta.x) > std::abs(e.swipe->delta.y) ? e.swipe->delta.x * e.scale : e.swipe->delta.y * e.scale; + } +} + +void CScrollMoveTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { + ITrackpadGesture::begin(e); + + m_wasScrollingLayout = currentScrollingLayout() != nullptr; + m_hasLastUpdate = false; + m_lastUpdateTimeMs = 0; + m_velocity = 0.0; +} + +void CScrollMoveTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) { + if (!m_wasScrollingLayout) + return; + + const auto SCROLLING = currentScrollingLayout(); + if (!SCROLLING) + return; + + const float DELTA = deltaForUpdate(e); + const double PRIMARY = SCROLLING->primaryViewportSize(); + + if (DELTA == 0.F || PRIMARY <= 0.0) + return; + + const double NORMALIZED_DELTA = DELTA / PRIMARY; + const double NORMALIZED_OFFSET_DELTA = -NORMALIZED_DELTA; + + SCROLLING->moveTapeNormalized(NORMALIZED_DELTA); + + if (!e.swipe) + return; + + if (m_hasLastUpdate && e.swipe->timeMs > m_lastUpdateTimeMs) { + const double DT = (e.swipe->timeMs - m_lastUpdateTimeMs) / 1000.0; + + if (DT > 0.0) { + const double INSTANT_VELOCITY = NORMALIZED_OFFSET_DELTA / DT; + + if (std::isfinite(INSTANT_VELOCITY)) + m_velocity = m_velocity * (1.0 - SCROLL_GESTURE_VELOCITY_SMOOTH) + INSTANT_VELOCITY * SCROLL_GESTURE_VELOCITY_SMOOTH; + } + } + + m_hasLastUpdate = true; + m_lastUpdateTimeMs = e.swipe->timeMs; +} + +void CScrollMoveTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) { + static auto PSNAP = CConfigValue("gestures:scrolling:move_snap_to_grid"); + + const auto SCROLLING = currentScrollingLayout(); + if (!m_wasScrollingLayout || !SCROLLING) { + m_wasScrollingLayout = false; + m_hasLastUpdate = false; + m_velocity = 0.0; + return; + } + + const bool CANCELLED = e.swipe && e.swipe->cancelled; + const double PROJECTED = SCROLLING->normalizedTapeOffset() + + (CANCELLED ? 0.0 : std::clamp(m_velocity / SCROLL_GESTURE_VELOCITY_DECAY, -SCROLL_GESTURE_MAX_PROJECTION, SCROLL_GESTURE_MAX_PROJECTION)); + + if (*PSNAP) { + const auto LANDED = SCROLLING->snapToProjectedOffset(PROJECTED); + SCROLLING->focusColumn(LANDED); + } else { + SCROLLING->moveTapeNormalized(PROJECTED); + SCROLLING->focusColumn(SCROLLING->getColumnAtViewportCenter()); + } + + m_wasScrollingLayout = false; + m_hasLastUpdate = false; + m_velocity = 0.0; +} diff --git a/src/managers/input/trackpad/gestures/ScrollMoveGesture.hpp b/src/managers/input/trackpad/gestures/ScrollMoveGesture.hpp new file mode 100644 index 000000000..8263b4c0d --- /dev/null +++ b/src/managers/input/trackpad/gestures/ScrollMoveGesture.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "ITrackpadGesture.hpp" + +#include + +class CScrollMoveTrackpadGesture : public ITrackpadGesture { + public: + CScrollMoveTrackpadGesture() = default; + virtual ~CScrollMoveTrackpadGesture() = default; + + virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e); + virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e); + virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e); + + private: + bool m_wasScrollingLayout = false; + bool m_hasLastUpdate = false; + uint32_t m_lastUpdateTimeMs = 0; + double m_velocity = 0.0; +}; From 0cf01125a21431bcea2703b93182dd3b2fdd01f8 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sun, 3 May 2026 13:01:14 +0100 Subject: [PATCH 2/4] try --- src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp b/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp index b4f8d8e9f..9a5d189fc 100644 --- a/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp +++ b/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp @@ -105,14 +105,14 @@ void CScrollMoveTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd } const bool CANCELLED = e.swipe && e.swipe->cancelled; - const double PROJECTED = SCROLLING->normalizedTapeOffset() + - (CANCELLED ? 0.0 : std::clamp(m_velocity / SCROLL_GESTURE_VELOCITY_DECAY, -SCROLL_GESTURE_MAX_PROJECTION, SCROLL_GESTURE_MAX_PROJECTION)); + const double Δ = (CANCELLED ? 0.0 : std::clamp(m_velocity / SCROLL_GESTURE_VELOCITY_DECAY, -SCROLL_GESTURE_MAX_PROJECTION, SCROLL_GESTURE_MAX_PROJECTION)); + const double PROJECTED = SCROLLING->normalizedTapeOffset() + Δ; if (*PSNAP) { const auto LANDED = SCROLLING->snapToProjectedOffset(PROJECTED); SCROLLING->focusColumn(LANDED); } else { - SCROLLING->moveTapeNormalized(PROJECTED); + SCROLLING->moveTape(Δ); SCROLLING->focusColumn(SCROLLING->getColumnAtViewportCenter()); } From b43ac6abf8608542c9633a16f1d82defde6b0de4 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sun, 3 May 2026 13:18:55 +0100 Subject: [PATCH 3/4] thing --- .../tiled/scrolling/ScrollingAlgorithm.cpp | 14 ++++++++++++++ .../tiled/scrolling/ScrollingAlgorithm.hpp | 1 + .../input/trackpad/gestures/ScrollMoveGesture.cpp | 14 ++++++++++++-- .../input/trackpad/gestures/ScrollMoveGesture.hpp | 14 ++++++++++---- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp index 9e8f1d710..2d5ad2d22 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp @@ -1621,6 +1621,20 @@ SP CScrollingAlgorithm::getColumnAtViewportCenter() { return m_scrollingData ? m_scrollingData->atCenter() : nullptr; } +SP CScrollingAlgorithm::currentColumn() { + auto focus = Desktop::focusState()->window(); + + if (!focus) + return nullptr; + + auto data = dataFor(focus->layoutTarget()); + + if (!data) + return nullptr; + + return data->column.lock(); +} + double CScrollingAlgorithm::primaryViewportSize() { const auto USABLE = usableArea(); diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp index 3d01effe9..3ae57ba2d 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp @@ -115,6 +115,7 @@ namespace Layout::Tiled { SP snapToProjectedOffset(double projectedNormalizedOffset); void focusColumn(SP column); SP getColumnAtViewportCenter(); + SP currentColumn(); double primaryViewportSize(); double normalizedTapeOffset(); diff --git a/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp b/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp index 9a5d189fc..3208b40c8 100644 --- a/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp +++ b/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp @@ -50,10 +50,15 @@ static float deltaForUpdate(const ITrackpadGesture::STrackpadGestureUpdate& e) { void CScrollMoveTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); - m_wasScrollingLayout = currentScrollingLayout() != nullptr; + const auto LAYOUT = currentScrollingLayout(); + + m_wasScrollingLayout = !!LAYOUT; m_hasLastUpdate = false; m_lastUpdateTimeMs = 0; m_velocity = 0.0; + + m_startedOffset = LAYOUT ? LAYOUT->normalizedTapeOffset() : 0.0; + m_startedColumn = LAYOUT ? LAYOUT->currentColumn() : nullptr; } void CScrollMoveTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) { @@ -110,7 +115,12 @@ void CScrollMoveTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd if (*PSNAP) { const auto LANDED = SCROLLING->snapToProjectedOffset(PROJECTED); - SCROLLING->focusColumn(LANDED); + + // if we land on the same thing we started, move the tape back + if (LANDED == m_startedColumn && LANDED) + SCROLLING->moveTapeNormalized(m_startedOffset - SCROLLING->normalizedTapeOffset()); + else + SCROLLING->focusColumn(LANDED); } else { SCROLLING->moveTape(Δ); SCROLLING->focusColumn(SCROLLING->getColumnAtViewportCenter()); diff --git a/src/managers/input/trackpad/gestures/ScrollMoveGesture.hpp b/src/managers/input/trackpad/gestures/ScrollMoveGesture.hpp index 8263b4c0d..26e51e009 100644 --- a/src/managers/input/trackpad/gestures/ScrollMoveGesture.hpp +++ b/src/managers/input/trackpad/gestures/ScrollMoveGesture.hpp @@ -4,6 +4,10 @@ #include +namespace Layout::Tiled { + struct SColumnData; +} + class CScrollMoveTrackpadGesture : public ITrackpadGesture { public: CScrollMoveTrackpadGesture() = default; @@ -14,8 +18,10 @@ class CScrollMoveTrackpadGesture : public ITrackpadGesture { virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e); private: - bool m_wasScrollingLayout = false; - bool m_hasLastUpdate = false; - uint32_t m_lastUpdateTimeMs = 0; - double m_velocity = 0.0; + bool m_wasScrollingLayout = false; + bool m_hasLastUpdate = false; + uint32_t m_lastUpdateTimeMs = 0; + double m_velocity = 0.0; + double m_startedOffset = 0.0; + WP m_startedColumn = nullptr; }; From a5e918648f379396990c2b996ef11c0dd1fd4544 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sun, 3 May 2026 13:20:20 +0100 Subject: [PATCH 4/4] e --- src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp b/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp index 3208b40c8..3833213f0 100644 --- a/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp +++ b/src/managers/input/trackpad/gestures/ScrollMoveGesture.cpp @@ -118,7 +118,7 @@ void CScrollMoveTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd // if we land on the same thing we started, move the tape back if (LANDED == m_startedColumn && LANDED) - SCROLLING->moveTapeNormalized(m_startedOffset - SCROLLING->normalizedTapeOffset()); + SCROLLING->moveTapeNormalized(SCROLLING->normalizedTapeOffset() - m_startedOffset); else SCROLLING->focusColumn(LANDED); } else {