diff --git a/hyprtester/plugin/src/main.cpp b/hyprtester/plugin/src/main.cpp index ac59119de1c..da88e097515 100644 --- a/hyprtester/plugin/src/main.cpp +++ b/hyprtester/plugin/src/main.cpp @@ -2,11 +2,13 @@ #include #include #include +#include #define private public #include #include #include +#include #include #include #include @@ -17,6 +19,7 @@ #undef private #include +#include #include using namespace Hyprutils::Utils; using namespace Hyprutils::String; @@ -156,6 +159,96 @@ static SDispatchResult simulateGesture(std::string in) { return {.success = true}; } +static SDispatchResult pinchUpdate(std::string in) { + CVarList data(in); + uint32_t fingers = 2; + double scale = 1.0; + Vector2D delta = {}; + double rotation{}; + + if (data.size() < 2) + return {.success = false, .error = "invalid input"}; + + if (const auto n = strToNumber(data[0]); n) + fingers = n.value(); + else + return {.success = false, .error = "invalid input"}; + + if (const auto n = strToNumber(data[1]); n) + scale = n.value(); + else + return {.success = false, .error = "invalid input"}; + + if (data.size() > 2) { + if (const auto n = strToNumber(data[2]); n) + delta.x = n.value(); + else + return {.success = false, .error = "invalid input"}; + } + + if (data.size() > 3) { + if (const auto n = strToNumber(data[3]); n) + delta.y = n.value(); + else + return {.success = false, .error = "invalid input"}; + } + + if (data.size() > 4) { + if (const auto n = strToNumber(data[4]); n) + rotation = n.value(); + else + return {.success = false, .error = "invalid input"}; + } + + g_pTrackpadGestures->gestureUpdate(IPointer::SPinchUpdateEvent{ + .fingers = fingers, + .delta = delta, + .scale = scale, + .rotation = rotation, + }); + + return {}; +} + +static SDispatchResult pinchEnd(std::string in) { + g_pTrackpadGestures->gestureEnd(IPointer::SPinchEndEvent{}); + + return {}; +} + +static SDispatchResult expectCursorZoom(std::string in) { + CVarList data(in); + float expected = 1.F; + float delta = 0.01F; + + if (data.size() < 1) + return {.success = false, .error = "invalid input"}; + + if (const auto n = strToNumber(data[0]); n) + expected = n.value(); + else + return {.success = false, .error = "invalid input"}; + + if (data.size() > 1) { + if (const auto n = strToNumber(data[1]); n) + delta = n.value(); + else + return {.success = false, .error = "invalid input"}; + } + + const auto PMONITOR = g_pCompositor->getMonitorFromVector(g_pInputManager->getMouseCoordsInternal()); + + if (!PMONITOR) + return {.success = false, .error = "No monitor under cursor"}; + + const auto actual = PMONITOR->m_cursorZoom->value(); + + if (std::abs(actual - expected) > delta) + return {.success = false, .error = std::format("Expected cursor zoom {} ± {}, got {}", expected, delta, actual)}; + + return {}; +} + static SDispatchResult vkb(std::string in) { auto tkb0 = CTestKeyboard::create(false); auto tkb1 = CTestKeyboard::create(false); @@ -375,6 +468,32 @@ static int luaGesture(lua_State* L) { return luaResult(L, ::simulateGesture(std::format("{},{}", direction, fingers))); } +static int luaPinchUpdate(lua_State* L) { + std::string in = std::format("{},{}", (int)luaL_checkinteger(L, 1), (double)luaL_checknumber(L, 2)); + + if (lua_gettop(L) > 2) + in += std::format(",{}", (double)luaL_checknumber(L, 3)); + if (lua_gettop(L) > 3) + in += std::format(",{}", (double)luaL_checknumber(L, 4)); + if (lua_gettop(L) > 4) + in += std::format(",{}", (double)luaL_checknumber(L, 5)); + + return luaResult(L, ::pinchUpdate(in)); +} + +static int luaPinchEnd(lua_State* L) { + return luaResult(L, ::pinchEnd("")); +} + +static int luaExpectCursorZoom(lua_State* L) { + const auto expected = (double)luaL_checknumber(L, 1); + + if (lua_gettop(L) > 1) + return luaResult(L, ::expectCursorZoom(std::format("{},{}", expected, (double)luaL_checknumber(L, 2)))); + + return luaResult(L, ::expectCursorZoom(std::format("{}", expected))); +} + static int luaScroll(lua_State* L) { return luaResult(L, ::scroll(std::to_string((double)luaL_checknumber(L, 1)))); } @@ -425,6 +544,9 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { addLuaFn("vkb", ::luaVkb); addLuaFn("alt", ::luaAlt); addLuaFn("gesture", ::luaGesture); + addLuaFn("pinch_update", ::luaPinchUpdate); + addLuaFn("pinch_end", ::luaPinchEnd); + addLuaFn("expect_cursor_zoom", ::luaExpectCursorZoom); addLuaFn("scroll", ::luaScroll); addLuaFn("click", ::luaClick); addLuaFn("keybind", ::luaKeybind); diff --git a/hyprtester/src/tests/main/gestures.cpp b/hyprtester/src/tests/main/gestures.cpp index f832471239c..f82a1613bc1 100644 --- a/hyprtester/src/tests/main/gestures.cpp +++ b/hyprtester/src/tests/main/gestures.cpp @@ -6,12 +6,14 @@ #include #include #include +#include #include #include #include "../shared.hpp" using namespace Hyprutils::OS; using namespace Hyprutils::Memory; +using namespace Hyprutils::String; #define UP CUniquePointer #define SP CSharedPointer @@ -178,4 +180,40 @@ TEST_CASE(gestures) { OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })")); } + const std::string cursorPosBeforePinch = getFromSocket("/cursorpos"); + + OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 500, y = 500 })")); + OK(getFromSocket("/eval hl.config({ cursor = { zoom_factor = 1 } })")); + OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1, 0.01)")); + + OK(getFromSocket("/eval hl.plugin.test.pinch_update(2, 1.2)")); + OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1.2, 0.01)")); + OK(getFromSocket("/eval hl.plugin.test.pinch_update(2, 1.6)")); + OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1.6, 0.01)")); + OK(getFromSocket("/eval hl.plugin.test.pinch_end()")); + OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1.6, 0.01)")); + + OK(getFromSocket("/eval hl.plugin.test.pinch_update(2, 0.64)")); + OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1, 0.01)")); + OK(getFromSocket("/eval hl.plugin.test.pinch_end()")); + OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1, 0.01)")); + + const auto comma = cursorPosBeforePinch.find(','); + + if (comma != std::string::npos) { + auto xSv = std::string_view(cursorPosBeforePinch).substr(0, comma); + auto ySv = std::string_view(cursorPosBeforePinch).substr(comma + 1); + while (!xSv.empty() && xSv.front() == ' ') + xSv.remove_prefix(1); + while (!ySv.empty() && ySv.front() == ' ') + ySv.remove_prefix(1); + + const auto x = strToNumber(xSv); + const auto y = strToNumber(ySv); + + if (!x || !y) + FAIL_TEST("Failed to restore cursor pos"); + + OK(getFromSocket(std::format("/dispatch hl.dsp.cursor.move({{ x = {}, y = {} }})", x.value(), y.value()))); + } } diff --git a/hyprtester/src/tests/main/groups.cpp b/hyprtester/src/tests/main/groups.cpp index 7e1c63b6b77..e1d4dd9be1a 100644 --- a/hyprtester/src/tests/main/groups.cpp +++ b/hyprtester/src/tests/main/groups.cpp @@ -192,6 +192,7 @@ TEST_CASE(groups) { NLog::log("{}Disable autogrouping", Colors::YELLOW); OK(getFromSocket("/eval hl.config({ group = { auto_group = false } })")); + OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 2 } })")); NLog::log("{}Spawn kittyProcC", Colors::YELLOW); auto kittyProcC = Tests::spawnKitty(); @@ -206,6 +207,7 @@ TEST_CASE(groups) { EXPECT_COUNT_STRING(str, "at: 22,22", 2); } + OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 0 } })")); OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'left' })")); OK(getFromSocket("/dispatch hl.dsp.group.active({ index = 1 })")); OK(getFromSocket("/eval hl.config({ group = { auto_group = true } })")); diff --git a/hyprtester/test.lua b/hyprtester/test.lua index 6f39b34a330..d1f8657e8dd 100644 --- a/hyprtester/test.lua +++ b/hyprtester/test.lua @@ -290,5 +290,6 @@ hl.gesture({ fingers = 5, direction = "left", action = function() hl.dispatch(hl hl.gesture({ fingers = 5, direction = "right", action = function() hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "t", window = "activewindow" })) end }) hl.gesture({ fingers = 4, direction = "right", action = function() hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "return", window = "activewindow" })) end }) hl.gesture({ fingers = 4, direction = "left", action = function() hl.dispatch(hl.dsp.cursor.move_to_corner({ corner = 1, window = "activewindow" })) end }) +hl.gesture({ fingers = 2, direction = "pinch", action = "cursorZoom", zoom_level = "1", mode = "live" }) hl.gesture({ fingers = 2, direction = "right", action = "float", disable_inhibit = true }) diff --git a/src/helpers/MonitorZoomController.cpp b/src/helpers/MonitorZoomController.cpp index c1b6cc6afce..b59e1eb2290 100644 --- a/src/helpers/MonitorZoomController.cpp +++ b/src/helpers/MonitorZoomController.cpp @@ -7,11 +7,27 @@ #include "desktop/DesktopTypes.hpp" #include "render/Renderer.hpp" +void CMonitorZoomController::pinAnchor(const Vector2D& anchor) { + m_pinnedAnchor = anchor; + m_anchorPinned = true; +} + +void CMonitorZoomController::clearAnchor() { + m_anchorPinned = false; +} + +Vector2D CMonitorZoomController::getAnchor(const PHLMONITORREF& monitor) { + if (m_anchorPinned) + return m_pinnedAnchor; + + return g_pInputManager->getMouseCoordsInternal() - monitor->m_position; +} + void CMonitorZoomController::zoomWithDetachedCamera(CBox& result, const Render::SRenderData& m_renderData) { const auto m = m_renderData.pMonitor; auto monbox = CBox(0, 0, m->m_size.x, m->m_size.y); const auto ZOOM = g_pHyprRenderer->m_renderData.mouseZoomFactor; - const auto MOUSE = g_pInputManager->getMouseCoordsInternal() - m->m_position; + const auto MOUSE = getAnchor(m); if (m_lastZoomLevel != ZOOM) { if (m_resetCameraState) { @@ -83,8 +99,7 @@ void CMonitorZoomController::applyZoomTransform(CBox& monbox, const Render::SRen if (*PZOOMDETACHEDCAMERA && !INITANIM) zoomWithDetachedCamera(monbox, m_renderData); else { - const auto ZOOMCENTER = - g_pHyprRenderer->m_renderData.mouseZoomUseMouse ? (g_pInputManager->getMouseCoordsInternal() - m->m_position) * m->m_scale : m->m_transformedSize / 2.f; + const auto ZOOMCENTER = g_pHyprRenderer->m_renderData.mouseZoomUseMouse ? getAnchor(m) * m->m_scale : m->m_transformedSize / 2.f; monbox.translate(-ZOOMCENTER).scale(ZOOM).translate(*PZOOMRIGID ? m->m_transformedSize / 2.0 : ZOOMCENTER); } diff --git a/src/helpers/MonitorZoomController.hpp b/src/helpers/MonitorZoomController.hpp index 94373bafc86..ce3ad55464a 100644 --- a/src/helpers/MonitorZoomController.hpp +++ b/src/helpers/MonitorZoomController.hpp @@ -1,6 +1,7 @@ #pragma once #include "./math/Math.hpp" +#include "../desktop/DesktopTypes.hpp" namespace Render { struct SRenderData; @@ -10,12 +11,18 @@ class CMonitorZoomController { public: bool m_resetCameraState = true; + void pinAnchor(const Vector2D& anchor); + void clearAnchor(); + void applyZoomTransform(CBox& monbox, const Render::SRenderData& m_renderData); private: - void zoomWithDetachedCamera(CBox& result, const Render::SRenderData& m_renderData); + void zoomWithDetachedCamera(CBox& result, const Render::SRenderData& m_renderData); + Vector2D getAnchor(const PHLMONITORREF& monitor); - CBox m_camera; - float m_lastZoomLevel = 1.0f; - bool m_padCamEdges = true; + CBox m_camera; + Vector2D m_pinnedAnchor = {}; + float m_lastZoomLevel = 1.0f; + bool m_padCamEdges = true; + bool m_anchorPinned = false; }; diff --git a/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp b/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp index 515edbb6b2a..418b8f7a22e 100644 --- a/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp +++ b/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp @@ -2,19 +2,40 @@ #include "../../../../Compositor.hpp" #include "../../../../helpers/Monitor.hpp" +#include "../../../../managers/input/InputManager.hpp" +#include CCursorZoomTrackpadGesture::CCursorZoomTrackpadGesture(const std::string& first, const std::string& second) { - try { - m_zoomValue = std::stof(first); - } catch (...) { ; } + if (const auto n = Hyprutils::String::strToNumber(first); n) + m_zoomValue = n.value(); if (second == "mult") m_mode = MODE_MULT; + else if (second == "live") + m_mode = MODE_LIVE; } void CCursorZoomTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); + if (m_mode == MODE_LIVE) { + if (!e.pinch) + return; + + m_monitor = g_pCompositor->getMonitorFromCursor(); + if (!m_monitor) + return; + + const auto PMONITOR = m_monitor.lock(); + if (!PMONITOR) + return; + + m_zoomBegin = std::clamp(PMONITOR->m_cursorZoom->value(), 1.0F, 100.0F); + PMONITOR->m_cursorZoom->setValueAndWarp(m_zoomBegin); + PMONITOR->m_zoomController.pinAnchor(g_pInputManager->getMouseCoordsInternal() - PMONITOR->m_position); + return; + } + if (m_mode == MODE_TOGGLE) m_zoomed = !m_zoomed; @@ -25,9 +46,35 @@ void CCursorZoomTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureB *m->m_cursorZoom = m_zoomed ? m_zoomValue : *PZOOMFACTOR; break; case MODE_MULT: *m->m_cursorZoom = std::clamp(m->m_cursorZoom->goal() * m_zoomValue, 1.0F, 100.0F); break; + case MODE_LIVE: break; } } } -void CCursorZoomTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {} -void CCursorZoomTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {} +void CCursorZoomTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) { + if (m_mode != MODE_LIVE || !m_monitor || !e.pinch) + return; + + const auto PMONITOR = m_monitor.lock(); + if (!PMONITOR) + return; + + auto zoom = std::clamp(m_zoomBegin * static_cast(e.pinch->scale), 1.0F, 100.0F); + + if (zoom < 1.05F) + zoom = 1.0F; + + PMONITOR->m_cursorZoom->setValueAndWarp(zoom); +} + +void CCursorZoomTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) { + if (m_mode != MODE_LIVE || !m_monitor) + return; + + const auto PMONITOR = m_monitor.lock(); + if (!PMONITOR) + return; + + PMONITOR->m_zoomController.clearAnchor(); + m_monitor.reset(); +} diff --git a/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp b/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp index b53c81e9878..3def907ff68 100644 --- a/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp +++ b/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp @@ -2,6 +2,8 @@ #include "ITrackpadGesture.hpp" +#include "../../../../desktop/DesktopTypes.hpp" + class CCursorZoomTrackpadGesture : public ITrackpadGesture { public: CCursorZoomTrackpadGesture(const std::string& zoomLevel, const std::string& mode); @@ -14,10 +16,13 @@ class CCursorZoomTrackpadGesture : public ITrackpadGesture { private: float m_zoomValue = 1.0; inline static bool m_zoomed = false; + PHLMONITORREF m_monitor; + float m_zoomBegin = 1.0; enum eMode : uint8_t { MODE_TOGGLE = 0, MODE_MULT, + MODE_LIVE, }; eMode m_mode = MODE_TOGGLE;