This commit is contained in:
Vaxry 2026-05-06 08:11:41 -04:00 committed by GitHub
commit 3ad4b1c767
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 335 additions and 7 deletions

View file

@ -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"
@ -1979,7 +1980,9 @@ std::optional<std::string> CConfigManager::handleGesture(const std::string& comm
else if (data[startDataIdx] == "cursorZoom") {
result = g_pTrackpadGestures->addGesture(makeUnique<CCursorZoomTrackpadGesture>(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<CScrollMoveTrackpadGesture>(), 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]);

View file

@ -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 <hyprutils/utils/ScopeGuard.hpp>
@ -721,7 +722,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);
@ -842,8 +843,10 @@ static int hlGesture(lua_State* L) {
result = g_pTrackpadGestures->addGesture(makeUnique<CFloatTrackpadGesture>(mode), fingerCount, direction, modMask, deltaScale, disableInhibit);
else if (action == "fullscreen")
result = g_pTrackpadGestures->addGesture(makeUnique<CFullscreenTrackpadGesture>(mode), fingerCount, direction, modMask, deltaScale, disableInhibit);
else if (action == "cursorZoom")
else if (action == "cursor_zoom" || action == "cursorZoom")
result = g_pTrackpadGestures->addGesture(makeUnique<CCursorZoomTrackpadGesture>(zoomLevel, mode), fingerCount, direction, modMask, deltaScale, disableInhibit);
else if (action == "scroll_move")
result = g_pTrackpadGestures->addGesture(makeUnique<CScrollMoveTrackpadGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit);
else if (action == "unset")
result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale, disableInhibit);
else

View file

@ -376,7 +376,7 @@ std::vector<SP<IValue>> Values::getConfigValues() {
MS<Bool>("gestures:workspace_swipe_forever", "if enabled, swiping will not clamp at the neighboring workspaces but continue to the further ones.", false),
MS<Bool>("gestures:workspace_swipe_use_r", "if enabled, swiping will use the r prefix instead of the m prefix for finding workspaces.", false),
MS<Int>("gestures:close_max_timeout", "Timeout for closing windows with the close gesture, in ms.", 1000, {.min = 10, .max = 2000}),
MS<Bool>("gestures:scrolling:move_snap_to_grid", "When releasing the scroll move gesture, whether it shoud try to snap to the grid.", true),
/*
* group:
*/

View file

@ -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;
}

View file

@ -1497,6 +1497,158 @@ 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<SColumnData> CScrollingAlgorithm::snapToProjectedOffset(double projectedNormalizedOffset) {
static const auto PFSONONE = CConfigValue<Config::INTEGER>("scrolling:fullscreen_on_one_column");
static const auto PFITMETHOD = CConfigValue<Config::INTEGER>("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<SColumnData> 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<SColumnData> CScrollingAlgorithm::getColumnAtViewportCenter() {
return m_scrollingData ? m_scrollingData->atCenter() : nullptr;
}
SP<SColumnData> 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();
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<Vector2D> CScrollingAlgorithm::predictSizeForNewTarget() {
return std::nullopt;
}

View file

@ -109,6 +109,17 @@ namespace Layout::Tiled {
virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
void moveTape(float delta);
void moveTapeNormalized(double delta);
void snapToGrid();
SP<SColumnData> snapToProjectedOffset(double projectedNormalizedOffset);
void focusColumn(SP<SColumnData> column);
SP<SColumnData> getColumnAtViewportCenter();
SP<SColumnData> currentColumn();
double primaryViewportSize();
double normalizedTapeOffset();
CBox usableArea();
SP<SScrollingTargetData> dataFor(SP<ITarget> t);

View file

@ -0,0 +1,132 @@
#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 <algorithm>
#include <cmath>
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<Layout::Tiled::CScrollingAlgorithm*>(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);
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) {
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<Config::BOOL>("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 Δ = (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);
// if we land on the same thing we started, move the tape back
if (LANDED == m_startedColumn && LANDED)
SCROLLING->moveTapeNormalized(SCROLLING->normalizedTapeOffset() - m_startedOffset);
else
SCROLLING->focusColumn(LANDED);
} else {
SCROLLING->moveTape(Δ);
SCROLLING->focusColumn(SCROLLING->getColumnAtViewportCenter());
}
m_wasScrollingLayout = false;
m_hasLastUpdate = false;
m_velocity = 0.0;
}

View file

@ -0,0 +1,27 @@
#pragma once
#include "ITrackpadGesture.hpp"
#include <cstdint>
namespace Layout::Tiled {
struct SColumnData;
}
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;
double m_startedOffset = 0.0;
WP<Layout::Tiled::SColumnData> m_startedColumn = nullptr;
};