diff --git a/hyprtester/plugin/src/main.cpp b/hyprtester/plugin/src/main.cpp index 1a7136d9..17b1e02a 100644 --- a/hyprtester/plugin/src/main.cpp +++ b/hyprtester/plugin/src/main.cpp @@ -6,6 +6,9 @@ #define private public #include #include +#include +#include +#include #undef private #include "globals.hpp" @@ -30,10 +33,26 @@ static SDispatchResult test(std::string in) { }; } +// Trigger a snap move event for the active window +static SDispatchResult snapMove(std::string in) { + const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock(); + if (!PLASTWINDOW->m_isFloating) + return {.success = false, .error = "Window must be floating"}; + + Vector2D pos = PLASTWINDOW->m_realPosition->goal(); + Vector2D size = PLASTWINDOW->m_realSize->goal(); + + g_pLayoutManager->getCurrentLayout()->performSnap(pos, size, PLASTWINDOW, MBIND_MOVE, -1, size); + *PLASTWINDOW->m_realPosition = pos.round(); + + return {}; +} + APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:test", ::test); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:snapmove", ::snapMove); return {"hyprtestplugin", "hyprtestplugin", "Vaxry", "1.0"}; } diff --git a/hyprtester/src/shared.hpp b/hyprtester/src/shared.hpp index cfce1ba7..8bea4c54 100644 --- a/hyprtester/src/shared.hpp +++ b/hyprtester/src/shared.hpp @@ -44,13 +44,13 @@ namespace Colors { } while (0) #define EXPECT_CONTAINS(haystack, needle) \ - if (!std::string{haystack}.contains(needle)) { \ - NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \ + if (const auto EXPECTED = needle; !std::string{haystack}.contains(EXPECTED)) { \ + NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, EXPECTED, __FILE__, __LINE__, \ std::string{haystack}); \ ret = 1; \ TESTS_FAILED++; \ } else { \ - NLog::log("{}Passed: {}{} contains {}.", Colors::GREEN, Colors::RESET, #haystack, #needle); \ + NLog::log("{}Passed: {}{} contains {}.", Colors::GREEN, Colors::RESET, #haystack, EXPECTED); \ TESTS_PASSED++; \ } diff --git a/hyprtester/src/tests/main/snap.cpp b/hyprtester/src/tests/main/snap.cpp new file mode 100644 index 00000000..c7c2b256 --- /dev/null +++ b/hyprtester/src/tests/main/snap.cpp @@ -0,0 +1,174 @@ +#include +#include +#include + +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "../shared.hpp" +#include "tests.hpp" + +using Hyprutils::Math::Vector2D; + +static int ret = 0; + +static bool spawnFloatingKitty() { + if (!Tests::spawnKitty()) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + OK(getFromSocket("/dispatch setfloating active")); + OK(getFromSocket("/dispatch resizeactive exact 100 100")); + return true; +} + +static void expectSocket(const std::string& CMD) { + if (const auto RESULT = getFromSocket(CMD); RESULT != "ok") { + NLog::log("{}Failed: {}getFromSocket({}), expected ok, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, CMD, RESULT, __FILE__, __LINE__); + ret = 1; + TESTS_FAILED++; + } else { + NLog::log("{}Passed: {}getFromSocket({}). Got ok", Colors::GREEN, Colors::RESET, CMD); + TESTS_PASSED++; + } +} + +static void expectSnapMove(const Vector2D FROM, const Vector2D* TO) { + const Vector2D& A = FROM; + const Vector2D& B = TO ? *TO : FROM; + if (TO) + NLog::log("{}Expecting snap to ({},{}) when window is moved to ({},{})", Colors::YELLOW, B.x, B.y, A.x, A.y); + else + NLog::log("{}Expecting no snap when window is moved to ({},{})", Colors::YELLOW, A.x, A.y); + + expectSocket(std::format("/dispatch moveactive exact {} {}", A.x, A.y)); + expectSocket("/dispatch plugin:test:snapmove"); + EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("at: {},{}", B.x, B.y)); +} + +static void testSnap(const bool OVERLAP, const bool RESPECT) { + const double BORDERSIZE = 2; + const double WINDOWSIZE = 100; + + // test window snapping + { + const double OTHER = 500; + const double WINDOWGAP = 8; + const double GAPSIN = 5; + const double GAP = (RESPECT ? GAPSIN : 0) + BORDERSIZE + (OVERLAP ? 0 : BORDERSIZE); + const double END = GAP + WINDOWSIZE; + + double x; + Vector2D predict; + + x = WINDOWGAP + END; + expectSnapMove({OTHER + x, OTHER}, nullptr); + expectSnapMove({OTHER - x, OTHER}, nullptr); + expectSnapMove({OTHER, OTHER + x}, nullptr); + expectSnapMove({OTHER, OTHER - x}, nullptr); + x -= 1; + expectSnapMove({OTHER + x, OTHER}, &(predict = {OTHER + END, OTHER})); + expectSnapMove({OTHER - x, OTHER}, &(predict = {OTHER - END, OTHER})); + expectSnapMove({OTHER, OTHER + x}, &(predict = {OTHER, OTHER + END})); + expectSnapMove({OTHER, OTHER - x}, &(predict = {OTHER, OTHER - END})); + } + + // test monitor snapping + { + const double MONITORGAP = 10; + const double GAPSOUT = 20; + const double RESP = (RESPECT ? GAPSOUT : 0); + const double GAP = RESP + (OVERLAP ? 0 : BORDERSIZE); + const double END = GAP + WINDOWSIZE; + + double x; + Vector2D predict; + + x = MONITORGAP + GAP; + expectSnapMove({x, x}, nullptr); + x -= 1; + expectSnapMove({x, x}, &(predict = {GAP, GAP})); + + x = MONITORGAP + END; + expectSnapMove({1920 - x, 1080 - x}, nullptr); + x -= 1; + expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - END, 1080 - END})); + + // test reserved area + const double RESERVED = 200; + const double RGAP = RESERVED + RESP + BORDERSIZE; + const double REND = RGAP + WINDOWSIZE; + + x = MONITORGAP + RGAP; + expectSnapMove({x, x}, nullptr); + x -= 1; + expectSnapMove({x, x}, &(predict = {RGAP, RGAP})); + + x = MONITORGAP + REND; + expectSnapMove({1920 - x, 1080 - x}, nullptr); + x -= 1; + expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - REND, 1080 - REND})); + } +} + +static bool test() { + NLog::log("{}Testing snap", Colors::GREEN); + + // move to monitor HEADLESS-2 + NLog::log("{}Moving to monitor HEADLESS-2", Colors::YELLOW); + OK(getFromSocket("/dispatch focusmonitor HEADLESS-2")); + NLog::log("{}Adding reserved monitor area to HEADLESS-2", Colors::YELLOW); + OK(getFromSocket("/keyword monitor HEADLESS-2,addreserved,200,200,200,200")); + + // test on workspace "snap" + NLog::log("{}Dispatching workspace `snap`", Colors::YELLOW); + OK(getFromSocket("/dispatch workspace name:snap")); + + // spawn a kitty terminal and move to (500,500) + NLog::log("{}Spawning kittyProcA", Colors::YELLOW); + if (!spawnFloatingKitty()) + return false; + + NLog::log("{}Expecting 1 window", Colors::YELLOW); + EXPECT(Tests::windowCount(), 1); + + NLog::log("{}Move the kitty window to (500,500)", Colors::YELLOW); + OK(getFromSocket("/dispatch moveactive exact 500 500")); + + // spawn a second kitty terminal + NLog::log("{}Spawning kittyProcB", Colors::YELLOW); + if (!spawnFloatingKitty()) + return false; + + NLog::log("{}Expecting 2 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 2); + + NLog::log(""); + testSnap(false, false); + + NLog::log("\n{}Turning on border_overlap", Colors::YELLOW); + OK(getFromSocket("/keyword general:snap:border_overlap true")); + testSnap(true, false); + + NLog::log("\n{}Turning on respect_gaps", Colors::YELLOW); + OK(getFromSocket("/keyword general:snap:border_overlap false")); + OK(getFromSocket("/keyword general:snap:respect_gaps true")); + testSnap(false, true); + + NLog::log("\n{}Turning on both border_overlap and respect_gaps", Colors::YELLOW); + OK(getFromSocket("/keyword general:snap:border_overlap true")); + testSnap(true, true); + + // kill all + NLog::log("\n{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); + + NLog::log("{}Reloading the config", Colors::YELLOW); + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_TEST_FN(test) diff --git a/hyprtester/test.conf b/hyprtester/test.conf index 60fd43fb..65f44e5c 100644 --- a/hyprtester/test.conf +++ b/hyprtester/test.conf @@ -73,6 +73,14 @@ general { border_size = 2 + snap { + enabled = true + window_gap = 8 + monitor_gap = 10 + respect_gaps = false + border_overlap = false + } + # https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg col.inactive_border = rgba(595959aa) @@ -302,4 +310,4 @@ workspace = n[s:window] f[1], gapsout:0, gapsin:0 windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] w[tv1] windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] w[tv1] windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] f[1] -windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] f[1] \ No newline at end of file +windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] f[1] diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp index 6bc82516..629fb3fb 100644 --- a/src/layout/IHyprLayout.cpp +++ b/src/layout/IHyprLayout.cpp @@ -9,7 +9,6 @@ #include "../protocols/core/Compositor.hpp" #include "../xwayland/XSurface.hpp" #include "../render/Renderer.hpp" -#include "../managers/input/InputManager.hpp" #include "../managers/LayoutManager.hpp" #include "../managers/EventManager.hpp" #include "../managers/HookSystemManager.hpp" @@ -415,7 +414,7 @@ static void snapResize(double& start, double& end, const double P) { using SnapFn = std::function; -static void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, PHLWINDOW DRAGGINGWINDOW, const eMouseBindMode MODE, const int CORNER, const Vector2D& BEGINSIZE) { +void IHyprLayout::performSnap(Vector2D& sourcePos, Vector2D& sourceSize, PHLWINDOW DRAGGINGWINDOW, const eMouseBindMode MODE, const int CORNER, const Vector2D& BEGINSIZE) { static auto SNAPWINDOWGAP = CConfigValue("general:snap:window_gap"); static auto SNAPMONITORGAP = CConfigValue("general:snap:monitor_gap"); static auto SNAPBORDEROVERLAP = CConfigValue("general:snap:border_overlap"); @@ -514,30 +513,33 @@ static void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, PHLWINDOW DRA gapOffset = std::max({PGAPSOUTPTR->m_left, PGAPSOUTPTR->m_right, PGAPSOUTPTR->m_top, PGAPSOUTPTR->m_bottom}); } + SRange monX = {MON->m_position.x + MON->m_reservedTopLeft.x + DRAGGINGBORDERSIZE + gapOffset, + MON->m_position.x + MON->m_size.x - MON->m_reservedBottomRight.x - DRAGGINGBORDERSIZE - gapOffset}; + SRange monY = {MON->m_position.y + MON->m_reservedTopLeft.y + DRAGGINGBORDERSIZE + gapOffset, + MON->m_position.y + MON->m_size.y - MON->m_reservedBottomRight.y - DRAGGINGBORDERSIZE - gapOffset}; + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && - ((MON->m_reservedTopLeft.x > 0 && canSnap(sourceX.start, MON->m_position.x + MON->m_reservedTopLeft.x + DRAGGINGBORDERSIZE + gapOffset, GAPSIZE)) || - canSnap(sourceX.start, MON->m_position.x + MON->m_reservedTopLeft.x - BORDERDIFF + gapOffset, GAPSIZE))) { - SNAP(sourceX.start, sourceX.end, MON->m_position.x + MON->m_reservedTopLeft.x + DRAGGINGBORDERSIZE + gapOffset); + ((MON->m_reservedTopLeft.x > 0 && canSnap(sourceX.start, monX.start, GAPSIZE)) || + canSnap(sourceX.start, (monX.start -= MON->m_reservedTopLeft.x + BORDERDIFF), GAPSIZE))) { + SNAP(sourceX.start, sourceX.end, monX.start); snaps |= SNAP_LEFT; } if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && - ((MON->m_reservedBottomRight.x > 0 && - canSnap(sourceX.end, MON->m_position.x + MON->m_size.x - MON->m_reservedBottomRight.x - DRAGGINGBORDERSIZE - gapOffset, GAPSIZE)) || - canSnap(sourceX.end, MON->m_position.x + MON->m_size.x - MON->m_reservedBottomRight.x + BORDERDIFF - gapOffset, GAPSIZE))) { - SNAP(sourceX.end, sourceX.start, MON->m_position.x + MON->m_size.x - MON->m_reservedBottomRight.x - DRAGGINGBORDERSIZE - gapOffset); + ((MON->m_reservedBottomRight.x > 0 && canSnap(sourceX.end, monX.end, GAPSIZE)) || + canSnap(sourceX.end, (monX.end += MON->m_reservedBottomRight.x + BORDERDIFF), GAPSIZE))) { + SNAP(sourceX.end, sourceX.start, monX.end); snaps |= SNAP_RIGHT; } if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && - ((MON->m_reservedTopLeft.y > 0 && canSnap(sourceY.start, MON->m_position.y + MON->m_reservedTopLeft.y + DRAGGINGBORDERSIZE + gapOffset, GAPSIZE)) || - canSnap(sourceY.start, MON->m_position.y + MON->m_reservedTopLeft.y - BORDERDIFF + gapOffset, GAPSIZE))) { - SNAP(sourceY.start, sourceY.end, MON->m_position.y + MON->m_reservedTopLeft.y + DRAGGINGBORDERSIZE + gapOffset); + ((MON->m_reservedTopLeft.y > 0 && canSnap(sourceY.start, monY.start, GAPSIZE)) || + canSnap(sourceY.start, (monY.start -= MON->m_reservedTopLeft.y + BORDERDIFF), GAPSIZE))) { + SNAP(sourceY.start, sourceY.end, monY.start); snaps |= SNAP_UP; } if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && - ((MON->m_reservedBottomRight.y > 0 && - canSnap(sourceY.end, MON->m_position.y + MON->m_size.y - MON->m_reservedBottomRight.y - DRAGGINGBORDERSIZE - gapOffset, GAPSIZE)) || - canSnap(sourceY.end, MON->m_position.y + MON->m_size.y - MON->m_reservedBottomRight.y + BORDERDIFF - gapOffset, GAPSIZE))) { - SNAP(sourceY.end, sourceY.start, MON->m_position.y + MON->m_size.y - MON->m_reservedBottomRight.y - DRAGGINGBORDERSIZE - gapOffset); + ((MON->m_reservedBottomRight.y > 0 && canSnap(sourceY.end, monY.end, GAPSIZE)) || + canSnap(sourceY.end, (monY.end += MON->m_reservedBottomRight.y + BORDERDIFF), GAPSIZE))) { + SNAP(sourceY.end, sourceY.start, monY.end); snaps |= SNAP_DOWN; } } @@ -632,17 +634,16 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) { if (*SNAPENABLED && !DRAGGINGWINDOW->m_draggingTiled) performSnap(newPos, newSize, DRAGGINGWINDOW, MBIND_MOVE, -1, m_beginDragSizeXY); - CBox wb = {newPos, newSize}; - wb.round(); + newPos = newPos.round(); if (*PANIMATEMOUSE) - *DRAGGINGWINDOW->m_realPosition = wb.pos(); + *DRAGGINGWINDOW->m_realPosition = newPos; else { - DRAGGINGWINDOW->m_realPosition->setValueAndWarp(wb.pos()); + DRAGGINGWINDOW->m_realPosition->setValueAndWarp(newPos); DRAGGINGWINDOW->sendWindowSize(); } - DRAGGINGWINDOW->m_position = wb.pos(); + DRAGGINGWINDOW->m_position = newPos; } else if (g_pInputManager->m_dragMode == MBIND_RESIZE || g_pInputManager->m_dragMode == MBIND_RESIZE_FORCE_RATIO || g_pInputManager->m_dragMode == MBIND_RESIZE_BLOCK_RATIO) { if (DRAGGINGWINDOW->m_isFloating) { diff --git a/src/layout/IHyprLayout.hpp b/src/layout/IHyprLayout.hpp index fd63d154..1e1141da 100644 --- a/src/layout/IHyprLayout.hpp +++ b/src/layout/IHyprLayout.hpp @@ -1,6 +1,7 @@ #pragma once #include "../defines.hpp" +#include "../managers/input/InputManager.hpp" #include class CWindow; @@ -211,6 +212,11 @@ class IHyprLayout { */ virtual bool updateDragWindow(); + /* + Triggers a window snap event + */ + virtual void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, PHLWINDOW DRAGGINGWINDOW, const eMouseBindMode MODE, const int CORNER, const Vector2D& BEGINSIZE); + private: int m_mouseMoveEventCount; Vector2D m_beginDragXY;