summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2026-02-21 21:30:39 +0000
committerGitHub <noreply@github.com>2026-02-21 21:30:39 +0000
commit723870337f299d4c80aa0048144f3a1ca1c885bd (patch)
treeca3279ae5dbc841f676c64d2f58f895de13cd3bf
parent51f8849e541f0a8150ac3a43fa928c1e299daf18 (diff)
layout: rethonk layouts from the ground up (#12890)
Rewrites layouts to be much smaller, and deal with much less annoying BS. Improves the overall architecture, unifies handling of pseudotiling, and various other improvements.
-rw-r--r--CMakeLists.txt1
-rw-r--r--hyprtester/plugin/src/main.cpp8
-rw-r--r--src/Compositor.cpp138
-rw-r--r--src/Compositor.hpp9
-rw-r--r--src/config/ConfigDescriptions.hpp75
-rw-r--r--src/config/ConfigManager.cpp51
-rw-r--r--src/config/ConfigManager.hpp1
-rw-r--r--src/debug/HyprCtl.cpp109
-rw-r--r--src/defines.hpp4
-rw-r--r--src/desktop/Workspace.cpp45
-rw-r--r--src/desktop/Workspace.hpp8
-rw-r--r--src/desktop/history/WindowHistoryTracker.cpp2
-rw-r--r--src/desktop/rule/windowRule/WindowRule.cpp2
-rw-r--r--src/desktop/rule/windowRule/WindowRuleApplicator.cpp1
-rw-r--r--src/desktop/state/FocusState.cpp30
-rw-r--r--src/desktop/state/FocusState.hpp17
-rw-r--r--src/desktop/view/Group.cpp337
-rw-r--r--src/desktop/view/Group.hpp68
-rw-r--r--src/desktop/view/Window.cpp544
-rw-r--r--src/desktop/view/Window.hpp56
-rw-r--r--src/helpers/Monitor.cpp23
-rw-r--r--src/helpers/math/Direction.cpp0
-rw-r--r--src/helpers/math/Direction.hpp35
-rw-r--r--src/layout/DwindleLayout.cpp1190
-rw-r--r--src/layout/DwindleLayout.hpp110
-rw-r--r--src/layout/IHyprLayout.cpp1062
-rw-r--r--src/layout/IHyprLayout.hpp248
-rw-r--r--src/layout/LayoutManager.cpp344
-rw-r--r--src/layout/LayoutManager.hpp86
-rw-r--r--src/layout/MasterLayout.cpp1526
-rw-r--r--src/layout/MasterLayout.hpp112
-rw-r--r--src/layout/algorithm/Algorithm.cpp264
-rw-r--r--src/layout/algorithm/Algorithm.hpp64
-rw-r--r--src/layout/algorithm/FloatingAlgorithm.cpp18
-rw-r--r--src/layout/algorithm/FloatingAlgorithm.hpp31
-rw-r--r--src/layout/algorithm/ModeAlgorithm.cpp11
-rw-r--r--src/layout/algorithm/ModeAlgorithm.hpp54
-rw-r--r--src/layout/algorithm/TiledAlgorithm.hpp26
-rw-r--r--src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp223
-rw-r--r--src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp26
-rw-r--r--src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp772
-rw-r--r--src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp57
-rw-r--r--src/layout/algorithm/tiled/master/MasterAlgorithm.cpp1292
-rw-r--r--src/layout/algorithm/tiled/master/MasterAlgorithm.hpp75
-rw-r--r--src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp274
-rw-r--r--src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp52
-rw-r--r--src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp293
-rw-r--r--src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp83
-rw-r--r--src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp1412
-rw-r--r--src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp137
-rw-r--r--src/layout/space/Space.cpp185
-rw-r--r--src/layout/space/Space.hpp67
-rw-r--r--src/layout/supplementary/DragController.cpp396
-rw-r--r--src/layout/supplementary/DragController.hpp54
-rw-r--r--src/layout/supplementary/WorkspaceAlgoMatcher.cpp139
-rw-r--r--src/layout/supplementary/WorkspaceAlgoMatcher.hpp46
-rw-r--r--src/layout/target/Target.cpp146
-rw-r--r--src/layout/target/Target.hpp79
-rw-r--r--src/layout/target/WindowGroupTarget.cpp92
-rw-r--r--src/layout/target/WindowGroupTarget.hpp39
-rw-r--r--src/layout/target/WindowTarget.cpp363
-rw-r--r--src/layout/target/WindowTarget.hpp40
-rw-r--r--src/managers/KeybindManager.cpp539
-rw-r--r--src/managers/LayoutManager.cpp60
-rw-r--r--src/managers/LayoutManager.hpp31
-rw-r--r--src/managers/SeatManager.cpp4
-rw-r--r--src/managers/animation/DesktopAnimationManager.cpp3
-rw-r--r--src/managers/input/InputManager.cpp22
-rw-r--r--src/managers/input/InputManager.hpp6
-rw-r--r--src/managers/input/trackpad/gestures/CloseGesture.cpp4
-rw-r--r--src/managers/input/trackpad/gestures/FloatGesture.cpp9
-rw-r--r--src/managers/input/trackpad/gestures/MoveGesture.cpp8
-rw-r--r--src/managers/input/trackpad/gestures/ResizeGesture.cpp6
-rw-r--r--src/plugins/PluginAPI.cpp34
-rw-r--r--src/plugins/PluginAPI.hpp20
-rw-r--r--src/plugins/PluginSystem.cpp8
-rw-r--r--src/plugins/PluginSystem.hpp2
-rw-r--r--src/protocols/ForeignToplevelWlr.cpp2
-rw-r--r--src/render/Renderer.cpp7
-rw-r--r--src/render/decorations/CHyprGroupBarDecoration.cpp122
-rw-r--r--src/render/decorations/DecorationPositioner.cpp4
-rw-r--r--src/render/pass/SurfacePassElement.cpp5
82 files changed, 8426 insertions, 5522 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f1a0087b..f84a8aea 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -110,6 +110,7 @@ add_compile_options(
-Wno-narrowing
-Wno-pointer-arith
-Wno-clobbered
+ -frtti
-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
# disable lto as it may break plugins
diff --git a/hyprtester/plugin/src/main.cpp b/hyprtester/plugin/src/main.cpp
index f8f858b4..6db352fc 100644
--- a/hyprtester/plugin/src/main.cpp
+++ b/hyprtester/plugin/src/main.cpp
@@ -6,8 +6,6 @@
#define private public
#include <src/config/ConfigManager.hpp>
#include <src/config/ConfigDescriptions.hpp>
-#include <src/layout/IHyprLayout.hpp>
-#include <src/managers/LayoutManager.hpp>
#include <src/managers/input/InputManager.hpp>
#include <src/managers/PointerManager.hpp>
#include <src/managers/input/trackpad/TrackpadGestures.hpp>
@@ -15,6 +13,7 @@
#include <src/desktop/rule/windowRule/WindowRuleApplicator.hpp>
#include <src/Compositor.hpp>
#include <src/desktop/state/FocusState.hpp>
+#include <src/layout/LayoutManager.hpp>
#undef private
#include <hyprutils/utils/ScopeGuard.hpp>
@@ -53,8 +52,9 @@ static SDispatchResult snapMove(std::string in) {
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();
+ g_layoutManager->performSnap(pos, size, PLASTWINDOW->layoutTarget(), MBIND_MOVE, -1, size);
+
+ PLASTWINDOW->layoutTarget()->setPositionGlobal(CBox{pos, size});
return {};
}
diff --git a/src/Compositor.cpp b/src/Compositor.cpp
index b722dab2..f05ff2f3 100644
--- a/src/Compositor.cpp
+++ b/src/Compositor.cpp
@@ -7,6 +7,7 @@
#include "desktop/state/FocusState.hpp"
#include "desktop/history/WindowHistoryTracker.hpp"
#include "desktop/history/WorkspaceHistoryTracker.hpp"
+#include "desktop/view/Group.hpp"
#include "helpers/Splashes.hpp"
#include "config/ConfigValue.hpp"
#include "config/ConfigWatcher.hpp"
@@ -62,7 +63,6 @@
#include "managers/EventManager.hpp"
#include "managers/HookSystemManager.hpp"
#include "managers/ProtocolManager.hpp"
-#include "managers/LayoutManager.hpp"
#include "managers/WelcomeManager.hpp"
#include "render/AsyncResourceGatherer.hpp"
#include "plugins/PluginSystem.hpp"
@@ -71,6 +71,8 @@
#include "debug/HyprDebugOverlay.hpp"
#include "helpers/MonitorFrameScheduler.hpp"
#include "i18n/Engine.hpp"
+#include "layout/LayoutManager.hpp"
+#include "layout/target/WindowTarget.hpp"
#include <hyprutils/string/String.hpp>
#include <aquamarine/input/Input.hpp>
@@ -590,7 +592,7 @@ void CCompositor::cleanup() {
g_pHyprRenderer.reset();
g_pHyprOpenGL.reset();
g_pConfigManager.reset();
- g_pLayoutManager.reset();
+ g_layoutManager.reset();
g_pHyprError.reset();
g_pConfigManager.reset();
g_pKeybindManager.reset();
@@ -642,7 +644,7 @@ void CCompositor::initManagers(eManagersInitStage stage) {
g_pHyprError = makeUnique<CHyprError>();
Log::logger->log(Log::DEBUG, "Creating the LayoutManager!");
- g_pLayoutManager = makeUnique<CLayoutManager>();
+ g_layoutManager = makeUnique<Layout::CLayoutManager>();
Log::logger->log(Log::DEBUG, "Creating the TokenManager!");
g_pTokenManager = makeUnique<CTokenManager>();
@@ -1377,8 +1379,8 @@ void CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) {
m_windowsFadingOut.emplace_back(pWindow);
}
-PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) {
- if (!isDirection(dir))
+PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection dir) {
+ if (dir == Math::DIRECTION_DEFAULT)
return nullptr;
const auto PMONITOR = pWindow->m_monitor.lock();
@@ -1395,8 +1397,8 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) {
return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating);
}
-PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow, bool useVectorAngles) {
- if (!isDirection(dir))
+PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow, bool useVectorAngles) {
+ if (dir == Math::DIRECTION_DEFAULT)
return nullptr;
// 0 -> history, 1 -> shared length
@@ -1431,28 +1433,27 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
double intersectLength = -1;
switch (dir) {
- case 'l':
+ case Math::DIRECTION_LEFT:
if (STICKS(POSA.x, POSB.x + SIZEB.x)) {
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
}
break;
- case 'r':
+ case Math::DIRECTION_RIGHT:
if (STICKS(POSA.x + SIZEA.x, POSB.x)) {
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
}
break;
- case 't':
- case 'u':
+ case Math::DIRECTION_UP:
if (STICKS(POSA.y, POSB.y + SIZEB.y)) {
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
}
break;
- case 'b':
- case 'd':
+ case Math::DIRECTION_DOWN:
if (STICKS(POSA.y + SIZEA.y, POSB.y)) {
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
}
break;
+ default: break;
}
if (*PMETHOD == 0 /* history */) {
@@ -1481,12 +1482,8 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
}
}
} else {
- if (dir == 'u')
- dir = 't';
- if (dir == 'd')
- dir = 'b';
-
- static const std::unordered_map<char, Vector2D> VECTORS = {{'r', {1, 0}}, {'t', {0, -1}}, {'b', {0, 1}}, {'l', {-1, 0}}};
+ static const std::unordered_map<Math::eDirection, Vector2D> VECTORS = {
+ {Math::DIRECTION_RIGHT, {1, 0}}, {Math::DIRECTION_UP, {0, -1}}, {Math::DIRECTION_DOWN, {0, 1}}, {Math::DIRECTION_LEFT, {-1, 0}}};
//
auto vectorAngles = [](const Vector2D& a, const Vector2D& b) -> double {
@@ -1665,11 +1662,11 @@ CBox CCompositor::calculateX11WorkArea() {
return workbox;
}
-PHLMONITOR CCompositor::getMonitorInDirection(const char& dir) {
+PHLMONITOR CCompositor::getMonitorInDirection(Math::eDirection dir) {
return getMonitorInDirection(Desktop::focusState()->monitor(), dir);
}
-PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const char& dir) {
+PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::eDirection dir) {
if (!pSourceMonitor)
return nullptr;
@@ -1686,7 +1683,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c
const auto POSB = m->m_position;
const auto SIZEB = m->m_size;
switch (dir) {
- case 'l':
+ case Math::DIRECTION_LEFT:
if (STICKS(POSA.x, POSB.x + SIZEB.x)) {
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
if (INTERSECTLEN > longestIntersect) {
@@ -1695,7 +1692,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c
}
}
break;
- case 'r':
+ case Math::DIRECTION_RIGHT:
if (STICKS(POSA.x + SIZEA.x, POSB.x)) {
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
if (INTERSECTLEN > longestIntersect) {
@@ -1704,8 +1701,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c
}
}
break;
- case 't':
- case 'u':
+ case Math::DIRECTION_UP:
if (STICKS(POSA.y, POSB.y + SIZEB.y)) {
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
if (INTERSECTLEN > longestIntersect) {
@@ -1714,8 +1710,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c
}
}
break;
- case 'b':
- case 'd':
+ case Math::DIRECTION_DOWN:
if (STICKS(POSA.y + SIZEA.y, POSB.y)) {
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
if (INTERSECTLEN > longestIntersect) {
@@ -1724,6 +1719,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c
}
}
break;
+ default: break;
}
}
@@ -1779,7 +1775,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
// additionally, move floating and fs windows manually
if (w->m_isFloating)
- *w->m_realPosition = w->m_realPosition->goal() - pMonitorA->m_position + pMonitorB->m_position;
+ w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorA->m_position + pMonitorB->m_position));
if (w->isFullscreen()) {
*w->m_realPosition = pMonitorB->m_position;
@@ -1804,7 +1800,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
// additionally, move floating and fs windows manually
if (w->m_isFloating)
- *w->m_realPosition = w->m_realPosition->goal() - pMonitorB->m_position + pMonitorA->m_position;
+ w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorB->m_position + pMonitorA->m_position));
if (w->isFullscreen()) {
*w->m_realPosition = pMonitorA->m_position;
@@ -1818,8 +1814,8 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
pMonitorA->m_activeWorkspace = PWORKSPACEB;
pMonitorB->m_activeWorkspace = PWORKSPACEA;
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorA->m_id);
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorB->m_id);
+ g_layoutManager->recalculateMonitor(pMonitorA);
+ g_layoutManager->recalculateMonitor(pMonitorB);
g_pDesktopAnimationManager->setFullscreenFadeAnimation(
PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);
@@ -1831,7 +1827,8 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
Desktop::focusState()->fullWindowFocus(
LASTWIN ? LASTWIN :
(g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(),
- Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING)));
+ Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING)),
+ Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
const auto PNEWWORKSPACE = pMonitorA->m_id == Desktop::focusState()->monitor()->m_id ? PWORKSPACEB : PWORKSPACEA;
g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspace", .data = PNEWWORKSPACE->m_name});
@@ -1853,7 +1850,7 @@ PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) {
if (name == "current")
return Desktop::focusState()->monitor();
else if (isDirection(name))
- return getMonitorInDirection(name[0]);
+ return getMonitorInDirection(Math::fromChar(name[0]));
else if (name[0] == '+' || name[0] == '-') {
// relative
@@ -1989,17 +1986,18 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
if (w->m_isMapped && !w->isHidden()) {
if (POLDMON) {
if (w->m_isFloating)
- *w->m_realPosition = w->m_realPosition->goal() - POLDMON->m_position + pMonitor->m_position;
+ w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-POLDMON->m_position + pMonitor->m_position));
if (w->isFullscreen()) {
*w->m_realPosition = pMonitor->m_position;
*w->m_realSize = pMonitor->m_size;
}
} else
- *w->m_realPosition = Vector2D{
- (pMonitor->m_size.x != 0) ? sc<int>(w->m_realPosition->goal().x) % sc<int>(pMonitor->m_size.x) : 0,
- (pMonitor->m_size.y != 0) ? sc<int>(w->m_realPosition->goal().y) % sc<int>(pMonitor->m_size.y) : 0,
- };
+ w->layoutTarget()->setPositionGlobal(CBox{Vector2D{
+ (pMonitor->m_size.x != 0) ? sc<int>(w->m_realPosition->goal().x) % sc<int>(pMonitor->m_size.x) : 0,
+ (pMonitor->m_size.y != 0) ? sc<int>(w->m_realPosition->goal().y) % sc<int>(pMonitor->m_size.y) : 0,
+ },
+ w->layoutTarget()->position().size()});
}
w->updateToplevel();
@@ -2027,7 +2025,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
pWorkspace->m_events.activeChanged.emit();
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id);
+ g_layoutManager->recalculateMonitor(pMonitor);
g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
pWorkspace->m_visible = true;
@@ -2040,7 +2038,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
// finalize
if (POLDMON) {
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(POLDMON->m_id);
+ g_layoutManager->recalculateMonitor(POLDMON);
if (valid(POLDMON->m_activeWorkspace))
g_pDesktopAnimationManager->setFullscreenFadeAnimation(POLDMON->m_activeWorkspace,
POLDMON->m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :
@@ -2138,15 +2136,16 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie
PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
PWINDOW->updateDecorationValues();
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID());
+ g_layoutManager->recalculateMonitor(PMONITOR);
return;
}
- g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PWINDOW, CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE);
+ PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE;
+ PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE;
+
+ g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE);
PWINDOW->m_fullscreenState.internal = state.internal;
- PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE;
- PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE;
g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc<int>(EFFECTIVE_MODE) != FSMODE_NONE)});
EMIT_HOOK_EVENT("fullscreen", PWINDOW);
@@ -2155,7 +2154,7 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
PWINDOW->updateDecorationValues();
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID());
+ g_layoutManager->recalculateMonitor(PMONITOR);
// make all windows and layers on the same workspace under the fullscreen window
for (auto const& w : m_windows) {
@@ -2268,7 +2267,7 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) {
}
for (auto const& w : g_pCompositor->m_windows) {
- if (!w->m_isMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w)))
+ if (!w->m_isMapped)
continue;
switch (mode) {
@@ -2573,59 +2572,30 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor
setWindowFullscreenInternal(pWindow, FSMODE_NONE);
const PHLWINDOW pFirstWindowOnWorkspace = pWorkspace->getFirstWindow();
- const int visibleWindowsOnWorkspace = pWorkspace->getWindows(std::nullopt, true);
+ const int visibleWindowsOnWorkspace = pWorkspace->getWindows(true, std::nullopt, true);
const auto POSTOMON = pWindow->m_realPosition->goal() - (pWindow->m_monitor ? pWindow->m_monitor->m_position : Vector2D{});
const auto PWORKSPACEMONITOR = pWorkspace->m_monitor.lock();
- if (!pWindow->m_isFloating)
- g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow);
-
pWindow->moveToWorkspace(pWorkspace);
pWindow->m_monitor = pWorkspace->m_monitor;
static auto PGROUPONMOVETOWORKSPACE = CConfigValue<Hyprlang::INT>("group:group_on_movetoworkspace");
- if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow &&
- pFirstWindowOnWorkspace->m_groupData.pNextWindow.lock() && pWindow->canBeGroupedInto(pFirstWindowOnWorkspace)) {
-
- pWindow->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state. Needed to group tiled into floated and vice versa.
- if (!pWindow->m_groupData.pNextWindow.expired()) {
- PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock();
- while (next != pWindow) {
- next->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state of group members
- next = next->m_groupData.pNextWindow.lock();
- }
- }
-
- static auto USECURRPOS = CConfigValue<Hyprlang::INT>("group:insert_after_current");
- (*USECURRPOS ? pFirstWindowOnWorkspace : pFirstWindowOnWorkspace->getGroupTail())->insertWindowToGroup(pWindow);
-
- pFirstWindowOnWorkspace->setGroupCurrent(pWindow);
- pWindow->updateWindowDecos();
- g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow);
-
- if (!pWindow->getDecorationByType(DECORATION_GROUPBAR))
- pWindow->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(pWindow));
-
+ if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && pFirstWindowOnWorkspace->m_group &&
+ pWindow->canBeGroupedInto(pFirstWindowOnWorkspace->m_group)) {
+ pFirstWindowOnWorkspace->m_group->add(pWindow);
} else {
- if (!pWindow->m_isFloating)
- g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow);
-
if (pWindow->m_isFloating)
- *pWindow->m_realPosition = POSTOMON + PWORKSPACEMONITOR->m_position;
+ pWindow->layoutTarget()->setPositionGlobal(CBox{POSTOMON + PWORKSPACEMONITOR->m_position, pWindow->layoutTarget()->position().size()});
}
pWindow->updateToplevel();
pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
pWindow->uncacheWindowDecos();
- pWindow->updateGroupOutputs();
- if (!pWindow->m_groupData.pNextWindow.expired()) {
- PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock();
- while (next != pWindow) {
- next->updateToplevel();
- next = next->m_groupData.pNextWindow.lock();
- }
- }
+ if (pWindow->m_group)
+ pWindow->m_group->updateWorkspace(pWorkspace);
+
+ g_layoutManager->newTarget(pWindow->layoutTarget(), pWorkspace->m_space);
if (FULLSCREEN)
setWindowFullscreenInternal(pWindow, FULLSCREENMODE);
diff --git a/src/Compositor.hpp b/src/Compositor.hpp
index afcda222..9a6d9bd4 100644
--- a/src/Compositor.hpp
+++ b/src/Compositor.hpp
@@ -4,6 +4,7 @@
#include <ranges>
+#include "helpers/math/Direction.hpp"
#include "managers/XWaylandManager.hpp"
#include "managers/KeybindManager.hpp"
#include "managers/SessionLockManager.hpp"
@@ -113,16 +114,16 @@ class CCompositor {
bool isWindowActive(PHLWINDOW);
void changeWindowZOrder(PHLWINDOW, bool);
void cleanupFadingOut(const MONITORID& monid);
- PHLWINDOW getWindowInDirection(PHLWINDOW, char);
- PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false);
+ PHLWINDOW getWindowInDirection(PHLWINDOW, Math::eDirection);
+ PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false);
PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool prev = false);
PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool next = false);
WORKSPACEID getNextAvailableNamedWorkspace();
bool isPointOnAnyMonitor(const Vector2D&);
bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);
CBox calculateX11WorkArea();
- PHLMONITOR getMonitorInDirection(const char&);
- PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&);
+ PHLMONITOR getMonitorInDirection(Math::eDirection);
+ PHLMONITOR getMonitorInDirection(PHLMONITOR, Math::eDirection);
void updateAllWindowsAnimatedDecorationValues();
MONITORID getNextAvailableMonitorID(std::string const& name);
void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false);
diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp
index 80cd1182..ebe13156 100644
--- a/src/config/ConfigDescriptions.hpp
+++ b/src/config/ConfigDescriptions.hpp
@@ -1868,6 +1868,22 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
},
/*
+ * layout:
+ */
+ SConfigOptionDescription{
+ .value = "layout:single_window_aspect_ratio",
+ .description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.",
+ .type = CONFIG_OPTION_VECTOR,
+ .data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}},
+ },
+ SConfigOptionDescription{
+ .value = "layout:single_window_aspect_ratio_tolerance",
+ .description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.",
+ .type = CONFIG_OPTION_FLOAT,
+ .data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f},
+ },
+
+ /*
* dwindle:
*/
@@ -1946,18 +1962,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
- SConfigOptionDescription{
- .value = "dwindle:single_window_aspect_ratio",
- .description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.",
- .type = CONFIG_OPTION_VECTOR,
- .data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}},
- },
- SConfigOptionDescription{
- .value = "dwindle:single_window_aspect_ratio_tolerance",
- .description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.",
- .type = CONFIG_OPTION_FLOAT,
- .data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f},
- },
/*
* master:
@@ -2044,6 +2048,53 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
},
/*
+ * scrolling:
+ */
+
+ SConfigOptionDescription{
+ .value = "scrolling:fullscreen_on_one_column",
+ .description = "when enabled, a single column on a workspace will always span the entire screen.",
+ .type = CONFIG_OPTION_BOOL,
+ .data = SConfigOptionDescription::SBoolData{true},
+ },
+ SConfigOptionDescription{
+ .value = "scrolling:column_width",
+ .description = "the default width of a column, [0.1 - 1.0].",
+ .type = CONFIG_OPTION_FLOAT,
+ .data = SConfigOptionDescription::SFloatData{.value = 0.5, .min = 0.1, .max = 1.0},
+ },
+ SConfigOptionDescription{
+ .value = "scrolling:focus_fit_method",
+ .description = "When a column is focused, what method should be used to bring it into view",
+ .type = CONFIG_OPTION_CHOICE,
+ .data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "center,fit"},
+ },
+ SConfigOptionDescription{
+ .value = "scrolling:follow_focus",
+ .description = "when a window is focused, should the layout move to bring it into view automatically",
+ .type = CONFIG_OPTION_BOOL,
+ .data = SConfigOptionDescription::SBoolData{.value = true},
+ },
+ SConfigOptionDescription{
+ .value = "scrolling:follow_min_visible",
+ .description = "when a window is focused, require that at least a given fraction of it is visible for focus to follow",
+ .type = CONFIG_OPTION_FLOAT,
+ .data = SConfigOptionDescription::SFloatData{.value = 0.4, .min = 0.0, .max = 1.0},
+ },
+ SConfigOptionDescription{
+ .value = "scrolling:explicit_column_widths",
+ .description = "A comma-separated list of preconfigured widths for colresize +conf/-conf",
+ .type = CONFIG_OPTION_STRING_SHORT,
+ .data = SConfigOptionDescription::SStringData{"0.333, 0.5, 0.667, 1.0"},
+ },
+ SConfigOptionDescription{
+ .value = "scrolling:direction",
+ .description = "Direction in which new windows appear and the layout scrolls",
+ .type = CONFIG_OPTION_CHOICE,
+ .data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "right,left,down,up"},
+ },
+
+ /*
* Quirks
*/
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
index f9fd107d..4bcbf45a 100644
--- a/src/config/ConfigManager.cpp
+++ b/src/config/ConfigManager.cpp
@@ -18,13 +18,14 @@
#include "../desktop/rule/layerRule/LayerRule.hpp"
#include "../debug/HyprCtl.hpp"
#include "../desktop/state/FocusState.hpp"
+#include "../layout/space/Space.hpp"
+#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include "defaultConfig.hpp"
#include "../render/Renderer.hpp"
#include "../hyprerror/HyprError.hpp"
#include "../managers/input/InputManager.hpp"
#include "../managers/eventLoop/EventLoopManager.hpp"
-#include "../managers/LayoutManager.hpp"
#include "../managers/EventManager.hpp"
#include "../managers/permissions/DynamicPermissionManager.hpp"
#include "../debug/HyprNotificationOverlay.hpp"
@@ -616,6 +617,9 @@ CConfigManager::CConfigManager() {
registerConfigVar("decoration:screen_shader", {STRVAL_EMPTY});
registerConfigVar("decoration:border_part_of_window", Hyprlang::INT{1});
+ registerConfigVar("layout:single_window_aspect_ratio", Hyprlang::VEC2{0, 0});
+ registerConfigVar("layout:single_window_aspect_ratio_tolerance", {0.1f});
+
registerConfigVar("dwindle:pseudotile", Hyprlang::INT{0});
registerConfigVar("dwindle:force_split", Hyprlang::INT{0});
registerConfigVar("dwindle:permanent_direction_override", Hyprlang::INT{0});
@@ -628,8 +632,6 @@ CConfigManager::CConfigManager() {
registerConfigVar("dwindle:smart_split", Hyprlang::INT{0});
registerConfigVar("dwindle:smart_resizing", Hyprlang::INT{1});
registerConfigVar("dwindle:precise_mouse_move", Hyprlang::INT{0});
- registerConfigVar("dwindle:single_window_aspect_ratio", Hyprlang::VEC2{0, 0});
- registerConfigVar("dwindle:single_window_aspect_ratio_tolerance", {0.1f});
registerConfigVar("master:special_scale_factor", {1.f});
registerConfigVar("master:mfact", {0.55f});
@@ -645,6 +647,14 @@ CConfigManager::CConfigManager() {
registerConfigVar("master:drop_at_cursor", Hyprlang::INT{1});
registerConfigVar("master:always_keep_position", Hyprlang::INT{0});
+ registerConfigVar("scrolling:fullscreen_on_one_column", Hyprlang::INT{1});
+ registerConfigVar("scrolling:column_width", Hyprlang::FLOAT{0.5F});
+ registerConfigVar("scrolling:focus_fit_method", Hyprlang::INT{1});
+ registerConfigVar("scrolling:follow_focus", Hyprlang::INT{1});
+ registerConfigVar("scrolling:follow_min_visible", Hyprlang::FLOAT{0.4});
+ registerConfigVar("scrolling:explicit_column_widths", Hyprlang::STRING{"0.333, 0.5, 0.667, 1.0"});
+ registerConfigVar("scrolling:direction", Hyprlang::STRING{"right"});
+
registerConfigVar("animations:enabled", Hyprlang::INT{1});
registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0});
@@ -715,9 +725,9 @@ CConfigManager::CConfigManager() {
registerConfigVar("binds:movefocus_cycles_fullscreen", Hyprlang::INT{0});
registerConfigVar("binds:movefocus_cycles_groupfirst", Hyprlang::INT{0});
registerConfigVar("binds:disable_keybind_grabbing", Hyprlang::INT{0});
- registerConfigVar("binds:window_direction_monitor_fallback", Hyprlang::INT{1});
registerConfigVar("binds:allow_pin_fullscreen", Hyprlang::INT{0});
registerConfigVar("binds:drag_threshold", Hyprlang::INT{0});
+ registerConfigVar("binds:window_direction_monitor_fallback", Hyprlang::INT{1});
registerConfigVar("gestures:workspace_swipe_distance", Hyprlang::INT{300});
registerConfigVar("gestures:workspace_swipe_invert", Hyprlang::INT{1});
@@ -1338,7 +1348,8 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {
static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor");
for (auto const& m : g_pCompositor->m_monitors) {
*(m->m_cursorZoom) = *PZOOMFACTOR;
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
+ if (m->m_activeWorkspace)
+ m->m_activeWorkspace->m_space->recalculate();
}
// Update the keyboard layout to the cfg'd one if this is not the first launch
@@ -1406,9 +1417,6 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {
// Update window border colors
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
- // update layout
- g_pLayoutManager->switchToLayout(std::any_cast<Hyprlang::STRING>(m_config->getConfigValue("general:layout")));
-
// manual crash
if (std::any_cast<Hyprlang::INT>(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) {
m_manualCrashInitiated = true;
@@ -1447,6 +1455,9 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {
if (!m_isFirstLaunch)
ensurePersistentWorkspacesPresent();
+ // update layouts
+ Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts();
+
EMIT_HOOK_EVENT("configReloaded", nullptr);
if (g_pEventManager)
g_pEventManager->postEvent(SHyprIPCEvent{"configreloaded", ""});
@@ -1469,8 +1480,9 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std::
// invalidate layouts if they changed
if (COMMAND == "monitor" || COMMAND.contains("gaps_") || COMMAND.starts_with("dwindle:") || COMMAND.starts_with("master:")) {
- for (auto const& m : g_pCompositor->m_monitors)
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
+ for (auto const& m : g_pCompositor->m_monitors) {
+ g_layoutManager->recalculateMonitor(m);
+ }
}
// Update window border colors
@@ -1642,6 +1654,8 @@ SWorkspaceRule CConfigManager::mergeWorkspaceRules(const SWorkspaceRule& rule1,
mergedRule.onCreatedEmptyRunCmd = rule2.onCreatedEmptyRunCmd;
if (rule2.defaultName.has_value())
mergedRule.defaultName = rule2.defaultName;
+ if (rule2.layout.has_value())
+ mergedRule.layout = rule2.layout;
if (!rule2.layoutopts.empty()) {
for (const auto& layoutopt : rule2.layoutopts) {
mergedRule.layoutopts[layoutopt.first] = layoutopt.second;
@@ -2710,6 +2724,9 @@ std::optional<std::string> CConfigManager::handleWorkspaceRules(const std::strin
opt = opt.substr(0, opt.find(':'));
wsRule.layoutopts[opt] = val;
+ } else if ((delim = rule.find("layout:")) != std::string::npos) {
+ std::string layout = rule.substr(delim + 7);
+ wsRule.layout = std::move(layout);
}
return {};
@@ -3072,20 +3089,20 @@ std::string SConfigOptionDescription::jsonify() const {
else if (typeid(Hyprlang::FLOAT) == std::type_index(CONFIGVALUE.type()))
currentValue = std::format("{:.2f}", std::any_cast<Hyprlang::FLOAT>(CONFIGVALUE));
else if (typeid(Hyprlang::STRING) == std::type_index(CONFIGVALUE.type()))
- currentValue = std::any_cast<Hyprlang::STRING>(CONFIGVALUE);
+ currentValue = std::format("\"{}\"", std::any_cast<Hyprlang::STRING>(CONFIGVALUE));
else if (typeid(Hyprlang::VEC2) == std::type_index(CONFIGVALUE.type())) {
const auto V = std::any_cast<Hyprlang::VEC2>(CONFIGVALUE);
- currentValue = std::format("{}, {}", V.x, V.y);
+ currentValue = std::format("\"{}, {}\"", V.x, V.y);
} else if (typeid(void*) == std::type_index(CONFIGVALUE.type())) {
const auto DATA = sc<ICustomConfigValueData*>(std::any_cast<void*>(CONFIGVALUE));
- currentValue = DATA->toString();
+ currentValue = std::format("\"{}\"", DATA->toString());
}
try {
using T = std::decay_t<decltype(val)>;
if constexpr (std::is_same_v<T, SStringData>) {
return std::format(R"#( "value": "{}",
- "current": "{}",
+ "current": {},
"explicit": {})#",
val.value, currentValue, EXPLICIT);
} else if constexpr (std::is_same_v<T, SRangeData>) {
@@ -3104,7 +3121,7 @@ std::string SConfigOptionDescription::jsonify() const {
val.value, val.min, val.max, currentValue, EXPLICIT);
} else if constexpr (std::is_same_v<T, SColorData>) {
return std::format(R"#( "value": "{}",
- "current": "{}",
+ "current": {},
"explicit": {})#",
val.color.getAsHex(), currentValue, EXPLICIT);
} else if constexpr (std::is_same_v<T, SBoolData>) {
@@ -3125,12 +3142,12 @@ std::string SConfigOptionDescription::jsonify() const {
"min_y": {},
"max_x": {},
"max_y": {},
- "current": "{}",
+ "current": {},
"explicit": {})#",
val.vec.x, val.vec.y, val.min.x, val.min.y, val.max.x, val.max.y, currentValue, EXPLICIT);
} else if constexpr (std::is_same_v<T, SGradientData>) {
return std::format(R"#( "value": "{}",
- "current": "{}",
+ "current": {},
"explicit": {})#",
val.gradient, currentValue, EXPLICIT);
}
diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp
index 83fef7b0..21a3c58c 100644
--- a/src/config/ConfigManager.hpp
+++ b/src/config/ConfigManager.hpp
@@ -46,6 +46,7 @@ struct SWorkspaceRule {
std::optional<bool> noShadow;
std::optional<std::string> onCreatedEmptyRunCmd;
std::optional<std::string> defaultName;
+ std::optional<std::string> layout;
std::map<std::string, std::string> layoutopts;
};
diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp
index 514315f5..f3a9402d 100644
--- a/src/debug/HyprCtl.cpp
+++ b/src/debug/HyprCtl.cpp
@@ -44,6 +44,7 @@ using namespace Hyprutils::OS;
#include "config/ConfigManager.hpp"
#include "helpers/MiscFunctions.hpp"
#include "../desktop/view/LayerSurface.hpp"
+#include "../desktop/view/Group.hpp"
#include "../desktop/rule/Engine.hpp"
#include "../desktop/history/WindowHistoryTracker.hpp"
#include "../desktop/state/FocusState.hpp"
@@ -52,12 +53,15 @@ using namespace Hyprutils::OS;
#include "../Compositor.hpp"
#include "../managers/input/InputManager.hpp"
#include "../managers/XWaylandManager.hpp"
-#include "../managers/LayoutManager.hpp"
#include "../plugins/PluginSystem.hpp"
#include "../managers/animation/AnimationManager.hpp"
#include "../debug/HyprNotificationOverlay.hpp"
#include "../render/Renderer.hpp"
#include "../render/OpenGL.hpp"
+#include "../layout/space/Space.hpp"
+#include "../layout/algorithm/Algorithm.hpp"
+#include "../layout/algorithm/TiledAlgorithm.hpp"
+#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#if defined(__DragonFly__) || defined(__FreeBSD__)
#include <sys/ucred.h>
@@ -330,23 +334,19 @@ static std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) {
static std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) {
const bool isJson = format == eHyprCtlOutputFormat::FORMAT_JSON;
- if (w->m_groupData.pNextWindow.expired())
+ if (!w->m_group)
return isJson ? "" : "0";
std::ostringstream result;
- PHLWINDOW head = w->getGroupHead();
- PHLWINDOW curr = head;
- while (true) {
+ for (const auto& curr : w->m_group->windows()) {
if (isJson)
result << std::format("\"0x{:x}\"", rc<uintptr_t>(curr.get()));
else
result << std::format("{:x}", rc<uintptr_t>(curr.get()));
- curr = curr->m_groupData.pNextWindow.lock();
- // We've wrapped around to the start, break out without trailing comma
- if (curr == head)
- break;
- result << (isJson ? ", " : ",");
+
+ if (curr != w->m_group->windows().back())
+ result << (isJson ? ", " : ",");
}
return result.str();
@@ -375,7 +375,6 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
"name": "{}"
}},
"floating": {},
- "pseudo": {},
"monitor": {},
"class": "{}",
"title": "{}",
@@ -399,15 +398,15 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
}},)#",
rc<uintptr_t>(w.get()), (w->m_isMapped ? "true" : "false"), (w->isHidden() ? "true" : "false"), sc<int>(w->m_realPosition->goal().x),
sc<int>(w->m_realPosition->goal().y), sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID,
- escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name), (sc<int>(w->m_isFloating) == 1 ? "true" : "false"), (w->m_isPseudotiled ? "true" : "false"),
- w->monitorID(), escapeJSONStrings(w->m_class), escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(),
- (sc<int>(w->m_isX11) == 1 ? "true" : "false"), (w->m_pinned ? "true" : "false"), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client),
- (w->m_createdOverFullscreen ? "true" : "false"), getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w),
+ escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name), (sc<int>(w->m_isFloating) == 1 ? "true" : "false"), w->monitorID(), escapeJSONStrings(w->m_class),
+ escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(), (sc<int>(w->m_isX11) == 1 ? "true" : "false"),
+ (w->m_pinned ? "true" : "false"), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client), (w->m_createdOverFullscreen ? "true" : "false"),
+ getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w),
(g_pInputManager->isWindowInhibiting(w, false) ? "true" : "false"), escapeJSONStrings(w->xdgTag().value_or("")), escapeJSONStrings(w->xdgDescription().value_or("")),
escapeJSONStrings(NContentType::toString(w->getContentType())), w->m_stableID);
} else {
return std::format(
- "Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tpseudo: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: "
+ "Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: "
"{}\n\tinitialClass: {}\n\tinitialTitle: {}\n\tpid: "
"{}\n\txwayland: {}\n\tpinned: "
"{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\toverFullscreen: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: "
@@ -415,11 +414,10 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
"{}\n\txdgDescription: {}\n\tcontentType: {}\n\tstableID: {:x}\n\n",
rc<uintptr_t>(w.get()), w->m_title, sc<int>(w->m_isMapped), sc<int>(w->isHidden()), sc<int>(w->m_realPosition->goal().x), sc<int>(w->m_realPosition->goal().y),
sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID,
- (!w->m_workspace ? "" : w->m_workspace->m_name), sc<int>(w->m_isFloating), sc<int>(w->m_isPseudotiled), w->monitorID(), w->m_class, w->m_title, w->m_initialClass,
- w->m_initialTitle, w->getPID(), sc<int>(w->m_isX11), sc<int>(w->m_pinned), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client),
- sc<int>(w->m_createdOverFullscreen), getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w),
- sc<int>(g_pInputManager->isWindowInhibiting(w, false)), w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType()),
- w->m_stableID);
+ (!w->m_workspace ? "" : w->m_workspace->m_name), sc<int>(w->m_isFloating), w->monitorID(), w->m_class, w->m_title, w->m_initialClass, w->m_initialTitle, w->getPID(),
+ sc<int>(w->m_isX11), sc<int>(w->m_pinned), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client), sc<int>(w->m_createdOverFullscreen),
+ getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w), sc<int>(g_pInputManager->isWindowInhibiting(w, false)),
+ w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType()), w->m_stableID);
}
}
@@ -453,8 +451,15 @@ static std::string clientsRequest(eHyprCtlOutputFormat format, std::string reque
}
std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format) {
- const auto PLASTW = w->getLastFocusedWindow();
- const auto PMONITOR = w->m_monitor.lock();
+ const auto PLASTW = w->getLastFocusedWindow();
+ const auto PMONITOR = w->m_monitor.lock();
+
+ std::string layoutName = "unknown";
+ if (w->m_space && w->m_space->algorithm() && w->m_space->algorithm()->tiledAlgo()) {
+ const auto& TILED_ALGO = w->m_space->algorithm()->tiledAlgo();
+ layoutName = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*TILED_ALGO.get()));
+ }
+
if (format == eHyprCtlOutputFormat::FORMAT_JSON) {
return std::format(R"#({{
"id": {},
@@ -465,16 +470,17 @@ std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat form
"hasfullscreen": {},
"lastwindow": "0x{:x}",
"lastwindowtitle": "{}",
- "ispersistent": {}
+ "ispersistent": {},
+ "tiledLayout": "{}"
}})#",
w->m_id, escapeJSONStrings(w->m_name), escapeJSONStrings(PMONITOR ? PMONITOR->m_name : "?"),
escapeJSONStrings(PMONITOR ? std::to_string(PMONITOR->m_id) : "null"), w->getWindows(), w->m_hasFullscreenWindow ? "true" : "false",
- rc<uintptr_t>(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false");
+ rc<uintptr_t>(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false", escapeJSONStrings(layoutName));
} else {
- return std::format(
- "workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: {}\n\n",
- w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(), sc<int>(w->m_hasFullscreenWindow),
- rc<uintptr_t>(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc<int>(w->isPersistent()));
+ return std::format("workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: "
+ "{}\n\ttiledLayout: {}\n\n",
+ w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(),
+ sc<int>(w->m_hasFullscreenWindow), rc<uintptr_t>(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc<int>(w->isPersistent()), layoutName);
}
}
@@ -673,28 +679,6 @@ static std::string layersRequest(eHyprCtlOutputFormat format, std::string reques
return result;
}
-static std::string layoutsRequest(eHyprCtlOutputFormat format, std::string request) {
- std::string result = "";
- if (format == eHyprCtlOutputFormat::FORMAT_JSON) {
- result += "[";
-
- for (auto const& m : g_pLayoutManager->getAllLayoutNames()) {
- result += std::format(
- R"#(
- "{}",)#",
- m);
- }
- trimTrailingComma(result);
-
- result += "\n]\n";
- } else {
- for (auto const& m : g_pLayoutManager->getAllLayoutNames()) {
- result += std::format("{}\n", m);
- }
- }
- return result;
-}
-
static std::string configErrorsRequest(eHyprCtlOutputFormat format, std::string request) {
std::string result = "";
std::string currErrors = g_pConfigManager->getErrors();
@@ -1316,10 +1300,8 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
g_pInputManager->setTabletConfigs(); // update tablets
}
- static auto PLAYOUT = CConfigValue<std::string>("general:layout");
-
- if (COMMAND.contains("general:layout"))
- g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout
+ if (COMMAND.contains("general:layout") || (COMMAND.contains("workspace") && VALUE.contains("layout:")))
+ Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts();
if (COMMAND.contains("decoration:screen_shader") || COMMAND == "source")
g_pHyprOpenGL->m_reloadScreenShader = true;
@@ -1339,7 +1321,8 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
for (auto const& m : g_pCompositor->m_monitors) {
*(m->m_cursorZoom) = *PZOOMFACTOR;
g_pHyprRenderer->damageMonitor(m);
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
+ if (m->m_activeWorkspace)
+ m->m_activeWorkspace->m_space->recalculate();
}
}
@@ -1613,7 +1596,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
static auto PGROUPACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_active");
static auto PGROUPINACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_inactive");
- const bool GROUPLOCKED = PWINDOW->m_groupData.pNextWindow.lock() ? PWINDOW->getGroupHead()->m_groupData.locked : false;
+ const bool GROUPLOCKED = PWINDOW->m_group ? PWINDOW->m_group->locked() : false;
if (active) {
auto* const ACTIVECOL = (CGradientValueData*)(PACTIVECOL.ptr())->getData();
@@ -1621,7 +1604,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
auto* const GROUPACTIVECOL = (CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData();
auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData();
const auto* const ACTIVECOLOR =
- !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
+ !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString();
if (FORMNORM)
@@ -1633,8 +1616,8 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
auto* const NOGROUPINACTIVECOL = (CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData();
auto* const GROUPINACTIVECOL = (CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData();
auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData();
- const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) :
- (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
+ const auto* const INACTIVECOLOR = !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) :
+ (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
std::string borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString();
if (FORMNORM)
@@ -2089,7 +2072,6 @@ CHyprCtl::CHyprCtl() {
registerCommand(SHyprCtlCommand{"systeminfo", true, systemInfoRequest});
registerCommand(SHyprCtlCommand{"animations", true, animationsRequest});
registerCommand(SHyprCtlCommand{"rollinglog", true, rollinglogRequest});
- registerCommand(SHyprCtlCommand{"layouts", true, layoutsRequest});
registerCommand(SHyprCtlCommand{"configerrors", true, configErrorsRequest});
registerCommand(SHyprCtlCommand{"locked", true, getIsLocked});
registerCommand(SHyprCtlCommand{"descriptions", true, getDescriptions});
@@ -2202,10 +2184,6 @@ std::string CHyprCtl::getReply(std::string request) {
g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs
g_pInputManager->setTabletConfigs(); // update tablets
- static auto PLAYOUT = CConfigValue<std::string>("general:layout");
-
- g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout
-
g_pHyprOpenGL->m_reloadScreenShader = true;
for (auto& [m, rd] : g_pHyprOpenGL->m_monitorRenderResources) {
@@ -2221,7 +2199,6 @@ std::string CHyprCtl::getReply(std::string request) {
for (auto const& m : g_pCompositor->m_monitors) {
g_pHyprRenderer->damageMonitor(m);
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
}
}
diff --git a/src/defines.hpp b/src/defines.hpp
index cd4c524d..571679dc 100644
--- a/src/defines.hpp
+++ b/src/defines.hpp
@@ -5,3 +5,7 @@
#include "helpers/Color.hpp"
#include "macros.hpp"
#include "desktop/DesktopTypes.hpp"
+
+#if !defined(__GXX_RTTI)
+#error "Hyprland requires C++ RTTI. Shit will hit the fan otherwise. Do not even try."
+#endif
diff --git a/src/desktop/Workspace.cpp b/src/desktop/Workspace.cpp
index 2895137d..f6e5288e 100644
--- a/src/desktop/Workspace.cpp
+++ b/src/desktop/Workspace.cpp
@@ -1,10 +1,13 @@
#include "Workspace.hpp"
+#include "view/Group.hpp"
#include "../Compositor.hpp"
#include "../config/ConfigValue.hpp"
#include "config/ConfigManager.hpp"
#include "managers/animation/AnimationManager.hpp"
#include "../managers/EventManager.hpp"
#include "../managers/HookSystemManager.hpp"
+#include "../layout/space/Space.hpp"
+#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/string/String.hpp>
@@ -41,6 +44,9 @@ void CWorkspace::init(PHLWORKSPACE self) {
m_lastFocusedWindow.reset();
});
+ m_space = Layout::CSpace::create(m_self.lock());
+ m_space->setAlgorithmProvider(Layout::Supplementary::algoMatcher()->createAlgorithmForWorkspace(m_self.lock()));
+
m_inert = false;
const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self);
@@ -406,14 +412,19 @@ bool CWorkspace::isVisibleNotCovered() {
int CWorkspace::getWindows(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) {
int no = 0;
- for (auto const& w : g_pCompositor->m_windows) {
- if (w->workspaceID() != m_id || !w->m_isMapped)
+
+ if (!m_space)
+ return 0;
+
+ for (auto const& t : m_space->targets()) {
+ if (!t)
continue;
- if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value())
+
+ if (onlyTiled.has_value() && t->floating() == onlyTiled.value())
continue;
- if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value())
+ if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value()))
continue;
- if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value())
+ if (onlyVisible.has_value() && (!t->window() || t->window()->isHidden() == onlyVisible.value()))
continue;
no++;
}
@@ -423,16 +434,16 @@ int CWorkspace::getWindows(std::optional<bool> onlyTiled, std::optional<bool> on
int CWorkspace::getGroups(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) {
int no = 0;
- for (auto const& w : g_pCompositor->m_windows) {
- if (w->workspaceID() != m_id || !w->m_isMapped)
- continue;
- if (!w->m_groupData.head)
+ for (auto const& g : Desktop::View::groups()) {
+ const auto HEAD = g->head();
+
+ if (HEAD->workspaceID() != m_id || !HEAD->m_isMapped)
continue;
- if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value())
+ if (onlyTiled.has_value() && HEAD->m_isFloating == onlyTiled.value())
continue;
- if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value())
+ if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value())
continue;
- if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value())
+ if (onlyVisible.has_value() && g->current()->isHidden() == onlyVisible.value())
continue;
no++;
}
@@ -514,13 +525,11 @@ void CWorkspace::rename(const std::string& name) {
}
void CWorkspace::updateWindows() {
- m_hasFullscreenWindow = std::ranges::any_of(g_pCompositor->m_windows, [this](const auto& w) { return w->m_isMapped && w->m_workspace == m_self && w->isFullscreen(); });
-
- for (auto const& w : g_pCompositor->m_windows) {
- if (!w->m_isMapped || w->m_workspace != m_self)
- continue;
+ m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; });
- w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
+ for (auto const& t : m_space->targets()) {
+ if (t->window())
+ t->window()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
}
}
diff --git a/src/desktop/Workspace.hpp b/src/desktop/Workspace.hpp
index 1aad1aaa..c39e928f 100644
--- a/src/desktop/Workspace.hpp
+++ b/src/desktop/Workspace.hpp
@@ -6,6 +6,10 @@
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/signal/Signal.hpp"
+namespace Layout {
+ class CSpace;
+};
+
enum eFullscreenMode : int8_t {
FSMODE_NONE = 0,
FSMODE_MAXIMIZED = 1 << 0,
@@ -20,7 +24,9 @@ class CWorkspace {
CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special = false, bool isEmpty = true);
~CWorkspace();
- WP<CWorkspace> m_self;
+ WP<CWorkspace> m_self;
+
+ SP<Layout::CSpace> m_space;
// Workspaces ID-based have IDs > 0
// and workspaces name-based have IDs starting with -1337
diff --git a/src/desktop/history/WindowHistoryTracker.cpp b/src/desktop/history/WindowHistoryTracker.cpp
index 5dc0742f..edaa2b5e 100644
--- a/src/desktop/history/WindowHistoryTracker.cpp
+++ b/src/desktop/history/WindowHistoryTracker.cpp
@@ -20,7 +20,7 @@ CWindowHistoryTracker::CWindowHistoryTracker() {
});
static auto P1 = g_pHookSystem->hookDynamic("activeWindow", [this](void* self, SCallbackInfo& info, std::any data) {
- auto window = std::any_cast<PHLWINDOW>(data);
+ auto window = std::any_cast<Desktop::View::SWindowActiveEvent>(data).window;
track(window);
});
diff --git a/src/desktop/rule/windowRule/WindowRule.cpp b/src/desktop/rule/windowRule/WindowRule.cpp
index 3511e0f9..fdc2de62 100644
--- a/src/desktop/rule/windowRule/WindowRule.cpp
+++ b/src/desktop/rule/windowRule/WindowRule.cpp
@@ -77,7 +77,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
return false;
break;
case RULE_PROP_GROUP:
- if (!engine->match(w->m_groupData.pNextWindow))
+ if (!engine->match(!!w->m_group))
return false;
break;
case RULE_PROP_MODAL:
diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp
index 037f8938..a30b65a8 100644
--- a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp
+++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp
@@ -4,7 +4,6 @@
#include "../utils/SetUtils.hpp"
#include "../../view/Window.hpp"
#include "../../types/OverridableVar.hpp"
-#include "../../../managers/LayoutManager.hpp"
#include "../../../managers/HookSystemManager.hpp"
#include <hyprutils/string/String.hpp>
diff --git a/src/desktop/state/FocusState.cpp b/src/desktop/state/FocusState.cpp
index 0712fc10..90524b74 100644
--- a/src/desktop/state/FocusState.cpp
+++ b/src/desktop/state/FocusState.cpp
@@ -3,15 +3,19 @@
#include "../../Compositor.hpp"
#include "../../protocols/XDGShell.hpp"
#include "../../render/Renderer.hpp"
-#include "../../managers/LayoutManager.hpp"
#include "../../managers/EventManager.hpp"
#include "../../managers/HookSystemManager.hpp"
+#include "../../managers/input/InputManager.hpp"
+#include "../../managers/SeatManager.hpp"
#include "../../xwayland/XSurface.hpp"
#include "../../protocols/PointerConstraints.hpp"
#include "managers/animation/DesktopAnimationManager.hpp"
+#include "../../layout/LayoutManager.hpp"
using namespace Desktop;
+#define COMMA ,
+
SP<CFocusState> Desktop::focusState() {
static SP<CFocusState> state = makeShared<CFocusState>();
return state;
@@ -63,7 +67,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO
return {};
}
-void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surface, bool forceFSCycle) {
+void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLSurfaceResource> surface, bool forceFSCycle) {
if (pWindow) {
if (!pWindow->m_workspace)
return;
@@ -83,10 +87,10 @@ void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surf
return;
}
- rawWindowFocus(pWindow, surface);
+ rawWindowFocus(pWindow, reason, surface);
}
-void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surface) {
+void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLSurfaceResource> surface) {
static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>("input:follow_mouse");
static auto PSPECIALFALLTHROUGH = CConfigValue<Hyprlang::INT>("input:special_fallthrough");
@@ -105,7 +109,9 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surfa
if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus())
return;
- g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow);
+ // m_target on purpose, this avoids the group
+ if (pWindow)
+ g_layoutManager->bringTargetToTop(pWindow->m_target);
if (!pWindow || !validMapped(pWindow)) {
@@ -127,9 +133,7 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surfa
g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","});
g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""});
- EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr});
-
- g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr);
+ EMIT_HOOK_EVENT("activeWindow", Desktop::View::SWindowActiveEvent{nullptr COMMA reason});
m_focusSurface.reset();
@@ -196,16 +200,14 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surfa
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = pWindow->m_class + "," + pWindow->m_title});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(pWindow.get()))});
- EMIT_HOOK_EVENT("activeWindow", pWindow);
-
- g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(pWindow);
+ EMIT_HOOK_EVENT("activeWindow", Desktop::View::SWindowActiveEvent{pWindow COMMA reason});
g_pInputManager->recheckIdleInhibitorStatus();
if (*PFOLLOWMOUSE == 0)
g_pInputManager->sendMotionEventsToFocused();
- if (pWindow->m_groupData.pNextWindow)
+ if (pWindow->m_group)
pWindow->deactivateGroupMembers();
}
@@ -296,3 +298,7 @@ void CFocusState::resetWindowFocus() {
m_focusWindow.reset();
m_focusSurface.reset();
}
+
+bool Desktop::isHardInputFocusReason(eFocusReason r) {
+ return r == FOCUS_REASON_NEW_WINDOW || r == FOCUS_REASON_KEYBIND || r == FOCUS_REASON_GHOSTS || r == FOCUS_REASON_CLICK || r == FOCUS_REASON_DESKTOP_STATE_CHANGE;
+}
diff --git a/src/desktop/state/FocusState.hpp b/src/desktop/state/FocusState.hpp
index 93ab2215..76a3538f 100644
--- a/src/desktop/state/FocusState.hpp
+++ b/src/desktop/state/FocusState.hpp
@@ -6,6 +6,19 @@
class CWLSurfaceResource;
namespace Desktop {
+ enum eFocusReason : uint8_t {
+ FOCUS_REASON_UNKNOWN = 0,
+ FOCUS_REASON_FFM,
+ FOCUS_REASON_KEYBIND,
+ FOCUS_REASON_CLICK,
+ FOCUS_REASON_OTHER,
+ FOCUS_REASON_DESKTOP_STATE_CHANGE,
+ FOCUS_REASON_NEW_WINDOW,
+ FOCUS_REASON_GHOSTS,
+ };
+
+ bool isHardInputFocusReason(eFocusReason r);
+
class CFocusState {
public:
CFocusState();
@@ -15,8 +28,8 @@ namespace Desktop {
CFocusState(CFocusState&) = delete;
CFocusState(const CFocusState&) = delete;
- void fullWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false);
- void rawWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr);
+ void fullWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false);
+ void rawWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr);
void rawSurfaceFocus(SP<CWLSurfaceResource> s, PHLWINDOW pWindowOwner = nullptr);
void rawMonitorFocus(PHLMONITOR m);
diff --git a/src/desktop/view/Group.cpp b/src/desktop/view/Group.cpp
new file mode 100644
index 00000000..67a89986
--- /dev/null
+++ b/src/desktop/view/Group.cpp
@@ -0,0 +1,337 @@
+#include "Group.hpp"
+#include "Window.hpp"
+
+#include "../../render/decorations/CHyprGroupBarDecoration.hpp"
+#include "../../layout/target/WindowGroupTarget.hpp"
+#include "../../layout/target/WindowTarget.hpp"
+#include "../../layout/target/Target.hpp"
+#include "../../layout/space/Space.hpp"
+#include "../../layout/LayoutManager.hpp"
+#include "../../desktop/state/FocusState.hpp"
+#include "../../Compositor.hpp"
+
+#include <algorithm>
+
+using namespace Desktop;
+using namespace Desktop::View;
+
+std::vector<WP<CGroup>>& View::groups() {
+ static std::vector<WP<CGroup>> g;
+ return g;
+}
+
+SP<CGroup> CGroup::create(std::vector<PHLWINDOWREF>&& windows) {
+ auto x = SP<CGroup>(new CGroup(std::move(windows)));
+ x->m_self = x;
+ x->m_target = Layout::CWindowGroupTarget::create(x);
+ groups().emplace_back(x);
+
+ x->init();
+
+ return x;
+}
+
+CGroup::CGroup(std::vector<PHLWINDOWREF>&& windows) : m_windows(std::move(windows)) {
+ ;
+}
+
+void CGroup::init() {
+ // for proper group logic:
+ // - add all windows to us
+ // - replace the first window with our target
+ // - remove all window targets from layout
+ // - apply updates
+
+ // FIXME: what if some windows are grouped? For now we only do 1-window but YNK
+ for (const auto& w : m_windows) {
+ RASSERT(!w->m_group, "CGroup: windows cannot contain grouped in init, this will explode");
+ w->m_group = m_self.lock();
+ }
+
+ g_layoutManager->switchTargets(m_windows.at(0)->m_target, m_target);
+
+ for (const auto& w : m_windows) {
+ w->m_target->setSpaceGhost(m_target->space());
+ }
+
+ for (const auto& w : m_windows) {
+ applyWindowDecosAndUpdates(w.lock());
+ }
+
+ updateWindowVisibility();
+}
+
+void CGroup::destroy() {
+ while (true) {
+ if (m_windows.size() == 1) {
+ remove(m_windows.at(0).lock());
+ break;
+ }
+
+ remove(m_windows.at(0).lock());
+ }
+}
+
+CGroup::~CGroup() {
+ if (m_target->space())
+ m_target->assignToSpace(nullptr);
+ std::erase_if(groups(), [this](const auto& e) { return !e || e == m_self; });
+}
+
+bool CGroup::has(PHLWINDOW w) const {
+ return std::ranges::contains(m_windows, w);
+}
+
+void CGroup::add(PHLWINDOW w) {
+ static auto INSERT_AFTER_CURRENT = CConfigValue<Hyprlang::INT>("group:insert_after_current");
+
+ if (w->m_group) {
+ if (w->m_group == m_self)
+ return;
+
+ const auto WINDOWS = w->m_group->windows();
+ for (const auto& w : WINDOWS) {
+ w->m_group->remove(w.lock());
+ add(w.lock());
+ }
+
+ return;
+ }
+
+ if (w->layoutTarget()->space()) {
+ // remove the target from a space if it is in one
+ g_layoutManager->removeTarget(w->layoutTarget());
+ }
+
+ w->m_group = m_self.lock();
+ w->m_target->setSpaceGhost(m_target->space());
+ w->m_target->setFloating(m_target->floating());
+
+ if (*INSERT_AFTER_CURRENT) {
+ m_windows.insert(m_windows.begin() + m_current + 1, w);
+ m_current++;
+ } else {
+ m_windows.emplace_back(w);
+ m_current = m_windows.size() - 1;
+ }
+
+ applyWindowDecosAndUpdates(w);
+ updateWindowVisibility();
+ m_target->recalc();
+}
+
+void CGroup::remove(PHLWINDOW w) {
+ std::optional<size_t> idx;
+ for (size_t i = 0; i < m_windows.size(); ++i) {
+ if (m_windows.at(i) == w) {
+ idx = i;
+ break;
+ }
+ }
+
+ if (!idx)
+ return;
+
+ if ((m_current >= *idx && idx != 0) || (m_current >= m_windows.size() - 1 && m_current > 0))
+ m_current--;
+
+ auto g = m_self.lock(); // keep ref to avoid uaf after w->m_group.reset()
+
+ w->m_group.reset();
+ removeWindowDecos(w);
+
+ w->setHidden(false);
+
+ const bool REMOVING_GROUP = m_windows.size() <= 1;
+
+ if (REMOVING_GROUP) {
+ w->m_target->assignToSpace(nullptr);
+ g_layoutManager->switchTargets(m_target, w->m_target);
+ }
+
+ // we do it after the above because switchTargets expects this to be a valid group
+ m_windows.erase(m_windows.begin() + *idx);
+
+ if (!m_windows.empty())
+ updateWindowVisibility();
+
+ // do this here: otherwise the new current is hidden and workspace rules get wrong data
+ if (!REMOVING_GROUP)
+ w->m_target->assignToSpace(m_target->space());
+}
+
+void CGroup::moveCurrent(bool next) {
+ size_t idx = m_current;
+
+ if (next) {
+ idx++;
+ if (idx >= m_windows.size())
+ idx = 0;
+ } else {
+ if (idx == 0)
+ idx = m_windows.size() - 1;
+ else
+ idx--;
+ }
+
+ setCurrent(idx);
+}
+
+void CGroup::setCurrent(size_t idx) {
+ if (idx == m_current)
+ return;
+
+ const auto FS_STATE = m_target->fullscreenMode();
+ const auto WASFOCUS = Desktop::focusState()->window() == current();
+ auto oldWindow = m_windows.at(m_current).lock();
+
+ if (FS_STATE != FSMODE_NONE)
+ g_pCompositor->setWindowFullscreenInternal(oldWindow, FSMODE_NONE);
+
+ m_current = std::clamp(idx, sc<size_t>(0), m_windows.size() - 1);
+ updateWindowVisibility();
+
+ auto newWindow = m_windows.at(m_current).lock();
+
+ if (FS_STATE != FSMODE_NONE) {
+ g_pCompositor->setWindowFullscreenInternal(newWindow, FS_STATE);
+ newWindow->m_target->warpPositionSize();
+ oldWindow->m_target->setPositionGlobal(newWindow->m_target->position()); // TODO: this is a hack and sucks
+ }
+
+ if (WASFOCUS)
+ Desktop::focusState()->rawWindowFocus(current(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
+}
+
+void CGroup::setCurrent(PHLWINDOW w) {
+ if (w == current())
+ return;
+
+ for (size_t i = 0; i < m_windows.size(); ++i) {
+ if (m_windows.at(i) == w) {
+ setCurrent(i);
+ return;
+ }
+ }
+}
+
+size_t CGroup::getCurrentIdx() const {
+ return m_current;
+}
+
+PHLWINDOW CGroup::head() const {
+ return m_windows.front().lock();
+}
+
+PHLWINDOW CGroup::tail() const {
+ return m_windows.back().lock();
+}
+
+PHLWINDOW CGroup::current() const {
+ return m_windows.at(m_current).lock();
+}
+
+PHLWINDOW CGroup::next() const {
+ return (m_current >= m_windows.size() - 1 ? m_windows.front() : m_windows.at(m_current + 1)).lock();
+}
+
+PHLWINDOW CGroup::fromIndex(size_t idx) const {
+ if (idx >= m_windows.size())
+ return nullptr;
+
+ return m_windows.at(idx).lock();
+}
+
+const std::vector<PHLWINDOWREF>& CGroup::windows() const {
+ return m_windows;
+}
+
+void CGroup::applyWindowDecosAndUpdates(PHLWINDOW x) {
+ x->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(x));
+
+ x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
+ x->updateWindowDecos();
+ x->updateDecorationValues();
+}
+
+void CGroup::removeWindowDecos(PHLWINDOW x) {
+ x->removeWindowDeco(x->getDecorationByType(DECORATION_GROUPBAR));
+
+ x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
+ x->updateWindowDecos();
+ x->updateDecorationValues();
+}
+
+void CGroup::updateWindowVisibility() {
+ for (size_t i = 0; i < m_windows.size(); ++i) {
+ if (i == m_current) {
+ auto& x = m_windows.at(i);
+ x->setHidden(false);
+ x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
+ x->updateWindowDecos();
+ x->updateDecorationValues();
+ } else
+ m_windows.at(i)->setHidden(true);
+ }
+
+ m_target->recalc();
+
+ m_target->damageEntire();
+}
+
+size_t CGroup::size() const {
+ return m_windows.size();
+}
+
+bool CGroup::locked() const {
+ return m_locked;
+}
+
+void CGroup::setLocked(bool x) {
+ m_locked = x;
+}
+
+bool CGroup::denied() const {
+ return m_deny;
+}
+
+void CGroup::setDenied(bool x) {
+ m_deny = x;
+}
+
+void CGroup::updateWorkspace(PHLWORKSPACE ws) {
+ if (!ws)
+ return;
+
+ for (const auto& w : windows()) {
+ w->m_monitor = ws->m_monitor;
+ w->moveToWorkspace(ws);
+ w->updateToplevel();
+ w->updateWindowDecos();
+ w->m_target->setSpaceGhost(ws->m_space);
+ }
+}
+
+void CGroup::swapWithNext() {
+ const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current);
+
+ size_t idx = m_current + 1 >= m_windows.size() ? 0 : m_current + 1;
+ std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx);
+
+ updateWindowVisibility();
+
+ if (HAD_FOCUS)
+ Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
+}
+
+void CGroup::swapWithLast() {
+ const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current);
+
+ size_t idx = m_current == 0 ? m_windows.size() - 1 : m_current - 1;
+ std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx);
+
+ updateWindowVisibility();
+
+ if (HAD_FOCUS)
+ Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
+}
diff --git a/src/desktop/view/Group.hpp b/src/desktop/view/Group.hpp
new file mode 100644
index 00000000..8a7bb840
--- /dev/null
+++ b/src/desktop/view/Group.hpp
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "../DesktopTypes.hpp"
+
+#include <vector>
+
+namespace Layout {
+ class CWindowGroupTarget;
+};
+
+namespace Desktop::View {
+ class CGroup {
+ public:
+ static SP<CGroup> create(std::vector<PHLWINDOWREF>&& windows);
+ ~CGroup();
+
+ bool has(PHLWINDOW w) const;
+
+ void add(PHLWINDOW w);
+ void remove(PHLWINDOW w);
+ void moveCurrent(bool next);
+ void setCurrent(size_t idx);
+ void setCurrent(PHLWINDOW w);
+ size_t getCurrentIdx() const;
+ size_t size() const;
+ void destroy();
+ void updateWorkspace(PHLWORKSPACE);
+
+ void swapWithNext();
+ void swapWithLast();
+
+ PHLWINDOW head() const;
+ PHLWINDOW tail() const;
+ PHLWINDOW current() const;
+ PHLWINDOW next() const;
+
+ PHLWINDOW fromIndex(size_t idx) const;
+
+ bool locked() const;
+ void setLocked(bool x);
+
+ bool denied() const;
+ void setDenied(bool x);
+
+ const std::vector<PHLWINDOWREF>& windows() const;
+
+ SP<Layout::CWindowGroupTarget> m_target;
+
+ private:
+ CGroup(std::vector<PHLWINDOWREF>&& windows);
+
+ void applyWindowDecosAndUpdates(PHLWINDOW x);
+ void removeWindowDecos(PHLWINDOW x);
+ void init();
+ void updateWindowVisibility();
+
+ WP<CGroup> m_self;
+
+ std::vector<PHLWINDOWREF> m_windows;
+
+ bool m_locked = false;
+ bool m_deny = false;
+
+ size_t m_current = 0;
+ };
+
+ std::vector<WP<CGroup>>& groups();
+}; \ No newline at end of file
diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp
index 7e1131b1..d94ee79a 100644
--- a/src/desktop/view/Window.cpp
+++ b/src/desktop/view/Window.cpp
@@ -3,6 +3,8 @@
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <re2/re2.h>
+#include "Group.hpp"
+
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
#include <sys/types.h>
#include <sys/sysctl.h>
@@ -37,12 +39,15 @@
#include "../../helpers/math/Expression.hpp"
#include "../../managers/XWaylandManager.hpp"
#include "../../render/Renderer.hpp"
-#include "../../managers/LayoutManager.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../managers/EventManager.hpp"
#include "../../managers/input/InputManager.hpp"
#include "../../managers/PointerManager.hpp"
#include "../../managers/animation/DesktopAnimationManager.hpp"
+#include "../../layout/space/Space.hpp"
+#include "../../layout/LayoutManager.hpp"
+#include "../../layout/target/WindowTarget.hpp"
+#include "../../layout/target/WindowGroupTarget.hpp"
#include <hyprutils/string/String.hpp>
@@ -57,6 +62,9 @@ using namespace Desktop::View;
static uint64_t windowIDCounter = 0x18000000;
//
+#define COMMA ,
+//
+
PHLWINDOW CWindow::create(SP<CXWaylandSurface> surface) {
PHLWINDOW pWindow = SP<CWindow>(new CWindow(surface));
@@ -79,6 +87,8 @@ PHLWINDOW CWindow::create(SP<CXWaylandSurface> surface) {
pWindow->addWindowDeco(makeUnique<CHyprDropShadowDecoration>(pWindow));
pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(pWindow));
+ pWindow->m_target = Layout::CWindowTarget::create(pWindow);
+
return pWindow;
}
@@ -104,6 +114,8 @@ PHLWINDOW CWindow::create(SP<CXDGSurfaceResource> resource) {
pWindow->addWindowDeco(makeUnique<CHyprDropShadowDecoration>(pWindow));
pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(pWindow));
+ pWindow->m_target = Layout::CWindowTarget::create(pWindow);
+
pWindow->wlSurface()->assign(pWindow->m_xdgSurface->m_surface.lock(), pWindow);
return pWindow;
@@ -263,23 +275,24 @@ CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() {
return CBox{sc<int>(POS.x), sc<int>(POS.y), sc<int>(SIZE.x), sc<int>(SIZE.y)};
}
- // get work area
- const auto WORKAREA = g_pLayoutManager->getCurrentLayout()->workAreaOnWorkspace(m_workspace);
- const auto RESERVED = CReservedArea{PMONITOR->logicalBox(), WORKAREA};
+ // fucker fucking fuck
+ const auto WORKAREA = m_workspace->m_space->workArea();
+ const auto& RESERVED = PMONITOR->m_reservedArea;
- if (DELTALESSTHAN(POS.y - PMONITOR->m_position.y, RESERVED.top(), 1)) {
- POS.y = PMONITOR->m_position.y;
- SIZE.y += RESERVED.top();
- }
- if (DELTALESSTHAN(POS.x - PMONITOR->m_position.x, RESERVED.left(), 1)) {
- POS.x = PMONITOR->m_position.x;
+ if (DELTALESSTHAN(POS.x, WORKAREA.x, 1)) {
+ POS.x -= RESERVED.left();
SIZE.x += RESERVED.left();
}
- if (DELTALESSTHAN(POS.x + SIZE.x - PMONITOR->m_position.x, PMONITOR->m_size.x - RESERVED.right(), 1))
+ if (DELTALESSTHAN(POS.y, WORKAREA.y, 1)) {
+ POS.y -= RESERVED.top();
+ SIZE.y += RESERVED.top();
+ }
+
+ if (DELTALESSTHAN(POS.x + SIZE.x, WORKAREA.x + WORKAREA.width, 1))
SIZE.x += RESERVED.right();
- if (DELTALESSTHAN(POS.y + SIZE.y - PMONITOR->m_position.y, PMONITOR->m_size.y - RESERVED.bottom(), 1))
+ if (DELTALESSTHAN(POS.y + SIZE.y, WORKAREA.y + WORKAREA.height, 1))
SIZE.y += RESERVED.bottom();
return CBox{sc<int>(POS.x), sc<int>(POS.y), sc<int>(SIZE.x), sc<int>(SIZE.y)};
@@ -357,14 +370,18 @@ void CWindow::addWindowDeco(UP<IHyprWindowDecoration> deco) {
m_windowDecorations.emplace_back(std::move(deco));
g_pDecorationPositioner->forceRecalcFor(m_self.lock());
updateWindowDecos();
- g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock());
+
+ if (layoutTarget())
+ layoutTarget()->recalc();
}
void CWindow::removeWindowDeco(IHyprWindowDecoration* deco) {
m_decosToRemove.push_back(deco);
g_pDecorationPositioner->forceRecalcFor(m_self.lock());
updateWindowDecos();
- g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock());
+
+ if (layoutTarget())
+ layoutTarget()->recalc();
}
void CWindow::uncacheWindowDecos() {
@@ -495,11 +512,9 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) {
OLDWORKSPACE->updateWindows();
OLDWORKSPACE->updateWindowData();
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(OLDWORKSPACE->monitorID());
pWorkspace->updateWindows();
pWorkspace->updateWindowData();
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
@@ -591,7 +606,7 @@ void CWindow::onUnmap() {
m_workspace->updateWindows();
m_workspace->updateWindowData();
}
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
+
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
m_workspace.reset();
@@ -723,311 +738,6 @@ bool CWindow::hasPopupAt(const Vector2D& pos) {
return popup && popup->wlSurface()->resource();
}
-void CWindow::applyGroupRules() {
- if ((m_groupRules & GROUP_SET && m_firstMap) || m_groupRules & GROUP_SET_ALWAYS)
- createGroup();
-
- if (m_groupData.pNextWindow.lock() && ((m_groupRules & GROUP_LOCK && m_firstMap) || m_groupRules & GROUP_LOCK_ALWAYS))
- getGroupHead()->m_groupData.locked = true;
-}
-
-void CWindow::createGroup() {
- if (m_groupData.deny) {
- Log::logger->log(Log::DEBUG, "createGroup: window:{:x},title:{} is denied as a group, ignored", rc<uintptr_t>(this), this->m_title);
- return;
- }
-
- if (m_groupData.pNextWindow.expired()) {
- m_groupData.pNextWindow = m_self;
- m_groupData.head = true;
- m_groupData.locked = false;
- m_groupData.deny = false;
-
- addWindowDeco(makeUnique<CHyprGroupBarDecoration>(m_self.lock()));
-
- if (m_workspace) {
- m_workspace->updateWindows();
- m_workspace->updateWindowData();
- }
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
- g_pCompositor->updateAllWindowsAnimatedDecorationValues();
-
- g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("1,{:x}", rc<uintptr_t>(this))});
- }
-
- m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
-}
-
-void CWindow::destroyGroup() {
- if (m_groupData.pNextWindow == m_self) {
- if (m_groupRules & GROUP_SET_ALWAYS) {
- Log::logger->log(Log::DEBUG, "destoryGroup: window:{:x},title:{} has rule [group set always], ignored", rc<uintptr_t>(this), this->m_title);
- return;
- }
- m_groupData.pNextWindow.reset();
- m_groupData.head = false;
- updateWindowDecos();
- if (m_workspace) {
- m_workspace->updateWindows();
- m_workspace->updateWindowData();
- }
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
- g_pCompositor->updateAllWindowsAnimatedDecorationValues();
-
- g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("0,{:x}", rc<uintptr_t>(this))});
- m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
- return;
- }
-
- std::string addresses;
- PHLWINDOW curr = m_self.lock();
- std::vector<PHLWINDOW> members;
- do {
- const auto PLASTWIN = curr;
- curr = curr->m_groupData.pNextWindow.lock();
- PLASTWIN->m_groupData.pNextWindow.reset();
- curr->setHidden(false);
- members.push_back(curr);
-
- addresses += std::format("{:x},", rc<uintptr_t>(curr.get()));
- } while (curr.get() != this);
-
- for (auto const& w : members) {
- if (w->m_groupData.head)
- g_pLayoutManager->getCurrentLayout()->onWindowRemoved(curr);
- w->m_groupData.head = false;
- }
-
- const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked;
- g_pKeybindManager->m_groupsLocked = true;
- for (auto const& w : members) {
- g_pLayoutManager->getCurrentLayout()->onWindowCreated(w);
- w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
- w->updateWindowDecos();
- }
- g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV;
-
- if (m_workspace) {
- m_workspace->updateWindows();
- m_workspace->updateWindowData();
- }
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
- g_pCompositor->updateAllWindowsAnimatedDecorationValues();
-
- if (!addresses.empty())
- addresses.pop_back();
-
- m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
- g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("0,{}", addresses)});
-}
-
-PHLWINDOW CWindow::getGroupHead() {
- PHLWINDOW curr = m_self.lock();
- while (!curr->m_groupData.head)
- curr = curr->m_groupData.pNextWindow.lock();
- return curr;
-}
-
-PHLWINDOW CWindow::getGroupTail() {
- PHLWINDOW curr = m_self.lock();
- while (!curr->m_groupData.pNextWindow->m_groupData.head)
- curr = curr->m_groupData.pNextWindow.lock();
- return curr;
-}
-
-PHLWINDOW CWindow::getGroupCurrent() {
- PHLWINDOW curr = m_self.lock();
- while (curr->isHidden())
- curr = curr->m_groupData.pNextWindow.lock();
- return curr;
-}
-
-int CWindow::getGroupSize() {
- int size = 1;
- PHLWINDOW curr = m_self.lock();
- while (curr->m_groupData.pNextWindow != m_self) {
- curr = curr->m_groupData.pNextWindow.lock();
- size++;
- }
- return size;
-}
-
-bool CWindow::canBeGroupedInto(PHLWINDOW pWindow) {
- static auto ALLOWGROUPMERGE = CConfigValue<Hyprlang::INT>("group:merge_groups_on_drag");
- bool isGroup = m_groupData.pNextWindow;
- bool disallowDragIntoGroup = g_pInputManager->m_wasDraggingWindow && isGroup && !sc<bool>(*ALLOWGROUPMERGE);
- return !g_pKeybindManager->m_groupsLocked // global group lock disengaged
- && ((m_groupRules & GROUP_INVADE && m_firstMap) // window ignore local group locks, or
- || (!pWindow->getGroupHead()->m_groupData.locked // target unlocked
- && !(m_groupData.pNextWindow.lock() && getGroupHead()->m_groupData.locked))) // source unlocked or isn't group
- && !m_groupData.deny // source is not denied entry
- && !(m_groupRules & GROUP_BARRED && m_firstMap) // group rule doesn't prevent adding window
- && !disallowDragIntoGroup; // config allows groups to be merged
-}
-
-PHLWINDOW CWindow::getGroupWindowByIndex(int index) {
- const int SIZE = getGroupSize();
- index = ((index % SIZE) + SIZE) % SIZE;
- PHLWINDOW curr = getGroupHead();
- while (index > 0) {
- curr = curr->m_groupData.pNextWindow.lock();
- index--;
- }
- return curr;
-}
-
-bool CWindow::hasInGroup(PHLWINDOW w) {
- PHLWINDOW curr = m_groupData.pNextWindow.lock();
- while (curr && curr != m_self) {
- if (curr == w)
- return true;
- curr = curr->m_groupData.pNextWindow.lock();
- }
- return false;
-}
-
-void CWindow::setGroupCurrent(PHLWINDOW pWindow) {
- PHLWINDOW curr = m_groupData.pNextWindow.lock();
- bool isMember = false;
- while (curr.get() != this) {
- if (curr == pWindow) {
- isMember = true;
- break;
- }
- curr = curr->m_groupData.pNextWindow.lock();
- }
-
- if (!isMember && pWindow.get() != this)
- return;
-
- const auto PCURRENT = getGroupCurrent();
- const bool FULLSCREEN = PCURRENT->isFullscreen();
- const auto WORKSPACE = PCURRENT->m_workspace;
- const auto MODE = PCURRENT->m_fullscreenState.internal;
-
- const auto CURRENTISFOCUS = PCURRENT == Desktop::focusState()->window();
-
- const auto PWINDOWSIZE = PCURRENT->m_realSize->value();
- const auto PWINDOWPOS = PCURRENT->m_realPosition->value();
- const auto PWINDOWSIZEGOAL = PCURRENT->m_realSize->goal();
- const auto PWINDOWPOSGOAL = PCURRENT->m_realPosition->goal();
- const auto PWINDOWLASTFLOATINGSIZE = PCURRENT->m_lastFloatingSize;
- const auto PWINDOWLASTFLOATINGPOSITION = PCURRENT->m_lastFloatingPosition;
-
- if (FULLSCREEN)
- g_pCompositor->setWindowFullscreenInternal(PCURRENT, FSMODE_NONE);
-
- PCURRENT->setHidden(true);
- pWindow->setHidden(false); // can remove m_pLastWindow
-
- g_pLayoutManager->getCurrentLayout()->replaceWindowDataWith(PCURRENT, pWindow);
-
- if (PCURRENT->m_isFloating) {
- pWindow->m_realPosition->setValueAndWarp(PWINDOWPOSGOAL);
- pWindow->m_realSize->setValueAndWarp(PWINDOWSIZEGOAL);
- pWindow->sendWindowSize();
- }
-
- pWindow->m_realPosition->setValue(PWINDOWPOS);
- pWindow->m_realSize->setValue(PWINDOWSIZE);
-
- if (FULLSCREEN)
- g_pCompositor->setWindowFullscreenInternal(pWindow, MODE);
-
- pWindow->m_lastFloatingSize = PWINDOWLASTFLOATINGSIZE;
- pWindow->m_lastFloatingPosition = PWINDOWLASTFLOATINGPOSITION;
-
- g_pCompositor->updateAllWindowsAnimatedDecorationValues();
-
- if (CURRENTISFOCUS)
- Desktop::focusState()->rawWindowFocus(pWindow);
-
- g_pHyprRenderer->damageWindow(pWindow);
-
- pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
- m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
- pWindow->updateWindowDecos();
-}
-
-void CWindow::insertWindowToGroup(PHLWINDOW pWindow) {
- const auto BEGINAT = m_self.lock();
- const auto ENDAT = m_groupData.pNextWindow.lock();
-
- if (!pWindow->m_groupData.pNextWindow.lock()) {
- BEGINAT->m_groupData.pNextWindow = pWindow;
- pWindow->m_groupData.pNextWindow = ENDAT;
- pWindow->m_groupData.head = false;
- pWindow->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(pWindow));
- return;
- }
-
- const auto SHEAD = pWindow->getGroupHead();
- const auto STAIL = pWindow->getGroupTail();
-
- SHEAD->m_groupData.head = false;
- BEGINAT->m_groupData.pNextWindow = SHEAD;
- STAIL->m_groupData.pNextWindow = ENDAT;
-
- pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
- m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
- pWindow->updateWindowDecos();
-}
-
-PHLWINDOW CWindow::getGroupPrevious() {
- PHLWINDOW curr = m_groupData.pNextWindow.lock();
-
- while (curr != m_self && curr->m_groupData.pNextWindow != m_self)
- curr = curr->m_groupData.pNextWindow.lock();
-
- return curr;
-}
-
-void CWindow::switchWithWindowInGroup(PHLWINDOW pWindow) {
- if (!m_groupData.pNextWindow.lock() || !pWindow->m_groupData.pNextWindow.lock())
- return;
-
- if (m_groupData.pNextWindow.lock() == pWindow) { // A -> this -> pWindow -> B >> A -> pWindow -> this -> B
- getGroupPrevious()->m_groupData.pNextWindow = pWindow;
- m_groupData.pNextWindow = pWindow->m_groupData.pNextWindow;
- pWindow->m_groupData.pNextWindow = m_self;
-
- } else if (pWindow->m_groupData.pNextWindow == m_self) { // A -> pWindow -> this -> B >> A -> this -> pWindow -> B
- pWindow->getGroupPrevious()->m_groupData.pNextWindow = m_self;
- pWindow->m_groupData.pNextWindow = m_groupData.pNextWindow;
- m_groupData.pNextWindow = pWindow;
-
- } else { // A -> this -> B | C -> pWindow -> D >> A -> pWindow -> B | C -> this -> D
- std::swap(m_groupData.pNextWindow, pWindow->m_groupData.pNextWindow);
- std::swap(getGroupPrevious()->m_groupData.pNextWindow, pWindow->getGroupPrevious()->m_groupData.pNextWindow);
- }
-
- std::swap(m_groupData.head, pWindow->m_groupData.head);
- std::swap(m_groupData.locked, pWindow->m_groupData.locked);
-
- pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
- m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
- pWindow->updateWindowDecos();
-}
-
-void CWindow::updateGroupOutputs() {
- if (m_groupData.pNextWindow.expired())
- return;
-
- PHLWINDOW curr = m_groupData.pNextWindow.lock();
-
- const auto WS = m_workspace;
-
- while (curr.get() != this) {
- curr->m_monitor = m_monitor;
- curr->moveToWorkspace(WS);
-
- *curr->m_realPosition = m_realPosition->goal();
- *curr->m_realSize = m_realSize->goal();
-
- curr = curr->m_groupData.pNextWindow.lock();
- }
-}
-
Vector2D CWindow::middle() {
return m_realPosition->goal() + m_realSize->goal() / 2.f;
}
@@ -1148,7 +858,7 @@ void CWindow::setAnimationsToMove() {
void CWindow::onWorkspaceAnimUpdate() {
// clip box for animated offsets
- if (!m_isFloating || m_pinned || isFullscreen() || m_draggingTiled) {
+ if (!m_isFloating || m_pinned || isFullscreen()) {
m_floatingOffset = Vector2D(0, 0);
return;
}
@@ -1326,7 +1036,7 @@ void CWindow::activate(bool force) {
if (m_isFloating)
g_pCompositor->changeWindowZOrder(m_self.lock(), true);
- Desktop::focusState()->fullWindowFocus(m_self.lock());
+ Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
warpCursor();
}
@@ -1378,7 +1088,8 @@ void CWindow::onUpdateMeta() {
if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = m_class + "," + m_title});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(this))});
- EMIT_HOOK_EVENT("activeWindow", m_self.lock());
+
+ // no need for a hook event
}
Log::logger->log(Log::DEBUG, "Window {:x} set title to {}", rc<uintptr_t>(this), m_title);
@@ -1392,7 +1103,8 @@ void CWindow::onUpdateMeta() {
if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = m_class + "," + m_title});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(this))});
- EMIT_HOOK_EVENT("activeWindow", m_self.lock());
+
+ // no need for a hook event
}
Log::logger->log(Log::DEBUG, "Window {:x} set class to {}", rc<uintptr_t>(this), m_class);
@@ -1472,7 +1184,7 @@ void CWindow::onX11ConfigureRequest(CBox box) {
g_pHyprRenderer->damageWindow(m_self.lock());
- if (!m_isFloating || isFullscreen() || g_pInputManager->m_currentlyDraggedWindow == m_self) {
+ if (!m_isFloating || isFullscreen() || g_layoutManager->dragController()->target() == m_self) {
sendWindowSize(true);
g_pInputManager->refocus();
g_pHyprRenderer->damageWindow(m_self.lock());
@@ -1689,20 +1401,17 @@ void CWindow::setContentType(NContentType::eContentType contentType) {
}
void CWindow::deactivateGroupMembers() {
- auto curr = getGroupHead();
- while (curr) {
- if (curr != m_self.lock()) {
+ if (!m_group)
+ return;
+ for (const auto& w : m_group->windows()) {
+ if (w != m_self.lock()) {
// we don't want to deactivate unfocused xwayland windows
// because X is weird, keep the behavior for wayland windows
// also its not really needed for xwayland windows
// ref: #9760 #9294
- if (!curr->m_isX11 && curr->m_xdgSurface && curr->m_xdgSurface->m_toplevel)
- curr->m_xdgSurface->m_toplevel->setActive(false);
+ if (!w->m_isX11 && w->m_xdgSurface && w->m_xdgSurface->m_toplevel)
+ w->m_xdgSurface->m_toplevel->setActive(false);
}
-
- curr = curr->m_groupData.pNextWindow.lock();
- if (curr == getGroupHead())
- break;
}
}
@@ -1827,21 +1536,13 @@ void CWindow::updateDecorationValues() {
const bool IS_SHADOWED_BY_MODAL = m_xdgSurface && m_xdgSurface->m_toplevel && m_xdgSurface->m_toplevel->anyChildModal();
- // border
- const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(m_self.lock());
- if (RENDERDATA.isBorderGradient)
- setBorderColor(*RENDERDATA.borderGradient);
- else {
- const bool GROUPLOCKED = m_groupData.pNextWindow.lock() ? getGroupHead()->m_groupData.locked : false;
- if (m_self == Desktop::focusState()->window()) {
- const auto* const ACTIVECOLOR =
- !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
- setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR));
- } else {
- const auto* const INACTIVECOLOR =
- !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
- setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR));
- }
+ const bool GROUPLOCKED = m_group ? m_group->locked() : false;
+ if (m_self == Desktop::focusState()->window()) {
+ const auto* const ACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
+ setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR));
+ } else {
+ const auto* const INACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
+ setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR));
}
// opacity
@@ -1929,6 +1630,7 @@ void CWindow::mapWindow() {
static auto PDIMSTRENGTH = CConfigValue<Hyprlang::FLOAT>("decoration:dim_strength");
static auto PNEWTAKESOVERFS = CConfigValue<Hyprlang::INT>("misc:on_focus_under_fullscreen");
static auto PINITIALWSTRACKING = CConfigValue<Hyprlang::INT>("misc:initial_workspace_tracking");
+ static auto PAUTOGROUP = CConfigValue<Hyprlang::INT>("group:auto_group");
const auto LAST_FOCUS_WINDOW = Desktop::focusState()->window();
const bool IS_LAST_IN_FS = LAST_FOCUS_WINDOW ? LAST_FOCUS_WINDOW->m_fullscreenState.internal != FSMODE_NONE : false;
@@ -2053,8 +1755,8 @@ void CWindow::mapWindow() {
requestedFSMonitor = MONITOR_INVALID;
}
- m_isFloating = m_ruleApplicator->static_.floating.value_or(m_isFloating);
- m_isPseudotiled = m_ruleApplicator->static_.pseudo.value_or(m_isPseudotiled);
+ m_isFloating = m_ruleApplicator->static_.floating.value_or(m_isFloating);
+ m_target->setPseudo(m_ruleApplicator->static_.pseudo.value_or(m_target->isPseudo()));
m_noInitialFocus = m_ruleApplicator->static_.noInitialFocus.value_or(m_noInitialFocus);
m_pinned = m_ruleApplicator->static_.pin.value_or(m_pinned);
@@ -2109,7 +1811,7 @@ void CWindow::mapWindow() {
} else if (v == "barred") {
m_groupRules |= Desktop::View::GROUP_BARRED;
} else if (v == "deny") {
- m_groupData.deny = true;
+ m_groupRules |= Desktop::View::GROUP_DENY;
} else if (v == "override") {
// Clear existing rules
m_groupRules = Desktop::View::GROUP_OVERRIDE;
@@ -2213,9 +1915,9 @@ void CWindow::mapWindow() {
m_isFloating = true;
if (PWORKSPACE->m_defaultPseudo) {
- m_isPseudotiled = true;
CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(m_self.lock());
- m_pseudoSize = Vector2D(desiredGeometry.width, desiredGeometry.height);
+ m_target->setPseudoSize(Vector2D{desiredGeometry.width, desiredGeometry.height});
+ m_target->setPseudo(true);
}
updateWindowData();
@@ -2230,43 +1932,29 @@ void CWindow::mapWindow() {
g_pEventManager->postEvent(SHyprIPCEvent{"openwindow", std::format("{:x},{},{},{}", m_self.lock(), PWORKSPACE->m_name, m_class, m_title)});
EMIT_HOOK_EVENT("openWindowEarly", m_self.lock());
- if (m_isFloating) {
- g_pLayoutManager->getCurrentLayout()->onWindowCreated(m_self.lock());
- m_createdOverFullscreen = true;
-
- if (!m_ruleApplicator->static_.size.empty()) {
- const auto COMPUTED = calculateExpression(m_ruleApplicator->static_.size);
- if (!COMPUTED)
- Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.size);
- else {
- *m_realSize = *COMPUTED;
- setHidden(false);
- }
- }
+ if (*PAUTOGROUP // auto_group enabled
+ && Desktop::focusState()->window() // focused window exists
+ && canBeGroupedInto(Desktop::focusState()->window()->m_group) // we can group
+ && Desktop::focusState()->window()->m_workspace == m_workspace // workspaces match, we're not opening on another ws
+ && !isModal() && !(parent() && m_isFloating) && !isX11OverrideRedirect() // not a modal, floating child or X11 OR
+ ) {
+ // add to group if we are focused on one
+ Desktop::focusState()->window()->m_group->add(m_self.lock());
+ } else
+ g_layoutManager->newTarget(m_target, m_workspace->m_space);
- if (!m_ruleApplicator->static_.position.empty()) {
- const auto COMPUTED = calculateExpression(m_ruleApplicator->static_.position);
- if (!COMPUTED)
- Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.position);
- else {
- *m_realPosition = *COMPUTED + PMONITOR->m_position;
- setHidden(false);
- }
- }
+ if (!m_group && (m_groupRules & GROUP_SET))
+ m_group = CGroup::create({m_self});
- if (m_ruleApplicator->static_.center.value_or(false)) {
- const auto WORKAREA = PMONITOR->logicalBoxMinusReserved();
- *m_realPosition = WORKAREA.middle() - m_realSize->goal() / 2.f;
- }
+ if (m_isFloating) {
+ m_createdOverFullscreen = true;
// set the pseudo size to the GOAL of our current size
// because the windows are animated on RealSize
- m_pseudoSize = m_realSize->goal();
+ m_target->setPseudoSize(m_realSize->goal());
g_pCompositor->changeWindowZOrder(m_self.lock(), true);
} else {
- g_pLayoutManager->getCurrentLayout()->onWindowCreated(m_self.lock());
-
bool setPseudo = false;
if (!m_ruleApplicator->static_.size.empty()) {
@@ -2274,14 +1962,14 @@ void CWindow::mapWindow() {
if (!COMPUTED)
Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.size);
else {
- setPseudo = true;
- m_pseudoSize = *COMPUTED;
+ setPseudo = true;
+ m_target->setPseudoSize(*COMPUTED);
setHidden(false);
}
}
if (!setPseudo)
- m_pseudoSize = m_realSize->goal() - Vector2D(10, 10);
+ m_target->setPseudoSize(m_realSize->goal() - Vector2D(10, 10));
}
const auto PFOCUSEDWINDOWPREV = Desktop::focusState()->window();
@@ -2311,13 +1999,13 @@ void CWindow::mapWindow() {
(!PFORCEFOCUS || PFORCEFOCUS == m_self.lock()) && !g_pInputManager->isConstrained()) {
// this window should gain focus: if it's grouped, preserve fullscreen state.
- const bool SAME_GROUP = hasInGroup(LAST_FOCUS_WINDOW);
+ const bool SAME_GROUP = m_group && m_group->has(LAST_FOCUS_WINDOW);
if (IS_LAST_IN_FS && SAME_GROUP) {
- Desktop::focusState()->rawWindowFocus(m_self.lock());
+ Desktop::focusState()->rawWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW);
g_pCompositor->setWindowFullscreenInternal(m_self.lock(), LAST_FS_MODE);
} else
- Desktop::focusState()->fullWindowFocus(m_self.lock());
+ Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW);
m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA);
m_dimPercent->setValueAndWarp(m_ruleApplicator->noDim().valueOrDefault() ? 0.f : *PDIMSTRENGTH);
@@ -2358,18 +2046,16 @@ void CWindow::mapWindow() {
if (workspaceSilent) {
if (validMapped(PFOCUSEDWINDOWPREV)) {
- Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV);
+ Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV, FOCUS_REASON_NEW_WINDOW);
PFOCUSEDWINDOWPREV->updateWindowDecos(); // need to for some reason i cba to find out why
} else if (!PFOCUSEDWINDOWPREV)
- Desktop::focusState()->rawWindowFocus(nullptr);
+ Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON_NEW_WINDOW);
}
// swallow
if (SWALLOWER) {
- g_pLayoutManager->getCurrentLayout()->onWindowRemoved(SWALLOWER);
- g_pHyprRenderer->damageWindow(SWALLOWER);
+ g_layoutManager->removeTarget(SWALLOWER->layoutTarget());
SWALLOWER->setHidden(true);
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
}
m_firstMap = false;
@@ -2382,7 +2068,7 @@ void CWindow::mapWindow() {
// apply data from default decos. Borders, shadows.
g_pDecorationPositioner->forceRecalcFor(m_self.lock());
updateWindowDecos();
- g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock());
+ layoutTarget()->recalc();
// do animations
g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_IN);
@@ -2454,10 +2140,10 @@ void CWindow::unmapWindow() {
m_swallowed->m_currentlySwallowed = false;
m_swallowed->setHidden(false);
- if (m_groupData.pNextWindow.lock())
+ if (m_group)
m_swallowed->m_groupSwallowed = true; // flag for the swallowed window to be created into the group where it belongs when auto_group = false.
- g_pLayoutManager->getCurrentLayout()->onWindowCreated(m_swallowed.lock());
+ g_layoutManager->newTarget(m_swallowed->layoutTarget(), m_workspace->m_space);
}
m_swallowed->m_groupSwallowed = false;
@@ -2466,7 +2152,7 @@ void CWindow::unmapWindow() {
bool wasLastWindow = false;
PHLWINDOW nextInGroup = [this] -> PHLWINDOW {
- if (!m_groupData.pNextWindow)
+ if (!m_group)
return nullptr;
// walk the history to find a suitable window
@@ -2475,7 +2161,7 @@ void CWindow::unmapWindow() {
if (!w || !w->m_isMapped || w == m_self)
continue;
- if (!hasInGroup(w.lock()))
+ if (!m_group->has(w.lock()))
continue;
return w.lock();
@@ -2491,7 +2177,7 @@ void CWindow::unmapWindow() {
g_pInputManager->releaseAllMouseButtons();
}
- if (m_self.lock() == g_pInputManager->m_currentlyDraggedWindow.lock())
+ if (m_self.lock() == g_layoutManager->dragController()->target())
CKeybindManager::changeMouseBindMode(MBIND_INVALID);
// remove the fullscreen window status from workspace if we closed it
@@ -2500,7 +2186,10 @@ void CWindow::unmapWindow() {
if (PWORKSPACE->m_hasFullscreenWindow && isFullscreen())
PWORKSPACE->m_hasFullscreenWindow = false;
- g_pLayoutManager->getCurrentLayout()->onWindowRemoved(m_self.lock());
+ if (m_group)
+ m_group->remove(m_self.lock());
+
+ g_layoutManager->removeTarget(m_target);
g_pHyprRenderer->damageWindow(m_self.lock());
@@ -2516,17 +2205,20 @@ void CWindow::unmapWindow() {
if (*FOCUSONCLOSE)
candidate = (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(),
Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING));
- else
- candidate = g_pLayoutManager->getCurrentLayout()->getNextWindowCandidate(m_self.lock());
+ else {
+ const auto CAND = g_layoutManager->getNextCandidate(m_workspace->m_space, layoutTarget());
+ if (CAND)
+ candidate = CAND->window();
+ }
}
Log::logger->log(Log::DEBUG, "On closed window, new focused candidate is {}", candidate);
if (candidate != Desktop::focusState()->window() && candidate) {
if (candidate == nextInGroup)
- Desktop::focusState()->rawWindowFocus(candidate);
+ Desktop::focusState()->rawWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE);
else
- Desktop::focusState()->fullWindowFocus(candidate);
+ Desktop::focusState()->fullWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE);
if ((*PEXITRETAINSFS || candidate == nextInGroup) && CURRENTWINDOWFSSTATE)
g_pCompositor->setWindowFullscreenInternal(candidate, CURRENTFSMODE);
@@ -2541,7 +2233,8 @@ void CWindow::unmapWindow() {
if (m_self.lock() == Desktop::focusState()->window() || !Desktop::focusState()->window()) {
g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","});
g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""});
- EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr});
+
+ EMIT_HOOK_EVENT("activeWindow", Desktop::View::SWindowActiveEvent{nullptr COMMA FOCUS_REASON_OTHER});
}
} else {
Log::logger->log(Log::DEBUG, "Unmapped was not focused, ignoring a refocus.");
@@ -2573,7 +2266,13 @@ void CWindow::commitWindow() {
// try to calculate static rules already for any floats
m_ruleApplicator->readStaticRules(true);
- Vector2D predSize = g_pLayoutManager->getCurrentLayout()->predictSizeForNewWindow(m_self.lock());
+ const Vector2D predSize = !m_ruleApplicator->static_.floating.value_or(false) // no float rule
+ && !m_isFloating // not floating
+ && !parent() // no parents
+ && !g_pXWaylandManager->shouldBeFloated(m_self.lock(), true) // should not be floated
+ ?
+ g_layoutManager->predictSizeForNewTiledTarget().value_or(Vector2D{}) :
+ Vector2D{};
Log::logger->log(Log::DEBUG, "Layout predicts size {} for {}", predSize, m_self.lock());
@@ -2634,7 +2333,7 @@ void CWindow::destroyWindow() {
m_listeners = {};
- g_pLayoutManager->getCurrentLayout()->onWindowRemoved(m_self.lock());
+ g_layoutManager->removeTarget(m_target);
m_readyToDelete = true;
@@ -2664,7 +2363,7 @@ void CWindow::activateX11() {
if (!m_xwaylandSurface->wantsFocus())
return;
- Desktop::focusState()->fullWindowFocus(m_self.lock());
+ Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
return;
}
@@ -2764,3 +2463,26 @@ std::optional<Vector2D> CWindow::maxSize() {
return maxSize;
}
+
+SP<Layout::ITarget> CWindow::layoutTarget() {
+ return m_group ? m_group->m_target : m_target;
+}
+
+bool CWindow::canBeGroupedInto(SP<CGroup> group) {
+ if (!group)
+ return false;
+
+ if (isX11OverrideRedirect())
+ return false;
+
+ static auto ALLOWGROUPMERGE = CConfigValue<Hyprlang::INT>("group:merge_groups_on_drag");
+ bool isGroup = m_group;
+ bool disallowDragIntoGroup = g_layoutManager->dragController()->wasDraggingWindow() && isGroup && !sc<bool>(*ALLOWGROUPMERGE);
+ return !g_pKeybindManager->m_groupsLocked // global group lock disengaged
+ && ((m_groupRules & GROUP_INVADE && m_firstMap) // window ignore local group locks, or
+ || (!group->locked() // target unlocked
+ && !(m_group && m_group->locked()))) // source unlocked or isn't group
+ && !(m_groupRules & GROUP_DENY) // source is not denied entry
+ && !(m_groupRules & GROUP_BARRED && m_firstMap) // group rule doesn't prevent adding window
+ && !disallowDragIntoGroup; // config allows groups to be merged
+}
diff --git a/src/desktop/view/Window.hpp b/src/desktop/view/Window.hpp
index 38e8ef0b..a986a63b 100644
--- a/src/desktop/view/Window.hpp
+++ b/src/desktop/view/Window.hpp
@@ -26,8 +26,19 @@ struct SWorkspaceRule;
class IWindowTransformer;
+namespace Layout {
+ class ITarget;
+ class CWindowTarget;
+}
+
+namespace Desktop {
+ enum eFocusReason : uint8_t;
+}
+
namespace Desktop::View {
+ class CGroup;
+
enum eGroupRules : uint8_t {
// effective only during first map, except for _ALWAYS variant
GROUP_NONE = 0,
@@ -38,6 +49,7 @@ namespace Desktop::View {
GROUP_LOCK_ALWAYS = 1 << 4,
GROUP_INVADE = 1 << 5, // Force enter a group, event if lock is engaged
GROUP_OVERRIDE = 1 << 6, // Override other rules
+ GROUP_DENY = 1 << 7, // deny
};
enum eGetWindowProperties : uint8_t {
@@ -61,6 +73,11 @@ namespace Desktop::View {
SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4,
};
+ struct SWindowActiveEvent {
+ PHLWINDOW window = nullptr;
+ eFocusReason reason = sc<eFocusReason>(0) /* unknown */;
+ };
+
struct SInitialWorkspaceToken {
PHLWINDOWREF primaryOwner;
std::string workspace;
@@ -97,6 +114,8 @@ namespace Desktop::View {
WP<CXDGSurfaceResource> m_xdgSurface;
WP<CXWaylandSurface> m_xwaylandSurface;
+ SP<Layout::ITarget> m_target;
+
// this is the position and size of the "bounding box"
Vector2D m_position = Vector2D(0, 0);
Vector2D m_size = Vector2D(0, 0);
@@ -112,23 +131,14 @@ namespace Desktop::View {
std::optional<std::pair<uint32_t, Vector2D>> m_pendingSizeAck;
std::vector<std::pair<uint32_t, Vector2D>> m_pendingSizeAcks;
- // for restoring floating statuses
- Vector2D m_lastFloatingSize;
- Vector2D m_lastFloatingPosition;
-
// for floating window offset in workspace animations
Vector2D m_floatingOffset = Vector2D(0, 0);
- // this is used for pseudotiling
- bool m_isPseudotiled = false;
- Vector2D m_pseudoSize = Vector2D(1280, 720);
-
// for recovering relative cursor position
Vector2D m_relativeCursorCoordsOnLastWarp = Vector2D(-1, -1);
bool m_firstMap = false; // for layouts
bool m_isFloating = false;
- bool m_draggingTiled = false; // for dragging around tiled windows
SFullscreenState m_fullscreenState = {.internal = FSMODE_NONE, .client = FSMODE_NONE};
std::string m_title = "";
std::string m_class = "";
@@ -229,15 +239,10 @@ namespace Desktop::View {
std::string m_initialWorkspaceToken = "";
// for groups
- struct SGroupData {
- PHLWINDOWREF pNextWindow; // nullptr means no grouping. Self means single group.
- bool head = false;
- bool locked = false; // per group lock
- bool deny = false; // deny window from enter a group or made a group
- } m_groupData;
- uint16_t m_groupRules = Desktop::View::GROUP_NONE;
+ SP<CGroup> m_group;
+ uint16_t m_groupRules = Desktop::View::GROUP_NONE;
- bool m_tearingHint = false;
+ bool m_tearingHint = false;
// Stable ID for ext_foreign_toplevel_list
const uint64_t m_stableID = 0x2137;
@@ -303,21 +308,6 @@ namespace Desktop::View {
bool isInCurvedCorner(double x, double y);
bool hasPopupAt(const Vector2D& pos);
int popupsCount();
- void applyGroupRules();
- void createGroup();
- void destroyGroup();
- PHLWINDOW getGroupHead();
- PHLWINDOW getGroupTail();
- PHLWINDOW getGroupCurrent();
- PHLWINDOW getGroupPrevious();
- PHLWINDOW getGroupWindowByIndex(int);
- bool hasInGroup(PHLWINDOW);
- int getGroupSize();
- bool canBeGroupedInto(PHLWINDOW pWindow);
- void setGroupCurrent(PHLWINDOW pWindow);
- void insertWindowToGroup(PHLWINDOW pWindow);
- void updateGroupOutputs();
- void switchWithWindowInGroup(PHLWINDOW pWindow);
void setAnimationsToMove();
void onWorkspaceAnimUpdate();
void onFocusAnimUpdate();
@@ -350,6 +340,8 @@ namespace Desktop::View {
std::optional<Vector2D> calculateExpression(const std::string& s);
std::optional<Vector2D> minSize();
std::optional<Vector2D> maxSize();
+ SP<Layout::ITarget> layoutTarget();
+ bool canBeGroupedInto(SP<CGroup> group);
CBox getWindowMainSurfaceBox() const {
return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y};
diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp
index 6cc0087a..593e4444 100644
--- a/src/helpers/Monitor.cpp
+++ b/src/helpers/Monitor.cpp
@@ -22,11 +22,11 @@
#include "../protocols/core/DataDevice.hpp"
#include "../render/Renderer.hpp"
#include "../managers/EventManager.hpp"
-#include "../managers/LayoutManager.hpp"
#include "../managers/animation/AnimationManager.hpp"
#include "../managers/animation/DesktopAnimationManager.hpp"
#include "../managers/input/InputManager.hpp"
#include "../hyprerror/HyprError.hpp"
+#include "../layout/LayoutManager.hpp"
#include "../i18n/Engine.hpp"
#include "sync/SyncTimeline.hpp"
#include "time/Time.hpp"
@@ -306,7 +306,7 @@ void CMonitor::onConnect(bool noRule) {
Desktop::focusState()->rawMonitorFocus(m_self.lock());
g_pHyprRenderer->arrangeLayersForMonitor(m_id);
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
+ g_layoutManager->recalculateMonitor(m_self.lock());
// ensure VRR (will enable if necessary)
g_pConfigManager->ensureVRR(m_self.lock());
@@ -1119,7 +1119,7 @@ void CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) {
// workspace exists, move it to the newly connected monitor
g_pCompositor->moveWorkspaceToMonitor(PNEWWORKSPACE, m_self.lock());
m_activeWorkspace = PNEWWORKSPACE;
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
+ g_layoutManager->recalculateMonitor(m_self.lock());
g_pDesktopAnimationManager->startAnimation(PNEWWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
} else {
if (newDefaultWorkspaceName.empty())
@@ -1323,13 +1323,13 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
pWindow = pWorkspace->getFirstWindow();
}
- Desktop::focusState()->fullWindowFocus(pWindow);
+ Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);
}
if (!noMouseMove)
g_pInputManager->simulateMouseMovement();
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
+ g_layoutManager->recalculateMonitor(m_self.lock());
g_pEventManager->postEvent(SHyprIPCEvent{"workspace", pWorkspace->m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)});
@@ -1392,11 +1392,11 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
if (POLDSPECIAL)
POLDSPECIAL->m_events.activeChanged.emit();
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
+ g_layoutManager->recalculateMonitor(m_self.lock());
if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) {
if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST)
- Desktop::focusState()->fullWindowFocus(PLAST);
+ Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);
else
g_pInputManager->refocus();
}
@@ -1421,7 +1421,7 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
const auto PMONITORWORKSPACEOWNER = pWorkspace->m_monitor.lock();
if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) {
PMWSOWNER->m_activeSpecialWorkspace.reset();
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PMWSOWNER->m_id);
+ g_layoutManager->recalculateMonitor(PMWSOWNER);
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name});
@@ -1480,17 +1480,16 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
} else
pos = pos - PMONFROMMIDDLE->m_position + m_position;
- *w->m_realPosition = pos;
- w->m_position = pos;
+ w->layoutTarget()->setPositionGlobal(CBox{pos, w->layoutTarget()->position().size()});
}
}
}
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
+ g_layoutManager->recalculateMonitor(m_self.lock());
if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) {
if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST)
- Desktop::focusState()->fullWindowFocus(PLAST);
+ Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);
else
g_pInputManager->refocus();
}
diff --git a/src/helpers/math/Direction.cpp b/src/helpers/math/Direction.cpp
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/helpers/math/Direction.cpp
diff --git a/src/helpers/math/Direction.hpp b/src/helpers/math/Direction.hpp
new file mode 100644
index 00000000..9905db4f
--- /dev/null
+++ b/src/helpers/math/Direction.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <cstdint>
+
+namespace Math {
+ enum eDirection : int8_t {
+ DIRECTION_DEFAULT = -1,
+ DIRECTION_UP,
+ DIRECTION_RIGHT,
+ DIRECTION_DOWN,
+ DIRECTION_LEFT
+ };
+
+ inline eDirection fromChar(char x) {
+ switch (x) {
+ case 'r': return DIRECTION_RIGHT;
+ case 'l': return DIRECTION_LEFT;
+ case 't':
+ case 'u': return DIRECTION_UP;
+ case 'b':
+ case 'd': return DIRECTION_DOWN;
+ default: return DIRECTION_DEFAULT;
+ }
+ }
+
+ inline const char* toString(eDirection d) {
+ switch (d) {
+ case DIRECTION_UP: return "up";
+ case DIRECTION_DOWN: return "down";
+ case DIRECTION_LEFT: return "left";
+ case DIRECTION_RIGHT: return "right";
+ default: return "default";
+ }
+ }
+}; \ No newline at end of file
diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp
deleted file mode 100644
index 70d052ea..00000000
--- a/src/layout/DwindleLayout.cpp
+++ /dev/null
@@ -1,1190 +0,0 @@
-#include "DwindleLayout.hpp"
-#include "../Compositor.hpp"
-#include "../config/ConfigValue.hpp"
-#include "../config/ConfigManager.hpp"
-#include "../render/decorations/CHyprGroupBarDecoration.hpp"
-#include "../render/Renderer.hpp"
-#include "../managers/input/InputManager.hpp"
-#include "../managers/LayoutManager.hpp"
-#include "../managers/EventManager.hpp"
-#include "../desktop/state/FocusState.hpp"
-#include "xwayland/XWayland.hpp"
-
-void SDwindleNodeData::recalcSizePosRecursive(bool force, bool horizontalOverride, bool verticalOverride) {
- if (children[0]) {
- static auto PSMARTSPLIT = CConfigValue<Hyprlang::INT>("dwindle:smart_split");
- static auto PPRESERVESPLIT = CConfigValue<Hyprlang::INT>("dwindle:preserve_split");
- static auto PFLMULT = CConfigValue<Hyprlang::FLOAT>("dwindle:split_width_multiplier");
-
- if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0)
- splitTop = box.h * *PFLMULT > box.w;
-
- if (verticalOverride)
- splitTop = true;
- else if (horizontalOverride)
- splitTop = false;
-
- const auto SPLITSIDE = !splitTop;
-
- if (SPLITSIDE) {
- // split left/right
- const float FIRSTSIZE = box.w / 2.0 * splitRatio;
- children[0]->box = CBox{box.x, box.y, FIRSTSIZE, box.h}.noNegativeSize();
- children[1]->box = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}.noNegativeSize();
- } else {
- // split top/bottom
- const float FIRSTSIZE = box.h / 2.0 * splitRatio;
- children[0]->box = CBox{box.x, box.y, box.w, FIRSTSIZE}.noNegativeSize();
- children[1]->box = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}.noNegativeSize();
- }
-
- children[0]->recalcSizePosRecursive(force);
- children[1]->recalcSizePosRecursive(force);
- } else {
- layout->applyNodeDataToWindow(self.lock(), force);
- }
-}
-
-void SDwindleNodeData::applyRootBox() {
- box = layout->workAreaOnWorkspace(g_pCompositor->getWorkspaceByID(workspaceID));
-}
-
-int CHyprDwindleLayout::getNodesOnWorkspace(const WORKSPACEID& id) {
- int no = 0;
- for (auto const& n : m_dwindleNodesData) {
- if (n->workspaceID == id && n->valid)
- ++no;
- }
- return no;
-}
-
-SP<SDwindleNodeData> CHyprDwindleLayout::getFirstNodeOnWorkspace(const WORKSPACEID& id) {
- for (auto& n : m_dwindleNodesData) {
- if (n->workspaceID == id && validMapped(n->pWindow))
- return n;
- }
- return nullptr;
-}
-
-SP<SDwindleNodeData> CHyprDwindleLayout::getClosestNodeOnWorkspace(const WORKSPACEID& id, const Vector2D& point) {
- SP<SDwindleNodeData> res = nullptr;
- double distClosest = -1;
- for (auto& n : m_dwindleNodesData) {
- if (n->workspaceID == id && validMapped(n->pWindow)) {
- auto distAnother = vecToRectDistanceSquared(point, n->box.pos(), n->box.pos() + n->box.size());
- if (!res || distAnother < distClosest) {
- res = n;
- distClosest = distAnother;
- }
- }
- }
- return res;
-}
-
-SP<SDwindleNodeData> CHyprDwindleLayout::getNodeFromWindow(PHLWINDOW pWindow) {
- for (auto& n : m_dwindleNodesData) {
- if (n->pWindow.lock() == pWindow && !n->isNode)
- return n;
- }
-
- return nullptr;
-}
-
-SP<SDwindleNodeData> CHyprDwindleLayout::getMasterNodeOnWorkspace(const WORKSPACEID& id) {
- for (auto& n : m_dwindleNodesData) {
- if (!n->pParent && n->workspaceID == id)
- return n;
- }
- return nullptr;
-}
-
-void CHyprDwindleLayout::applyNodeDataToWindow(SP<SDwindleNodeData> pNode, bool force) {
- // Don't set nodes, only windows.
- if (pNode->isNode)
- return;
-
- PHLMONITOR PMONITOR = nullptr;
-
- const auto WS = g_pCompositor->getWorkspaceByID(pNode->workspaceID);
-
- if (g_pCompositor->isWorkspaceSpecial(pNode->workspaceID)) {
- for (auto const& m : g_pCompositor->m_monitors) {
- if (m->activeSpecialWorkspaceID() == pNode->workspaceID) {
- PMONITOR = m;
- break;
- }
- }
- } else if (WS)
- PMONITOR = WS->m_monitor.lock();
-
- if (!PMONITOR || !WS) {
- Log::logger->log(Log::ERR, "Orphaned Node {}!!", pNode);
- return;
- }
-
- // for gaps outer
- const auto MONITOR_WORKAREA = workAreaOnWorkspace(WS);
- const bool DISPLAYLEFT = STICKS(pNode->box.x, MONITOR_WORKAREA.x);
- const bool DISPLAYRIGHT = STICKS(pNode->box.x + pNode->box.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);
- const bool DISPLAYTOP = STICKS(pNode->box.y, MONITOR_WORKAREA.y);
- const bool DISPLAYBOTTOM = STICKS(pNode->box.y + pNode->box.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h);
-
- const auto PWINDOW = pNode->pWindow.lock();
- // get specific gaps and rules for this workspace,
- // if user specified them in config
- const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(g_pCompositor->getWorkspaceByID(pNode->workspaceID));
-
- if (!validMapped(PWINDOW)) {
- Log::logger->log(Log::ERR, "Node {} holding invalid {}!!", pNode, PWINDOW);
- onWindowRemovedTiling(PWINDOW);
- return;
- }
-
- if (PWINDOW->isFullscreen() && !pNode->ignoreFullscreenChecks)
- return;
-
- PWINDOW->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT);
- PWINDOW->updateWindowData();
-
- static auto PGAPSINDATA = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_in");
- auto* const PGAPSIN = sc<CCssGapData*>((PGAPSINDATA.ptr())->getData());
-
- auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN);
- CBox nodeBox = pNode->box;
- nodeBox.round();
-
- PWINDOW->m_size = nodeBox.size();
- PWINDOW->m_position = nodeBox.pos();
-
- PWINDOW->updateWindowDecos();
-
- auto calcPos = PWINDOW->m_position;
- auto calcSize = PWINDOW->m_size;
-
- const static auto REQUESTEDRATIO = CConfigValue<Hyprlang::VEC2>("dwindle:single_window_aspect_ratio");
- const static auto REQUESTEDRATIOTOLERANCE = CConfigValue<Hyprlang::FLOAT>("dwindle:single_window_aspect_ratio_tolerance");
-
- Vector2D ratioPadding;
-
- if ((*REQUESTEDRATIO).y != 0 && !pNode->pParent) {
- const Vector2D originalSize = MONITOR_WORKAREA.size();
-
- const double requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y;
- const double originalRatio = originalSize.x / originalSize.y;
-
- if (requestedRatio > originalRatio) {
- double padding = originalSize.y - (originalSize.x / requestedRatio);
-
- if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.y)
- ratioPadding = Vector2D{0., padding};
- } else if (requestedRatio < originalRatio) {
- double padding = originalSize.x - (originalSize.y * requestedRatio);
-
- if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.x)
- ratioPadding = Vector2D{padding, 0.};
- }
- }
-
- const auto GAPOFFSETTOPLEFT = Vector2D(sc<double>(DISPLAYLEFT ? 0 : gapsIn.m_left), sc<double>(DISPLAYTOP ? 0 : gapsIn.m_top));
-
- const auto GAPOFFSETBOTTOMRIGHT = Vector2D(sc<double>(DISPLAYRIGHT ? 0 : gapsIn.m_right), sc<double>(DISPLAYBOTTOM ? 0 : gapsIn.m_bottom));
-
- calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2;
- calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding;
-
- if (PWINDOW->m_isPseudotiled) {
- // Calculate pseudo
- float scale = 1;
-
- // adjust if doesn't fit
- if (PWINDOW->m_pseudoSize.x > calcSize.x || PWINDOW->m_pseudoSize.y > calcSize.y) {
- if (PWINDOW->m_pseudoSize.x > calcSize.x) {
- scale = calcSize.x / PWINDOW->m_pseudoSize.x;
- }
-
- if (PWINDOW->m_pseudoSize.y * scale > calcSize.y) {
- scale = calcSize.y / PWINDOW->m_pseudoSize.y;
- }
-
- auto DELTA = calcSize - PWINDOW->m_pseudoSize * scale;
- calcSize = PWINDOW->m_pseudoSize * scale;
- calcPos = calcPos + DELTA / 2.f; // center
- } else {
- auto DELTA = calcSize - PWINDOW->m_pseudoSize;
- calcPos = calcPos + DELTA / 2.f; // center
- calcSize = PWINDOW->m_pseudoSize;
- }
- }
-
- const auto RESERVED = PWINDOW->getFullWindowReservedArea();
- calcPos = calcPos + RESERVED.topLeft;
- calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight);
-
- Vector2D availableSpace = calcSize;
-
- static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>("misc:size_limits_tiled");
-
- if (*PCLAMP_TILED) {
- const auto borderSize = PWINDOW->getRealBorderSize();
- Vector2D monitorAvailable = MONITOR_WORKAREA.size() - Vector2D{2.0 * borderSize, 2.0 * borderSize};
-
- Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable);
- Vector2D maxSize = PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} :
- PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable);
- calcSize = calcSize.clamp(minSize, maxSize);
-
- calcPos += (availableSpace - calcSize) / 2.0;
-
- calcPos.x = std::clamp(calcPos.x, MONITOR_WORKAREA.x + borderSize, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w - calcSize.x - borderSize);
- calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y + borderSize, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y - borderSize);
- }
-
- if (PWINDOW->onSpecialWorkspace() && !PWINDOW->isFullscreen()) {
- // if special, we adjust the coords a bit
- static auto PSCALEFACTOR = CConfigValue<Hyprlang::FLOAT>("dwindle:special_scale_factor");
-
- CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR};
- wb.round(); // avoid rounding mess
-
- *PWINDOW->m_realPosition = wb.pos();
- *PWINDOW->m_realSize = wb.size();
- } else {
- CBox wb = {calcPos, calcSize};
- wb.round(); // avoid rounding mess
-
- *PWINDOW->m_realSize = wb.size();
- *PWINDOW->m_realPosition = wb.pos();
- }
-
- if (force) {
- g_pHyprRenderer->damageWindow(PWINDOW);
-
- PWINDOW->m_realPosition->warp();
- PWINDOW->m_realSize->warp();
-
- g_pHyprRenderer->damageWindow(PWINDOW);
- }
-
- PWINDOW->updateWindowDecos();
-}
-
-void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection direction) {
- if (pWindow->m_isFloating)
- return;
-
- const auto PNODE = m_dwindleNodesData.emplace_back(makeShared<SDwindleNodeData>());
- PNODE->self = PNODE;
-
- const auto PMONITOR = pWindow->m_monitor.lock();
-
- static auto PUSEACTIVE = CConfigValue<Hyprlang::INT>("dwindle:use_active_for_splits");
- static auto PDEFAULTSPLIT = CConfigValue<Hyprlang::FLOAT>("dwindle:default_split_ratio");
-
- if (direction != DIRECTION_DEFAULT && m_overrideDirection == DIRECTION_DEFAULT)
- m_overrideDirection = direction;
-
- // Populate the node with our window's data
- PNODE->workspaceID = pWindow->workspaceID();
- PNODE->pWindow = pWindow;
- PNODE->isNode = false;
- PNODE->layout = this;
-
- SP<SDwindleNodeData> OPENINGON;
-
- const auto MOUSECOORDS = m_overrideFocalPoint.value_or(g_pInputManager->getMouseCoordsInternal());
- const auto MONFROMCURSOR = g_pCompositor->getMonitorFromVector(MOUSECOORDS);
-
- if (PMONITOR->m_id == MONFROMCURSOR->m_id &&
- (PNODE->workspaceID == PMONITOR->activeWorkspaceID() || (g_pCompositor->isWorkspaceSpecial(PNODE->workspaceID) && PMONITOR->m_activeSpecialWorkspace)) && !*PUSEACTIVE) {
- OPENINGON = getNodeFromWindow(
- g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::SKIP_FULLSCREEN_PRIORITY));
-
- if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, PMONITOR))
- OPENINGON = getClosestNodeOnWorkspace(PNODE->workspaceID, MOUSECOORDS);
-
- } else if (*PUSEACTIVE) {
- if (Desktop::focusState()->window() && !Desktop::focusState()->window()->m_isFloating && Desktop::focusState()->window() != pWindow &&
- Desktop::focusState()->window()->m_workspace == pWindow->m_workspace && Desktop::focusState()->window()->m_isMapped) {
- OPENINGON = getNodeFromWindow(Desktop::focusState()->window());
- } else {
- OPENINGON = getNodeFromWindow(g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS));
- }
-
- if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, PMONITOR))
- OPENINGON = getClosestNodeOnWorkspace(PNODE->workspaceID, MOUSECOORDS);
-
- } else
- OPENINGON = getFirstNodeOnWorkspace(pWindow->workspaceID());
-
- Log::logger->log(Log::DEBUG, "OPENINGON: {}, Monitor: {}", OPENINGON, PMONITOR->m_id);
-
- if (OPENINGON && OPENINGON->workspaceID != PNODE->workspaceID) {
- // special workspace handling
- OPENINGON = getFirstNodeOnWorkspace(PNODE->workspaceID);
- }
-
- // first, check if OPENINGON isn't too big.
- const auto PREDSIZEMAX = OPENINGON ? Vector2D(OPENINGON->box.w, OPENINGON->box.h) : PMONITOR->m_size;
- if (const auto MAXSIZE = pWindow->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PREDSIZEMAX.x || MAXSIZE.y < PREDSIZEMAX.y) {
- // we can't continue. make it floating.
- pWindow->m_isFloating = true;
- std::erase(m_dwindleNodesData, PNODE);
- g_pLayoutManager->getCurrentLayout()->onWindowCreatedFloating(pWindow);
- return;
- }
-
- // last fail-safe to avoid duplicate fullscreens
- if ((!OPENINGON || OPENINGON->pWindow.lock() == pWindow) && getNodesOnWorkspace(PNODE->workspaceID) > 1) {
- for (auto& node : m_dwindleNodesData) {
- if (node->workspaceID == PNODE->workspaceID && node->pWindow.lock() && node->pWindow.lock() != pWindow) {
- OPENINGON = node;
- break;
- }
- }
- }
-
- // if it's the first, it's easy. Make it fullscreen.
- if (!OPENINGON || OPENINGON->pWindow.lock() == pWindow) {
- PNODE->applyRootBox();
- applyNodeDataToWindow(PNODE);
- return;
- }
-
- // get the node under our cursor
-
- const auto NEWPARENT = m_dwindleNodesData.emplace_back(makeShared<SDwindleNodeData>());
-
- // make the parent have the OPENINGON's stats
- NEWPARENT->box = OPENINGON->box;
- NEWPARENT->workspaceID = OPENINGON->workspaceID;
- NEWPARENT->pParent = OPENINGON->pParent;
- NEWPARENT->isNode = true; // it is a node
- NEWPARENT->splitRatio = std::clamp(*PDEFAULTSPLIT, 0.1f, 1.9f);
- NEWPARENT->layout = this;
-
- static auto PWIDTHMULTIPLIER = CConfigValue<Hyprlang::FLOAT>("dwindle:split_width_multiplier");
-
- // if cursor over first child, make it first, etc
- const auto SIDEBYSIDE = NEWPARENT->box.w > NEWPARENT->box.h * *PWIDTHMULTIPLIER;
- NEWPARENT->splitTop = !SIDEBYSIDE;
-
- static auto PFORCESPLIT = CConfigValue<Hyprlang::INT>("dwindle:force_split");
- static auto PERMANENTDIRECTIONOVERRIDE = CConfigValue<Hyprlang::INT>("dwindle:permanent_direction_override");
- static auto PSMARTSPLIT = CConfigValue<Hyprlang::INT>("dwindle:smart_split");
- static auto PSPLITBIAS = CConfigValue<Hyprlang::INT>("dwindle:split_bias");
-
- bool horizontalOverride = false;
- bool verticalOverride = false;
-
- // let user select position -> top, right, bottom, left
- if (m_overrideDirection != DIRECTION_DEFAULT) {
-
- // this is horizontal
- if (m_overrideDirection % 2 == 0)
- verticalOverride = true;
- else
- horizontalOverride = true;
-
- // 0 -> top and left | 1,2 -> right and bottom
- if (m_overrideDirection % 3 == 0) {
- NEWPARENT->children[1] = OPENINGON;
- NEWPARENT->children[0] = PNODE;
- } else {
- NEWPARENT->children[0] = OPENINGON;
- NEWPARENT->children[1] = PNODE;
- }
-
- // whether or not the override persists after opening one window
- if (*PERMANENTDIRECTIONOVERRIDE == 0)
- m_overrideDirection = DIRECTION_DEFAULT;
- } else if (*PSMARTSPLIT == 1) {
- const auto PARENT_CENTER = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2;
- const auto PARENT_PROPORTIONS = NEWPARENT->box.h / NEWPARENT->box.w;
- const auto DELTA = MOUSECOORDS - PARENT_CENTER;
- const auto DELTA_SLOPE = DELTA.y / DELTA.x;
-
- if (abs(DELTA_SLOPE) < PARENT_PROPORTIONS) {
- if (DELTA.x > 0) {
- // right
- NEWPARENT->splitTop = false;
- NEWPARENT->children[0] = OPENINGON;
- NEWPARENT->children[1] = PNODE;
- } else {
- // left
- NEWPARENT->splitTop = false;
- NEWPARENT->children[0] = PNODE;
- NEWPARENT->children[1] = OPENINGON;
- }
- } else {
- if (DELTA.y > 0) {
- // bottom
- NEWPARENT->splitTop = true;
- NEWPARENT->children[0] = OPENINGON;
- NEWPARENT->children[1] = PNODE;
- } else {
- // top
- NEWPARENT->splitTop = true;
- NEWPARENT->children[0] = PNODE;
- NEWPARENT->children[1] = OPENINGON;
- }
- }
- } else if (*PFORCESPLIT == 0 || !pWindow->m_firstMap) {
- if ((SIDEBYSIDE &&
- VECINRECT(MOUSECOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y + NEWPARENT->box.h)) ||
- (!SIDEBYSIDE &&
- VECINRECT(MOUSECOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w, NEWPARENT->box.y + NEWPARENT->box.h / 2.f))) {
- // we are hovering over the first node, make PNODE first.
- NEWPARENT->children[1] = OPENINGON;
- NEWPARENT->children[0] = PNODE;
- } else {
- // we are hovering over the second node, make PNODE second.
- NEWPARENT->children[0] = OPENINGON;
- NEWPARENT->children[1] = PNODE;
- }
- } else {
- if (*PFORCESPLIT == 1) {
- NEWPARENT->children[1] = OPENINGON;
- NEWPARENT->children[0] = PNODE;
- } else {
- NEWPARENT->children[0] = OPENINGON;
- NEWPARENT->children[1] = PNODE;
- }
- }
-
- // split in favor of a specific window
- if (*PSPLITBIAS && NEWPARENT->children[0] == PNODE)
- NEWPARENT->splitRatio = 2.f - NEWPARENT->splitRatio;
-
- // and update the previous parent if it exists
- if (OPENINGON->pParent) {
- if (OPENINGON->pParent->children[0] == OPENINGON) {
- OPENINGON->pParent->children[0] = NEWPARENT;
- } else {
- OPENINGON->pParent->children[1] = NEWPARENT;
- }
- }
-
- // Update the children
- if (!verticalOverride && (NEWPARENT->box.w * *PWIDTHMULTIPLIER > NEWPARENT->box.h || horizontalOverride)) {
- // split left/right -> forced
- OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)};
- PNODE->box = {Vector2D(NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)};
- } else {
- // split top/bottom
- OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)};
- PNODE->box = {Vector2D(NEWPARENT->box.x, NEWPARENT->box.y + NEWPARENT->box.h / 2.f), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)};
- }
-
- OPENINGON->pParent = NEWPARENT;
- PNODE->pParent = NEWPARENT;
-
- NEWPARENT->recalcSizePosRecursive(false, horizontalOverride, verticalOverride);
-
- recalculateMonitor(pWindow->monitorID());
- pWindow->m_workspace->updateWindows();
-}
-
-void CHyprDwindleLayout::onWindowRemovedTiling(PHLWINDOW pWindow) {
-
- const auto PNODE = getNodeFromWindow(pWindow);
-
- if (!PNODE) {
- Log::logger->log(Log::ERR, "onWindowRemovedTiling node null?");
- return;
- }
-
- pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT);
- pWindow->updateWindowData();
-
- if (pWindow->isFullscreen())
- g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE_NONE);
-
- const auto PPARENT = PNODE->pParent;
-
- if (!PPARENT) {
- Log::logger->log(Log::DEBUG, "Removing last node (dwindle)");
- std::erase(m_dwindleNodesData, PNODE);
- return;
- }
-
- const auto PSIBLING = PPARENT->children[0] == PNODE ? PPARENT->children[1] : PPARENT->children[0];
-
- PSIBLING->box = PPARENT->box;
- PSIBLING->pParent = PPARENT->pParent;
-
- if (PPARENT->pParent != nullptr) {
- if (PPARENT->pParent->children[0] == PPARENT) {
- PPARENT->pParent->children[0] = PSIBLING;
- } else {
- PPARENT->pParent->children[1] = PSIBLING;
- }
- }
-
- PPARENT->valid = false;
- PNODE->valid = false;
-
- if (PSIBLING->pParent)
- PSIBLING->pParent->recalcSizePosRecursive();
- else
- PSIBLING->recalcSizePosRecursive();
-
- std::erase(m_dwindleNodesData, PPARENT);
- std::erase(m_dwindleNodesData, PNODE);
- pWindow->m_workspace->updateWindows();
-}
-
-void CHyprDwindleLayout::recalculateMonitor(const MONITORID& monid) {
- const auto PMONITOR = g_pCompositor->getMonitorFromID(monid);
-
- if (!PMONITOR || !PMONITOR->m_activeWorkspace)
- return; // ???
-
- g_pHyprRenderer->damageMonitor(PMONITOR);
-
- if (PMONITOR->m_activeSpecialWorkspace)
- calculateWorkspace(PMONITOR->m_activeSpecialWorkspace);
-
- calculateWorkspace(PMONITOR->m_activeWorkspace);
-
-#ifndef NO_XWAYLAND
- CBox box = g_pCompositor->calculateX11WorkArea();
- if (!g_pXWayland || !g_pXWayland->m_wm)
- return;
- g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h);
-#endif
-}
-
-void CHyprDwindleLayout::calculateWorkspace(const PHLWORKSPACE& pWorkspace) {
- const auto PMONITOR = pWorkspace->m_monitor.lock();
-
- if (!PMONITOR)
- return;
-
- if (pWorkspace->m_hasFullscreenWindow) {
- // massive hack from the fullscreen func
- const auto PFULLWINDOW = pWorkspace->getFullscreenWindow();
-
- if (pWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) {
- *PFULLWINDOW->m_realPosition = PMONITOR->m_position;
- *PFULLWINDOW->m_realSize = PMONITOR->m_size;
- } else if (pWorkspace->m_fullscreenMode == FSMODE_MAXIMIZED) {
- SP<SDwindleNodeData> fakeNode = makeShared<SDwindleNodeData>();
- fakeNode->self = fakeNode;
- fakeNode->pWindow = PFULLWINDOW;
- fakeNode->box = workAreaOnWorkspace(pWorkspace);
- fakeNode->workspaceID = pWorkspace->m_id;
- PFULLWINDOW->m_position = fakeNode->box.pos();
- PFULLWINDOW->m_size = fakeNode->box.size();
- fakeNode->ignoreFullscreenChecks = true;
- fakeNode->layout = this;
-
- applyNodeDataToWindow(fakeNode);
- }
-
- // if has fullscreen, don't calculate the rest
- return;
- }
-
- const auto TOPNODE = getMasterNodeOnWorkspace(pWorkspace->m_id);
-
- if (TOPNODE) {
- TOPNODE->applyRootBox();
- TOPNODE->recalcSizePosRecursive();
- }
-}
-
-bool CHyprDwindleLayout::isWindowTiled(PHLWINDOW pWindow) {
- return getNodeFromWindow(pWindow) != nullptr;
-}
-
-void CHyprDwindleLayout::onBeginDragWindow() {
- m_pseudoDragFlags.started = false;
- m_pseudoDragFlags.pseudo = false;
- IHyprLayout::onBeginDragWindow();
-}
-
-void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorner corner, PHLWINDOW pWindow) {
-
- const auto PWINDOW = pWindow ? pWindow : Desktop::focusState()->window();
-
- if (!validMapped(PWINDOW))
- return;
-
- const auto PNODE = getNodeFromWindow(PWINDOW);
-
- if (!PNODE) {
- *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + pixResize)
- .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}),
- PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}));
- PWINDOW->updateWindowDecos();
- return;
- }
-
- static auto PANIMATE = CConfigValue<Hyprlang::INT>("misc:animate_manual_resizes");
- static auto PSMARTRESIZING = CConfigValue<Hyprlang::INT>("dwindle:smart_resizing");
-
- // get some data about our window
- const auto PMONITOR = PWINDOW->m_monitor.lock();
- const auto MONITOR_WORKAREA = PMONITOR->logicalBoxMinusReserved();
- const bool DISPLAYLEFT = STICKS(PWINDOW->m_position.x, MONITOR_WORKAREA.x);
- const bool DISPLAYRIGHT = STICKS(PWINDOW->m_position.x + PWINDOW->m_size.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);
- const bool DISPLAYTOP = STICKS(PWINDOW->m_position.y, MONITOR_WORKAREA.y);
- const bool DISPLAYBOTTOM = STICKS(PWINDOW->m_position.y + PWINDOW->m_size.y, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h);
-
- if (PWINDOW->m_isPseudotiled) {
- if (!m_pseudoDragFlags.started) {
- m_pseudoDragFlags.started = true;
-
- const auto pseudoSize = PWINDOW->m_realSize->goal();
- const auto mouseOffset = g_pInputManager->getMouseCoordsInternal() - (PNODE->box.pos() + ((PNODE->box.size() / 2) - (pseudoSize / 2)));
-
- if (mouseOffset.x > 0 && mouseOffset.x < pseudoSize.x && mouseOffset.y > 0 && mouseOffset.y < pseudoSize.y) {
- m_pseudoDragFlags.pseudo = true;
- m_pseudoDragFlags.xExtent = mouseOffset.x > pseudoSize.x / 2;
- m_pseudoDragFlags.yExtent = mouseOffset.y > pseudoSize.y / 2;
-
- PWINDOW->m_pseudoSize = pseudoSize;
- } else {
- m_pseudoDragFlags.pseudo = false;
- }
- }
-
- if (m_pseudoDragFlags.pseudo) {
- if (m_pseudoDragFlags.xExtent)
- PWINDOW->m_pseudoSize.x += pixResize.x * 2;
- else
- PWINDOW->m_pseudoSize.x -= pixResize.x * 2;
- if (m_pseudoDragFlags.yExtent)
- PWINDOW->m_pseudoSize.y += pixResize.y * 2;
- else
- PWINDOW->m_pseudoSize.y -= pixResize.y * 2;
-
- CBox wbox = PNODE->box;
- wbox.round();
-
- Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{30.0, 30.0});
- Vector2D maxSize = PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY});
- Vector2D upperBound = Vector2D{std::min(maxSize.x, wbox.w), std::min(maxSize.y, wbox.h)};
-
- PWINDOW->m_pseudoSize = PWINDOW->m_pseudoSize.clamp(minSize, upperBound);
-
- PWINDOW->m_lastFloatingSize = PWINDOW->m_pseudoSize;
- PNODE->recalcSizePosRecursive(*PANIMATE == 0);
-
- return;
- }
- }
-
- // construct allowed movement
- Vector2D allowedMovement = pixResize;
- if (DISPLAYLEFT && DISPLAYRIGHT)
- allowedMovement.x = 0;
-
- if (DISPLAYBOTTOM && DISPLAYTOP)
- allowedMovement.y = 0;
-
- if (*PSMARTRESIZING == 1) {
- // Identify inner and outer nodes for both directions
- SP<SDwindleNodeData> PVOUTER = nullptr;
- SP<SDwindleNodeData> PVINNER = nullptr;
- SP<SDwindleNodeData> PHOUTER = nullptr;
- SP<SDwindleNodeData> PHINNER = nullptr;
-
- const auto LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT || DISPLAYRIGHT;
- const auto TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || DISPLAYBOTTOM;
- const auto RIGHT = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || DISPLAYLEFT;
- const auto BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || DISPLAYTOP;
- const auto NONE = corner == CORNER_NONE;
-
- for (auto PCURRENT = PNODE; PCURRENT && PCURRENT->pParent; PCURRENT = PCURRENT->pParent.lock()) {
- const auto PPARENT = PCURRENT->pParent;
-
- if (!PVOUTER && PPARENT->splitTop && (NONE || (TOP && PPARENT->children[1] == PCURRENT) || (BOTTOM && PPARENT->children[0] == PCURRENT)))
- PVOUTER = PCURRENT;
- else if (!PVOUTER && !PVINNER && PPARENT->splitTop)
- PVINNER = PCURRENT;
- else if (!PHOUTER && !PPARENT->splitTop && (NONE || (LEFT && PPARENT->children[1] == PCURRENT) || (RIGHT && PPARENT->children[0] == PCURRENT)))
- PHOUTER = PCURRENT;
- else if (!PHOUTER && !PHINNER && !PPARENT->splitTop)
- PHINNER = PCURRENT;
-
- if (PVOUTER && PHOUTER)
- break;
- }
-
- if (PHOUTER) {
- PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9);
-
- if (PHINNER) {
- const auto ORIGINAL = PHINNER->box.w;
- PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
- if (PHINNER->pParent->children[0] == PHINNER)
- PHINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9);
- else
- PHINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9);
- PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
- } else
- PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
- }
-
- if (PVOUTER) {
- PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9);
-
- if (PVINNER) {
- const auto ORIGINAL = PVINNER->box.h;
- PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
- if (PVINNER->pParent->children[0] == PVINNER)
- PVINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9);
- else
- PVINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9);
- PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
- } else
- PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
- }
- } else {
- // get the correct containers to apply splitratio to
- const auto PPARENT = PNODE->pParent;
-
- if (!PPARENT)
- return; // the only window on a workspace, ignore
-
- const bool PARENTSIDEBYSIDE = !PPARENT->splitTop;
-
- // Get the parent's parent
- auto PPARENT2 = PPARENT->pParent;
-
- // No parent means we have only 2 windows, and thus one axis of freedom
- if (!PPARENT2) {
- if (PARENTSIDEBYSIDE) {
- allowedMovement.x *= 2.f / PPARENT->box.w;
- PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9);
- PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
- } else {
- allowedMovement.y *= 2.f / PPARENT->box.h;
- PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9);
- PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
- }
-
- return;
- }
-
- // Get first parent with other split
- while (PPARENT2 && PPARENT2->splitTop == !PARENTSIDEBYSIDE)
- PPARENT2 = PPARENT2->pParent;
-
- // no parent, one axis of freedom
- if (!PPARENT2) {
- if (PARENTSIDEBYSIDE) {
- allowedMovement.x *= 2.f / PPARENT->box.w;
- PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9);
- PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
- } else {
- allowedMovement.y *= 2.f / PPARENT->box.h;
- PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9);
- PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
- }
-
- return;
- }
-
- // 2 axes of freedom
- const auto SIDECONTAINER = PARENTSIDEBYSIDE ? PPARENT : PPARENT2;
- const auto TOPCONTAINER = PARENTSIDEBYSIDE ? PPARENT2 : PPARENT;
-
- allowedMovement.x *= 2.f / SIDECONTAINER->box.w;
- allowedMovement.y *= 2.f / TOPCONTAINER->box.h;
-
- SIDECONTAINER->splitRatio = std::clamp(SIDECONTAINER->splitRatio + allowedMovement.x, 0.1, 1.9);
- TOPCONTAINER->splitRatio = std::clamp(TOPCONTAINER->splitRatio + allowedMovement.y, 0.1, 1.9);
- SIDECONTAINER->recalcSizePosRecursive(*PANIMATE == 0);
- TOPCONTAINER->recalcSizePosRecursive(*PANIMATE == 0);
- }
-}
-
-void CHyprDwindleLayout::fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE) {
- const auto PMONITOR = pWindow->m_monitor.lock();
- const auto PWORKSPACE = pWindow->m_workspace;
-
- // save position and size if floating
- if (pWindow->m_isFloating && CURRENT_EFFECTIVE_MODE == FSMODE_NONE) {
- pWindow->m_lastFloatingSize = pWindow->m_realSize->goal();
- pWindow->m_lastFloatingPosition = pWindow->m_realPosition->goal();
- pWindow->m_position = pWindow->m_realPosition->goal();
- pWindow->m_size = pWindow->m_realSize->goal();
- }
-
- if (EFFECTIVE_MODE == FSMODE_NONE) {
- // if it got its fullscreen disabled, set back its node if it had one
- const auto PNODE = getNodeFromWindow(pWindow);
- if (PNODE)
- applyNodeDataToWindow(PNODE);
- else {
- // get back its' dimensions from position and size
- *pWindow->m_realPosition = pWindow->m_lastFloatingPosition;
- *pWindow->m_realSize = pWindow->m_lastFloatingSize;
-
- pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT);
- pWindow->updateWindowData();
- }
- } else {
- // apply new pos and size being monitors' box
- if (EFFECTIVE_MODE == FSMODE_FULLSCREEN) {
- *pWindow->m_realPosition = PMONITOR->m_position;
- *pWindow->m_realSize = PMONITOR->m_size;
- } else {
- // This is a massive hack.
- // We make a fake "only" node and apply
- // To keep consistent with the settings without C+P code
-
- SP<SDwindleNodeData> fakeNode = makeShared<SDwindleNodeData>();
- fakeNode->self = fakeNode;
- fakeNode->pWindow = pWindow;
- fakeNode->box = PMONITOR->logicalBoxMinusReserved();
- fakeNode->workspaceID = pWindow->workspaceID();
- pWindow->m_position = fakeNode->box.pos();
- pWindow->m_size = fakeNode->box.size();
- fakeNode->ignoreFullscreenChecks = true;
- fakeNode->layout = this;
-
- applyNodeDataToWindow(fakeNode);
- }
- }
-
- g_pCompositor->changeWindowZOrder(pWindow, true);
-}
-
-void CHyprDwindleLayout::recalculateWindow(PHLWINDOW pWindow) {
- const auto PNODE = getNodeFromWindow(pWindow);
-
- if (!PNODE)
- return;
-
- PNODE->recalcSizePosRecursive();
-}
-
-SWindowRenderLayoutHints CHyprDwindleLayout::requestRenderHints(PHLWINDOW pWindow) {
- // window should be valid, insallah
- SWindowRenderLayoutHints hints;
-
- const auto PNODE = getNodeFromWindow(pWindow);
- if (!PNODE)
- return hints; // left for the future, maybe floating funkiness
-
- return hints;
-}
-
-void CHyprDwindleLayout::moveWindowTo(PHLWINDOW pWindow, const std::string& dir, bool silent) {
- if (!isDirection(dir))
- return;
-
- const auto PNODE = getNodeFromWindow(pWindow);
- const auto originalWorkspaceID = pWindow->workspaceID();
- const Vector2D originalPos = pWindow->middle();
-
- if (!PNODE || !pWindow->m_monitor)
- return;
-
- Vector2D focalPoint;
-
- const auto WINDOWIDEALBB = pWindow->isFullscreen() ? CBox{pWindow->m_monitor->m_position, pWindow->m_monitor->m_size} : pWindow->getWindowIdealBoundingBoxIgnoreReserved();
-
- switch (dir[0]) {
- case 't':
- case 'u': focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, -1.0}; break;
- case 'd':
- case 'b': focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, WINDOWIDEALBB.size().y + 1.0}; break;
- case 'l': focalPoint = WINDOWIDEALBB.pos() + Vector2D{-1.0, WINDOWIDEALBB.size().y / 2.0}; break;
- case 'r': focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x + 1.0, WINDOWIDEALBB.size().y / 2.0}; break;
- default: UNREACHABLE();
- }
-
- pWindow->setAnimationsToMove();
-
- onWindowRemovedTiling(pWindow);
-
- m_overrideFocalPoint = focalPoint;
-
- const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(focalPoint);
-
- if (PMONITORFOCAL != pWindow->m_monitor) {
- pWindow->moveToWorkspace(PMONITORFOCAL->m_activeWorkspace);
- pWindow->m_monitor = PMONITORFOCAL;
- }
-
- pWindow->updateGroupOutputs();
- if (!pWindow->m_groupData.pNextWindow.expired()) {
- PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock();
- while (next != pWindow) {
- next->updateToplevel();
- next = next->m_groupData.pNextWindow.lock();
- }
- }
-
- onWindowCreatedTiling(pWindow);
-
- m_overrideFocalPoint.reset();
-
- // restore focus to the previous position
- if (silent) {
- const auto PNODETOFOCUS = getClosestNodeOnWorkspace(originalWorkspaceID, originalPos);
- if (PNODETOFOCUS && PNODETOFOCUS->pWindow.lock())
- Desktop::focusState()->fullWindowFocus(PNODETOFOCUS->pWindow.lock());
- }
-}
-
-void CHyprDwindleLayout::switchWindows(PHLWINDOW pWindow, PHLWINDOW pWindow2) {
- // windows should be valid, insallah
-
- auto PNODE = getNodeFromWindow(pWindow);
- auto PNODE2 = getNodeFromWindow(pWindow2);
-
- if (!PNODE2 || !PNODE)
- return;
-
- const eFullscreenMode MODE1 = pWindow->m_fullscreenState.internal;
- const eFullscreenMode MODE2 = pWindow2->m_fullscreenState.internal;
-
- g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE_NONE);
- g_pCompositor->setWindowFullscreenInternal(pWindow2, FSMODE_NONE);
-
- SDwindleNodeData* ACTIVE1 = nullptr;
- SDwindleNodeData* ACTIVE2 = nullptr;
-
- // swap the windows and recalc
- PNODE2->pWindow = pWindow;
- PNODE->pWindow = pWindow2;
-
- if (PNODE->workspaceID != PNODE2->workspaceID) {
- std::swap(pWindow2->m_monitor, pWindow->m_monitor);
- std::swap(pWindow2->m_workspace, pWindow->m_workspace);
- }
-
- pWindow->setAnimationsToMove();
- pWindow2->setAnimationsToMove();
-
- // recalc the workspace
- getMasterNodeOnWorkspace(PNODE->workspaceID)->recalcSizePosRecursive();
-
- if (PNODE2->workspaceID != PNODE->workspaceID)
- getMasterNodeOnWorkspace(PNODE2->workspaceID)->recalcSizePosRecursive();
-
- if (ACTIVE1) {
- ACTIVE1->box = PNODE->box;
- ACTIVE1->pWindow->m_position = ACTIVE1->box.pos();
- ACTIVE1->pWindow->m_size = ACTIVE1->box.size();
- }
-
- if (ACTIVE2) {
- ACTIVE2->box = PNODE2->box;
- ACTIVE2->pWindow->m_position = ACTIVE2->box.pos();
- ACTIVE2->pWindow->m_size = ACTIVE2->box.size();
- }
-
- g_pHyprRenderer->damageWindow(pWindow);
- g_pHyprRenderer->damageWindow(pWindow2);
-
- g_pCompositor->setWindowFullscreenInternal(pWindow2, MODE1);
- g_pCompositor->setWindowFullscreenInternal(pWindow, MODE2);
-}
-
-void CHyprDwindleLayout::alterSplitRatio(PHLWINDOW pWindow, float ratio, bool exact) {
- // window should be valid, insallah
-
- const auto PNODE = getNodeFromWindow(pWindow);
-
- if (!PNODE || !PNODE->pParent)
- return;
-
- float newRatio = exact ? ratio : PNODE->pParent->splitRatio + ratio;
- PNODE->pParent->splitRatio = std::clamp(newRatio, 0.1f, 1.9f);
-
- PNODE->pParent->recalcSizePosRecursive();
-}
-
-std::any CHyprDwindleLayout::layoutMessage(SLayoutMessageHeader header, std::string message) {
- const auto ARGS = CVarList(message, 0, ' ');
- if (ARGS[0] == "togglesplit") {
- toggleSplit(header.pWindow);
- } else if (ARGS[0] == "swapsplit") {
- swapSplit(header.pWindow);
- } else if (ARGS[0] == "movetoroot") {
- const auto WINDOW = ARGS[1].empty() ? header.pWindow : g_pCompositor->getWindowByRegex(ARGS[1]);
- const auto STABLE = ARGS[2].empty() || ARGS[2] != "unstable";
- moveToRoot(WINDOW, STABLE);
- } else if (ARGS[0] == "preselect") {
- std::string direction = ARGS[1];
-
- if (direction.empty()) {
- Log::logger->log(Log::ERR, "Expected direction for preselect");
- return "";
- }
-
- switch (direction.front()) {
- case 'u':
- case 't': {
- m_overrideDirection = DIRECTION_UP;
- break;
- }
- case 'd':
- case 'b': {
- m_overrideDirection = DIRECTION_DOWN;
- break;
- }
- case 'r': {
- m_overrideDirection = DIRECTION_RIGHT;
- break;
- }
- case 'l': {
- m_overrideDirection = DIRECTION_LEFT;
- break;
- }
- default: {
- // any other character resets the focus direction
- // needed for the persistent mode
- m_overrideDirection = DIRECTION_DEFAULT;
- break;
- }
- }
- }
-
- return "";
-}
-
-void CHyprDwindleLayout::toggleSplit(PHLWINDOW pWindow) {
- const auto PNODE = getNodeFromWindow(pWindow);
-
- if (!PNODE || !PNODE->pParent)
- return;
-
- if (pWindow->isFullscreen())
- return;
-
- PNODE->pParent->splitTop = !PNODE->pParent->splitTop;
-
- PNODE->pParent->recalcSizePosRecursive();
-}
-
-void CHyprDwindleLayout::swapSplit(PHLWINDOW pWindow) {
- const auto PNODE = getNodeFromWindow(pWindow);
-
- if (!PNODE || !PNODE->pParent)
- return;
-
- if (pWindow->isFullscreen())
- return;
-
- std::swap(PNODE->pParent->children[0], PNODE->pParent->children[1]);
-
- PNODE->pParent->recalcSizePosRecursive();
-}
-
-// goal: maximize the chosen window within current dwindle layout
-// impl: swap the selected window with the other sub-tree below root
-void CHyprDwindleLayout::moveToRoot(PHLWINDOW pWindow, bool stable) {
- const auto PNODE = getNodeFromWindow(pWindow);
-
- if (!PNODE || !PNODE->pParent)
- return;
-
- if (pWindow->isFullscreen())
- return;
-
- // already at root
- if (!PNODE->pParent->pParent)
- return;
-
- auto& pNode = PNODE->pParent->children[0] == PNODE ? PNODE->pParent->children[0] : PNODE->pParent->children[1];
-
- // instead of [getMasterNodeOnWorkspace], we walk back to root since we need
- // to know which children of root is our ancestor
- auto pAncestor = PNODE, pRoot = PNODE->pParent.lock();
- while (pRoot->pParent) {
- pAncestor = pRoot;
- pRoot = pRoot->pParent.lock();
- }
-
- auto& pSwap = pRoot->children[0] == pAncestor ? pRoot->children[1] : pRoot->children[0];
- std::swap(pNode, pSwap);
- std::swap(pNode->pParent, pSwap->pParent);
-
- // [stable] in that the focused window occupies same side of screen
- if (stable)
- std::swap(pRoot->children[0], pRoot->children[1]);
-
- // if the workspace is visible, recalculate layout
- if (pWindow->m_workspace && pWindow->m_workspace->isVisible())
- pRoot->recalcSizePosRecursive();
-}
-
-void CHyprDwindleLayout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) {
- const auto PNODE = getNodeFromWindow(from);
-
- if (!PNODE)
- return;
-
- PNODE->pWindow = to;
-
- applyNodeDataToWindow(PNODE, true);
-}
-
-std::string CHyprDwindleLayout::getLayoutName() {
- return "dwindle";
-}
-
-void CHyprDwindleLayout::onEnable() {
- for (auto const& w : g_pCompositor->m_windows) {
- if (w->m_isFloating || !w->m_isMapped || w->isHidden())
- continue;
-
- onWindowCreatedTiling(w);
- }
-}
-
-void CHyprDwindleLayout::onDisable() {
- m_dwindleNodesData.clear();
-}
-
-Vector2D CHyprDwindleLayout::predictSizeForNewWindowTiled() {
- if (!Desktop::focusState()->monitor())
- return {};
-
- // get window candidate
- PHLWINDOW candidate = Desktop::focusState()->window();
-
- if (!candidate)
- candidate = Desktop::focusState()->monitor()->m_activeWorkspace->getFirstWindow();
-
- // create a fake node
- SDwindleNodeData node;
-
- if (!candidate)
- return Desktop::focusState()->monitor()->m_size;
- else {
- const auto PNODE = getNodeFromWindow(candidate);
-
- if (!PNODE)
- return {};
-
- node = *PNODE;
- node.pWindow.reset();
-
- CBox box = PNODE->box;
-
- static auto PFLMULT = CConfigValue<Hyprlang::FLOAT>("dwindle:split_width_multiplier");
-
- bool splitTop = box.h * *PFLMULT > box.w;
-
- const auto SPLITSIDE = !splitTop;
-
- if (SPLITSIDE)
- node.box = {{}, {box.w / 2.0, box.h}};
- else
- node.box = {{}, {box.w, box.h / 2.0}};
-
- // TODO: make this better and more accurate
-
- return node.box.size();
- }
-
- return {};
-}
diff --git a/src/layout/DwindleLayout.hpp b/src/layout/DwindleLayout.hpp
deleted file mode 100644
index e704b8f4..00000000
--- a/src/layout/DwindleLayout.hpp
+++ /dev/null
@@ -1,110 +0,0 @@
-#pragma once
-
-#include "IHyprLayout.hpp"
-#include "../desktop/DesktopTypes.hpp"
-
-#include <list>
-#include <vector>
-#include <array>
-#include <optional>
-#include <format>
-
-class CHyprDwindleLayout;
-enum eFullscreenMode : int8_t;
-
-struct SDwindleNodeData {
- WP<SDwindleNodeData> pParent;
- bool isNode = false;
-
- PHLWINDOWREF pWindow;
-
- std::array<WP<SDwindleNodeData>, 2> children = {};
- WP<SDwindleNodeData> self;
-
- bool splitTop = false; // for preserve_split
-
- CBox box = {0};
-
- WORKSPACEID workspaceID = WORKSPACE_INVALID;
-
- float splitRatio = 1.f;
-
- bool valid = true;
-
- bool ignoreFullscreenChecks = false;
-
- // For list lookup
- bool operator==(const SDwindleNodeData& rhs) const {
- return pWindow.lock() == rhs.pWindow.lock() && workspaceID == rhs.workspaceID && box == rhs.box && pParent == rhs.pParent && children[0] == rhs.children[0] &&
- children[1] == rhs.children[1];
- }
-
- void recalcSizePosRecursive(bool force = false, bool horizontalOverride = false, bool verticalOverride = false);
- void applyRootBox();
- CHyprDwindleLayout* layout = nullptr;
-};
-
-class CHyprDwindleLayout : public IHyprLayout {
- public:
- virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT);
- virtual void onWindowRemovedTiling(PHLWINDOW);
- virtual bool isWindowTiled(PHLWINDOW);
- virtual void recalculateMonitor(const MONITORID&);
- virtual void recalculateWindow(PHLWINDOW);
- virtual void onBeginDragWindow();
- virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr);
- virtual void fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE);
- virtual std::any layoutMessage(SLayoutMessageHeader, std::string);
- virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW);
- virtual void switchWindows(PHLWINDOW, PHLWINDOW);
- virtual void moveWindowTo(PHLWINDOW, const std::string& dir, bool silent);
- virtual void alterSplitRatio(PHLWINDOW, float, bool);
- virtual std::string getLayoutName();
- virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to);
- virtual Vector2D predictSizeForNewWindowTiled();
-
- virtual void onEnable();
- virtual void onDisable();
-
- private:
- std::vector<SP<SDwindleNodeData>> m_dwindleNodesData;
-
- struct {
- bool started = false;
- bool pseudo = false;
- bool xExtent = false;
- bool yExtent = false;
- } m_pseudoDragFlags;
-
- std::optional<Vector2D> m_overrideFocalPoint; // for onWindowCreatedTiling.
-
- int getNodesOnWorkspace(const WORKSPACEID&);
- void applyNodeDataToWindow(SP<SDwindleNodeData>, bool force = false);
- void calculateWorkspace(const PHLWORKSPACE& pWorkspace);
- SP<SDwindleNodeData> getNodeFromWindow(PHLWINDOW);
- SP<SDwindleNodeData> getFirstNodeOnWorkspace(const WORKSPACEID&);
- SP<SDwindleNodeData> getClosestNodeOnWorkspace(const WORKSPACEID&, const Vector2D&);
- SP<SDwindleNodeData> getMasterNodeOnWorkspace(const WORKSPACEID&);
-
- void toggleSplit(PHLWINDOW);
- void swapSplit(PHLWINDOW);
- void moveToRoot(PHLWINDOW, bool stable = true);
-
- eDirection m_overrideDirection = DIRECTION_DEFAULT;
-
- friend struct SDwindleNodeData;
-};
-
-template <typename CharT>
-struct std::formatter<SP<SDwindleNodeData>, CharT> : std::formatter<CharT> {
- template <typename FormatContext>
- auto format(const SP<SDwindleNodeData>& node, FormatContext& ctx) const {
- auto out = ctx.out();
- if (!node)
- return std::format_to(out, "[Node nullptr]");
- std::format_to(out, "[Node {:x}: workspace: {}, pos: {:j2}, size: {:j2}", rc<uintptr_t>(node.get()), node->workspaceID, node->box.pos(), node->box.size());
- if (!node->isNode && !node->pWindow.expired())
- std::format_to(out, ", window: {:x}", node->pWindow.lock());
- return std::format_to(out, "]");
- }
-};
diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp
deleted file mode 100644
index f434b580..00000000
--- a/src/layout/IHyprLayout.cpp
+++ /dev/null
@@ -1,1062 +0,0 @@
-#include "IHyprLayout.hpp"
-#include "../defines.hpp"
-#include "../Compositor.hpp"
-#include "../render/decorations/CHyprGroupBarDecoration.hpp"
-#include "../config/ConfigValue.hpp"
-#include "../config/ConfigManager.hpp"
-#include "../desktop/view/Window.hpp"
-#include "../desktop/state/FocusState.hpp"
-#include "../protocols/XDGShell.hpp"
-#include "../protocols/core/Compositor.hpp"
-#include "../xwayland/XSurface.hpp"
-#include "../render/Renderer.hpp"
-#include "../managers/LayoutManager.hpp"
-#include "../managers/EventManager.hpp"
-#include "../managers/HookSystemManager.hpp"
-#include "../managers/cursor/CursorShapeOverrideController.hpp"
-#include "../desktop/rule/windowRule/WindowRule.hpp"
-
-void IHyprLayout::onWindowCreated(PHLWINDOW pWindow, eDirection direction) {
- CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(pWindow);
-
- const bool HASPERSISTENTSIZE = pWindow->m_ruleApplicator->persistentSize().valueOrDefault();
-
- const auto STOREDSIZE = HASPERSISTENTSIZE ? g_pConfigManager->getStoredFloatingSize(pWindow) : std::nullopt;
-
- if (STOREDSIZE.has_value()) {
- Log::logger->log(Log::DEBUG, "using stored size {}x{} for new window {}::{}", STOREDSIZE->x, STOREDSIZE->y, pWindow->m_class, pWindow->m_title);
- pWindow->m_lastFloatingSize = STOREDSIZE.value();
- } else if (desiredGeometry.width <= 5 || desiredGeometry.height <= 5) {
- const auto PMONITOR = pWindow->m_monitor.lock();
- pWindow->m_lastFloatingSize = PMONITOR->m_size / 2.f;
- } else
- pWindow->m_lastFloatingSize = Vector2D(desiredGeometry.width, desiredGeometry.height);
-
- pWindow->m_pseudoSize = pWindow->m_lastFloatingSize;
-
- bool autoGrouped = IHyprLayout::onWindowCreatedAutoGroup(pWindow);
- if (autoGrouped)
- return;
-
- if (pWindow->m_isFloating)
- onWindowCreatedFloating(pWindow);
- else
- onWindowCreatedTiling(pWindow, direction);
-
- if (!g_pXWaylandManager->shouldBeFloated(pWindow)) // do not apply group rules to child windows
- pWindow->applyGroupRules();
-}
-
-void IHyprLayout::onWindowRemoved(PHLWINDOW pWindow) {
- if (pWindow->isFullscreen())
- g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE_NONE);
-
- if (!pWindow->m_groupData.pNextWindow.expired()) {
- if (pWindow->m_groupData.pNextWindow.lock() == pWindow) {
- pWindow->m_groupData.pNextWindow.reset();
- pWindow->updateWindowDecos();
- } else {
- // find last window and update
- PHLWINDOW PWINDOWPREV = pWindow->getGroupPrevious();
- const auto WINDOWISVISIBLE = pWindow->getGroupCurrent() == pWindow;
-
- if (WINDOWISVISIBLE)
- PWINDOWPREV->setGroupCurrent(pWindow->m_groupData.head ? pWindow->m_groupData.pNextWindow.lock() : PWINDOWPREV);
-
- PWINDOWPREV->m_groupData.pNextWindow = pWindow->m_groupData.pNextWindow;
-
- pWindow->m_groupData.pNextWindow.reset();
-
- if (pWindow->m_groupData.head) {
- std::swap(PWINDOWPREV->m_groupData.pNextWindow->m_groupData.head, pWindow->m_groupData.head);
- std::swap(PWINDOWPREV->m_groupData.pNextWindow->m_groupData.locked, pWindow->m_groupData.locked);
- }
-
- if (pWindow == m_lastTiledWindow)
- m_lastTiledWindow.reset();
-
- pWindow->setHidden(false);
-
- pWindow->updateWindowDecos();
- PWINDOWPREV->getGroupCurrent()->updateWindowDecos();
- pWindow->updateDecorationValues();
-
- return;
- }
- }
-
- if (pWindow->m_isFloating) {
- onWindowRemovedFloating(pWindow);
- } else {
- onWindowRemovedTiling(pWindow);
- }
-
- if (pWindow == m_lastTiledWindow)
- m_lastTiledWindow.reset();
-}
-
-void IHyprLayout::onWindowRemovedFloating(PHLWINDOW pWindow) {
- ; // no-op
-}
-
-void IHyprLayout::onWindowCreatedFloating(PHLWINDOW pWindow) {
-
- CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(pWindow);
- const auto PMONITOR = pWindow->m_monitor.lock();
-
- if (pWindow->m_isX11) {
- Vector2D xy = {desiredGeometry.x, desiredGeometry.y};
- xy = g_pXWaylandManager->xwaylandToWaylandCoords(xy);
- desiredGeometry.x = xy.x;
- desiredGeometry.y = xy.y;
- } else if (pWindow->m_ruleApplicator->persistentSize().valueOrDefault()) {
- desiredGeometry.w = pWindow->m_lastFloatingSize.x;
- desiredGeometry.h = pWindow->m_lastFloatingSize.y;
- }
-
- static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling");
-
- if (!PMONITOR) {
- Log::logger->log(Log::ERR, "{:m} has an invalid monitor in onWindowCreatedFloating!!!", pWindow);
- return;
- }
-
- if (desiredGeometry.width <= 5 || desiredGeometry.height <= 5) {
- const auto PWINDOWSURFACE = pWindow->wlSurface()->resource();
- *pWindow->m_realSize = PWINDOWSURFACE->m_current.size;
-
- if ((desiredGeometry.width <= 1 || desiredGeometry.height <= 1) && pWindow->m_isX11 &&
- pWindow->isX11OverrideRedirect()) { // XDG windows should be fine. TODO: check for weird atoms?
- pWindow->setHidden(true);
- return;
- }
-
- // reject any windows with size <= 5x5
- if (pWindow->m_realSize->goal().x <= 5 || pWindow->m_realSize->goal().y <= 5)
- *pWindow->m_realSize = PMONITOR->m_size / 2.f;
-
- if (pWindow->m_isX11 && pWindow->isX11OverrideRedirect()) {
-
- if (pWindow->m_xwaylandSurface->m_geometry.x != 0 && pWindow->m_xwaylandSurface->m_geometry.y != 0)
- *pWindow->m_realPosition = g_pXWaylandManager->xwaylandToWaylandCoords(pWindow->m_xwaylandSurface->m_geometry.pos());
- else
- *pWindow->m_realPosition = Vector2D(PMONITOR->m_position.x + (PMONITOR->m_size.x - pWindow->m_realSize->goal().x) / 2.f,
- PMONITOR->m_position.y + (PMONITOR->m_size.y - pWindow->m_realSize->goal().y) / 2.f);
- } else {
- *pWindow->m_realPosition = Vector2D(PMONITOR->m_position.x + (PMONITOR->m_size.x - pWindow->m_realSize->goal().x) / 2.f,
- PMONITOR->m_position.y + (PMONITOR->m_size.y - pWindow->m_realSize->goal().y) / 2.f);
- }
- } else {
- // we respect the size.
- *pWindow->m_realSize = Vector2D(desiredGeometry.width, desiredGeometry.height);
-
- // check if it's on the correct monitor!
- Vector2D middlePoint = Vector2D(desiredGeometry.x, desiredGeometry.y) + Vector2D(desiredGeometry.width, desiredGeometry.height) / 2.f;
-
- // check if it's visible on any monitor (only for XDG)
- bool visible = pWindow->m_isX11;
-
- if (!visible) {
- visible = g_pCompositor->isPointOnAnyMonitor(Vector2D(desiredGeometry.x, desiredGeometry.y)) &&
- g_pCompositor->isPointOnAnyMonitor(Vector2D(desiredGeometry.x + desiredGeometry.width, desiredGeometry.y)) &&
- g_pCompositor->isPointOnAnyMonitor(Vector2D(desiredGeometry.x, desiredGeometry.y + desiredGeometry.height)) &&
- g_pCompositor->isPointOnAnyMonitor(Vector2D(desiredGeometry.x + desiredGeometry.width, desiredGeometry.y + desiredGeometry.height));
- }
-
- // TODO: detect a popup in a more consistent way.
- bool centeredOnParent = false;
- if ((desiredGeometry.x == 0 && desiredGeometry.y == 0) || !visible || !pWindow->m_isX11) {
- // if the pos isn't set, fall back to the center placement if it's not a child
- auto pos = PMONITOR->m_position + PMONITOR->m_size / 2.F - desiredGeometry.size() / 2.F;
-
- // otherwise middle of parent if available
- if (!pWindow->m_isX11) {
- if (const auto PARENT = pWindow->parent(); PARENT) {
- *pWindow->m_realPosition = PARENT->m_realPosition->goal() + PARENT->m_realSize->goal() / 2.F - desiredGeometry.size() / 2.F;
- pWindow->m_workspace = PARENT->m_workspace;
- pWindow->m_monitor = PARENT->m_monitor;
- centeredOnParent = true;
- }
- }
- if (!centeredOnParent)
- *pWindow->m_realPosition = pos;
- } else {
- // if it is, we respect where it wants to put itself, but apply monitor offset if outside
- // most of these are popups
-
- if (const auto POPENMON = g_pCompositor->getMonitorFromVector(middlePoint); POPENMON->m_id != PMONITOR->m_id)
- *pWindow->m_realPosition = Vector2D(desiredGeometry.x, desiredGeometry.y) - POPENMON->m_position + PMONITOR->m_position;
- else
- *pWindow->m_realPosition = Vector2D(desiredGeometry.x, desiredGeometry.y);
- }
- }
-
- if (*PXWLFORCESCALEZERO && pWindow->m_isX11)
- *pWindow->m_realSize = pWindow->m_realSize->goal() / PMONITOR->m_scale;
-
- if (pWindow->m_X11DoesntWantBorders || (pWindow->m_isX11 && pWindow->isX11OverrideRedirect())) {
- pWindow->m_realPosition->warp();
- pWindow->m_realSize->warp();
- }
-
- if (!pWindow->isX11OverrideRedirect())
- g_pCompositor->changeWindowZOrder(pWindow, true);
- else {
- pWindow->m_pendingReportedSize = pWindow->m_realSize->goal();
- pWindow->m_reportedSize = pWindow->m_pendingReportedSize;
- }
-}
-
-bool IHyprLayout::onWindowCreatedAutoGroup(PHLWINDOW pWindow) {
- static auto PAUTOGROUP = CConfigValue<Hyprlang::INT>("group:auto_group");
- const PHLWINDOW OPENINGON = Desktop::focusState()->window() && Desktop::focusState()->window()->m_workspace == pWindow->m_workspace ?
- Desktop::focusState()->window() :
- (pWindow->m_workspace ? pWindow->m_workspace->getFirstWindow() : nullptr);
- const bool FLOATEDINTOTILED = pWindow->m_isFloating && OPENINGON && !OPENINGON->m_isFloating;
- const bool SWALLOWING = pWindow->m_swallowed || pWindow->m_groupSwallowed;
-
- if ((*PAUTOGROUP || SWALLOWING) // continue if auto_group is enabled or if dealing with window swallowing.
- && OPENINGON // this shouldn't be 0, but honestly, better safe than sorry.
- && OPENINGON != pWindow // prevent freeze when the "group set" window rule makes the new window to be already a group.
- && OPENINGON->m_groupData.pNextWindow.lock() // check if OPENINGON is a group.
- && pWindow->canBeGroupedInto(OPENINGON) // check if the new window can be grouped into OPENINGON.
- && !g_pXWaylandManager->shouldBeFloated(pWindow) // don't group child windows. Fix for floated groups. Tiled groups don't need this because we check if !FLOATEDINTOTILED.
- && !FLOATEDINTOTILED) { // don't group a new floated window into a tiled group (for convenience).
-
- pWindow->m_isFloating = OPENINGON->m_isFloating; // match the floating state. Needed to autogroup a new tiled window into a floated group.
-
- static auto USECURRPOS = CConfigValue<Hyprlang::INT>("group:insert_after_current");
- (*USECURRPOS ? OPENINGON : OPENINGON->getGroupTail())->insertWindowToGroup(pWindow);
-
- OPENINGON->setGroupCurrent(pWindow);
- pWindow->applyGroupRules();
- pWindow->updateWindowDecos();
- recalculateWindow(pWindow);
-
- return true;
- }
-
- return false;
-}
-
-void IHyprLayout::onBeginDragWindow() {
- const auto DRAGGINGWINDOW = g_pInputManager->m_currentlyDraggedWindow.lock();
- static auto PDRAGTHRESHOLD = CConfigValue<Hyprlang::INT>("binds:drag_threshold");
-
- m_mouseMoveEventCount = 1;
- m_beginDragSizeXY = Vector2D();
-
- // Window will be floating. Let's check if it's valid. It should be, but I don't like crashing.
- if (!validMapped(DRAGGINGWINDOW)) {
- Log::logger->log(Log::ERR, "Dragging attempted on an invalid window!");
- CKeybindManager::changeMouseBindMode(MBIND_INVALID);
- return;
- }
-
- // Try to pick up dragged window now if drag_threshold is disabled
- // or at least update dragging related variables for the cursors
- g_pInputManager->m_dragThresholdReached = *PDRAGTHRESHOLD <= 0;
- if (updateDragWindow())
- return;
-
- // get the grab corner
- static auto RESIZECORNER = CConfigValue<Hyprlang::INT>("general:resize_corner");
- if (*RESIZECORNER != 0 && *RESIZECORNER <= 4 && DRAGGINGWINDOW->m_isFloating) {
- switch (*RESIZECORNER) {
- case 1:
- m_grabbedCorner = CORNER_TOPLEFT;
- Cursor::overrideController->setOverride("nw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
- break;
- case 2:
- m_grabbedCorner = CORNER_TOPRIGHT;
- Cursor::overrideController->setOverride("ne-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
- break;
- case 3:
- m_grabbedCorner = CORNER_BOTTOMRIGHT;
- Cursor::overrideController->setOverride("se-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
- break;
- case 4:
- m_grabbedCorner = CORNER_BOTTOMLEFT;
- Cursor::overrideController->setOverride("sw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
- break;
- }
- } else if (m_beginDragXY.x < m_beginDragPositionXY.x + m_beginDragSizeXY.x / 2.0) {
- if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.0) {
- m_grabbedCorner = CORNER_TOPLEFT;
- Cursor::overrideController->setOverride("nw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
- } else {
- m_grabbedCorner = CORNER_BOTTOMLEFT;
- Cursor::overrideController->setOverride("sw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
- }
- } else {
- if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.0) {
- m_grabbedCorner = CORNER_TOPRIGHT;
- Cursor::overrideController->setOverride("ne-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
- } else {
- m_grabbedCorner = CORNER_BOTTOMRIGHT;
- Cursor::overrideController->setOverride("se-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
- }
- }
-
- if (g_pInputManager->m_dragMode != MBIND_RESIZE && g_pInputManager->m_dragMode != MBIND_RESIZE_FORCE_RATIO && g_pInputManager->m_dragMode != MBIND_RESIZE_BLOCK_RATIO)
- Cursor::overrideController->setOverride("grabbing", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
-
- g_pHyprRenderer->damageWindow(DRAGGINGWINDOW);
-
- g_pKeybindManager->shadowKeybinds();
-
- Desktop::focusState()->rawWindowFocus(DRAGGINGWINDOW);
- g_pCompositor->changeWindowZOrder(DRAGGINGWINDOW, true);
-}
-
-void IHyprLayout::onEndDragWindow() {
- const auto DRAGGINGWINDOW = g_pInputManager->m_currentlyDraggedWindow.lock();
-
- m_mouseMoveEventCount = 1;
-
- if (!validMapped(DRAGGINGWINDOW)) {
- if (DRAGGINGWINDOW) {
- Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
- g_pInputManager->m_currentlyDraggedWindow.reset();
- }
- return;
- }
-
- Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
- g_pInputManager->m_currentlyDraggedWindow.reset();
- g_pInputManager->m_wasDraggingWindow = true;
-
- if (g_pInputManager->m_dragMode == MBIND_MOVE) {
- g_pHyprRenderer->damageWindow(DRAGGINGWINDOW);
- const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();
- PHLWINDOW pWindow =
- g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGINGWINDOW);
-
- if (pWindow) {
- if (pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_END, MOUSECOORDS, DRAGGINGWINDOW))
- return;
-
- const bool FLOATEDINTOTILED = !pWindow->m_isFloating && !DRAGGINGWINDOW->m_draggingTiled;
- static auto PDRAGINTOGROUP = CConfigValue<Hyprlang::INT>("group:drag_into_group");
-
- if (pWindow->m_groupData.pNextWindow.lock() && DRAGGINGWINDOW->canBeGroupedInto(pWindow) && *PDRAGINTOGROUP == 1 && !FLOATEDINTOTILED) {
-
- if (DRAGGINGWINDOW->m_groupData.pNextWindow) {
- PHLWINDOW next = DRAGGINGWINDOW->m_groupData.pNextWindow.lock();
- while (next != DRAGGINGWINDOW) {
- next->m_isFloating = pWindow->m_isFloating; // match the floating state of group members
- *next->m_realSize = pWindow->m_realSize->goal(); // match the size of group members
- *next->m_realPosition = pWindow->m_realPosition->goal(); // match the position of group members
- next = next->m_groupData.pNextWindow.lock();
- }
- }
-
- DRAGGINGWINDOW->m_isFloating = pWindow->m_isFloating; // match the floating state of the window
- DRAGGINGWINDOW->m_lastFloatingSize = m_draggingWindowOriginalFloatSize;
- DRAGGINGWINDOW->m_draggingTiled = false;
-
- static auto USECURRPOS = CConfigValue<Hyprlang::INT>("group:insert_after_current");
- (*USECURRPOS ? pWindow : pWindow->getGroupTail())->insertWindowToGroup(DRAGGINGWINDOW);
- pWindow->setGroupCurrent(DRAGGINGWINDOW);
- DRAGGINGWINDOW->applyGroupRules();
- DRAGGINGWINDOW->updateWindowDecos();
- }
- }
- }
-
- if (DRAGGINGWINDOW->m_draggingTiled) {
- static auto PPRECISEMOUSE = CConfigValue<Hyprlang::INT>("dwindle:precise_mouse_move");
- DRAGGINGWINDOW->m_isFloating = false;
- g_pInputManager->refocus();
-
- if (*PPRECISEMOUSE) {
- eDirection direction = DIRECTION_DEFAULT;
-
- const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();
- const PHLWINDOW pReferenceWindow =
- g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGINGWINDOW);
-
- if (pReferenceWindow && pReferenceWindow != DRAGGINGWINDOW) {
- const Vector2D draggedCenter = DRAGGINGWINDOW->m_realPosition->goal() + DRAGGINGWINDOW->m_realSize->goal() / 2.f;
- const Vector2D referenceCenter = pReferenceWindow->m_realPosition->goal() + pReferenceWindow->m_realSize->goal() / 2.f;
- const float xDiff = draggedCenter.x - referenceCenter.x;
- const float yDiff = draggedCenter.y - referenceCenter.y;
-
- if (fabs(xDiff) > fabs(yDiff))
- direction = xDiff < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
- else
- direction = yDiff < 0 ? DIRECTION_UP : DIRECTION_DOWN;
- }
-
- onWindowRemovedTiling(DRAGGINGWINDOW);
- onWindowCreatedTiling(DRAGGINGWINDOW, direction);
- } else
- changeWindowFloatingMode(DRAGGINGWINDOW);
-
- DRAGGINGWINDOW->m_lastFloatingSize = m_draggingWindowOriginalFloatSize;
- }
-
- g_pHyprRenderer->damageWindow(DRAGGINGWINDOW);
- Desktop::focusState()->fullWindowFocus(DRAGGINGWINDOW);
-
- g_pInputManager->m_wasDraggingWindow = false;
-}
-
-static inline bool canSnap(const double SIDEA, const double SIDEB, const double GAP) {
- return std::abs(SIDEA - SIDEB) < GAP;
-}
-
-static void snapMove(double& start, double& end, const double P) {
- end = P + (end - start);
- start = P;
-}
-
-static void snapResize(double& start, double& end, const double P) {
- start = P;
-}
-
-using SnapFn = std::function<void(double&, double&, const double)>;
-
-void IHyprLayout::performSnap(Vector2D& sourcePos, Vector2D& sourceSize, PHLWINDOW DRAGGINGWINDOW, const eMouseBindMode MODE, const int CORNER, const Vector2D& BEGINSIZE) {
- static auto SNAPWINDOWGAP = CConfigValue<Hyprlang::INT>("general:snap:window_gap");
- static auto SNAPMONITORGAP = CConfigValue<Hyprlang::INT>("general:snap:monitor_gap");
- static auto SNAPBORDEROVERLAP = CConfigValue<Hyprlang::INT>("general:snap:border_overlap");
- static auto SNAPRESPECTGAPS = CConfigValue<Hyprlang::INT>("general:snap:respect_gaps");
-
- static auto PGAPSIN = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_in");
- static auto PGAPSOUT = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_out");
- const auto GAPSNONE = CCssGapData{0, 0, 0, 0};
-
- const SnapFn SNAP = (MODE == MBIND_MOVE) ? snapMove : snapResize;
- int snaps = 0;
-
- struct SRange {
- double start = 0;
- double end = 0;
- };
- const auto EXTENTS = DRAGGINGWINDOW->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS);
- SRange sourceX = {sourcePos.x - EXTENTS.topLeft.x, sourcePos.x + sourceSize.x + EXTENTS.bottomRight.x};
- SRange sourceY = {sourcePos.y - EXTENTS.topLeft.y, sourcePos.y + sourceSize.y + EXTENTS.bottomRight.y};
-
- if (*SNAPWINDOWGAP) {
- const double GAPSIZE = *SNAPWINDOWGAP;
- const auto WSID = DRAGGINGWINDOW->workspaceID();
- const bool HASFULLSCREEN = DRAGGINGWINDOW->m_workspace && DRAGGINGWINDOW->m_workspace->m_hasFullscreenWindow;
-
- const auto* GAPSIN = *SNAPRESPECTGAPS ? sc<CCssGapData*>(PGAPSIN.ptr()->getData()) : &GAPSNONE;
- const double GAPSX = GAPSIN->m_left + GAPSIN->m_right;
- const double GAPSY = GAPSIN->m_top + GAPSIN->m_bottom;
-
- for (auto& other : g_pCompositor->m_windows) {
- if ((HASFULLSCREEN && !other->m_createdOverFullscreen) || other == DRAGGINGWINDOW || other->workspaceID() != WSID || !other->m_isMapped || other->m_fadingOut ||
- other->isX11OverrideRedirect())
- continue;
-
- const CBox SURF = other->getWindowBoxUnified(Desktop::View::RESERVED_EXTENTS);
- const SRange SURFBX = {SURF.x - GAPSX, SURF.x + SURF.w + GAPSX};
- const SRange SURFBY = {SURF.y - GAPSY, SURF.y + SURF.h + GAPSY};
-
- // only snap windows if their ranges overlap in the opposite axis
- if (sourceY.start <= SURFBY.end && SURFBY.start <= sourceY.end) {
- if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && canSnap(sourceX.start, SURFBX.end, GAPSIZE)) {
- SNAP(sourceX.start, sourceX.end, SURFBX.end);
- snaps |= SNAP_LEFT;
- } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && canSnap(sourceX.end, SURFBX.start, GAPSIZE)) {
- SNAP(sourceX.end, sourceX.start, SURFBX.start);
- snaps |= SNAP_RIGHT;
- }
- }
- if (sourceX.start <= SURFBX.end && SURFBX.start <= sourceX.end) {
- if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && canSnap(sourceY.start, SURFBY.end, GAPSIZE)) {
- SNAP(sourceY.start, sourceY.end, SURFBY.end);
- snaps |= SNAP_UP;
- } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && canSnap(sourceY.end, SURFBY.start, GAPSIZE)) {
- SNAP(sourceY.end, sourceY.start, SURFBY.start);
- snaps |= SNAP_DOWN;
- }
- }
-
- // corner snapping
- if (sourceX.start == SURFBX.end || SURFBX.start == sourceX.end) {
- const SRange SURFY = {SURFBY.start + GAPSY, SURFBY.end - GAPSY};
- if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && !(snaps & SNAP_UP) && canSnap(sourceY.start, SURFY.start, GAPSIZE)) {
- SNAP(sourceY.start, sourceY.end, SURFY.start);
- snaps |= SNAP_UP;
- } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_DOWN) && canSnap(sourceY.end, SURFY.end, GAPSIZE)) {
- SNAP(sourceY.end, sourceY.start, SURFY.end);
- snaps |= SNAP_DOWN;
- }
- }
- if (sourceY.start == SURFBY.end || SURFBY.start == sourceY.end) {
- const SRange SURFX = {SURFBX.start + GAPSX, SURFBX.end - GAPSX};
- if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && !(snaps & SNAP_LEFT) && canSnap(sourceX.start, SURFX.start, GAPSIZE)) {
- SNAP(sourceX.start, sourceX.end, SURFX.start);
- snaps |= SNAP_LEFT;
- } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_RIGHT) && canSnap(sourceX.end, SURFX.end, GAPSIZE)) {
- SNAP(sourceX.end, sourceX.start, SURFX.end);
- snaps |= SNAP_RIGHT;
- }
- }
- }
- }
-
- if (*SNAPMONITORGAP) {
- const double GAPSIZE = *SNAPMONITORGAP;
- const auto EXTENTNONE = SBoxExtents{{0, 0}, {0, 0}};
- const auto* EXTENTDIFF = *SNAPBORDEROVERLAP ? &EXTENTS : &EXTENTNONE;
- const auto MON = DRAGGINGWINDOW->m_monitor.lock();
-
- const auto* GAPSOUT = *SNAPRESPECTGAPS ? sc<CCssGapData*>(PGAPSOUT.ptr()->getData()) : &GAPSNONE;
- const auto WORK_AREA = Desktop::CReservedArea{GAPSOUT->m_top, GAPSOUT->m_right, GAPSOUT->m_bottom, GAPSOUT->m_left}.apply(MON->logicalBoxMinusReserved());
-
- SRange monX = {WORK_AREA.x, WORK_AREA.x + WORK_AREA.w};
- SRange monY = {WORK_AREA.y, WORK_AREA.y + WORK_AREA.h};
-
- const bool HAS_LEFT = MON->m_reservedArea.left() > 0;
- const bool HAS_TOP = MON->m_reservedArea.top() > 0;
- const bool HAS_BOTTOM = MON->m_reservedArea.bottom() > 0;
- const bool HAS_RIGHT = MON->m_reservedArea.right() > 0;
-
- if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) &&
- ((HAS_LEFT && canSnap(sourceX.start, monX.start, GAPSIZE)) || canSnap(sourceX.start, (monX.start -= MON->m_reservedArea.left() + EXTENTDIFF->topLeft.x), GAPSIZE))) {
- SNAP(sourceX.start, sourceX.end, monX.start);
- snaps |= SNAP_LEFT;
- }
- if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) &&
- ((HAS_RIGHT && canSnap(sourceX.end, monX.end, GAPSIZE)) || canSnap(sourceX.end, (monX.end += MON->m_reservedArea.right() + EXTENTDIFF->bottomRight.x), GAPSIZE))) {
- SNAP(sourceX.end, sourceX.start, monX.end);
- snaps |= SNAP_RIGHT;
- }
- if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) &&
- ((HAS_TOP && canSnap(sourceY.start, monY.start, GAPSIZE)) || canSnap(sourceY.start, (monY.start -= MON->m_reservedArea.top() + EXTENTDIFF->topLeft.y), GAPSIZE))) {
- SNAP(sourceY.start, sourceY.end, monY.start);
- snaps |= SNAP_UP;
- }
- if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) &&
- ((HAS_BOTTOM && canSnap(sourceY.end, monY.end, GAPSIZE)) || canSnap(sourceY.end, (monY.end += MON->m_reservedArea.bottom() + EXTENTDIFF->bottomRight.y), GAPSIZE))) {
- SNAP(sourceY.end, sourceY.start, monY.end);
- snaps |= SNAP_DOWN;
- }
- }
-
- // remove extents from main surface
- sourceX = {sourceX.start + EXTENTS.topLeft.x, sourceX.end - EXTENTS.bottomRight.x};
- sourceY = {sourceY.start + EXTENTS.topLeft.y, sourceY.end - EXTENTS.bottomRight.y};
-
- if (MODE == MBIND_RESIZE_FORCE_RATIO) {
- if ((CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && snaps & SNAP_LEFT) || (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && snaps & SNAP_RIGHT)) {
- const double SIZEY = (sourceX.end - sourceX.start) * (BEGINSIZE.y / BEGINSIZE.x);
- if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT))
- sourceY.start = sourceY.end - SIZEY;
- else
- sourceY.end = sourceY.start + SIZEY;
- } else if ((CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && snaps & SNAP_UP) || (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && snaps & SNAP_DOWN)) {
- const double SIZEX = (sourceY.end - sourceY.start) * (BEGINSIZE.x / BEGINSIZE.y);
- if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT))
- sourceX.start = sourceX.end - SIZEX;
- else
- sourceX.end = sourceX.start + SIZEX;
- }
- }
-
- sourcePos = {sourceX.start, sourceY.start};
- sourceSize = {sourceX.end - sourceX.start, sourceY.end - sourceY.start};
-}
-
-void IHyprLayout::onMouseMove(const Vector2D& mousePos) {
- if (g_pInputManager->m_currentlyDraggedWindow.expired())
- return;
-
- const auto DRAGGINGWINDOW = g_pInputManager->m_currentlyDraggedWindow.lock();
- static auto PDRAGTHRESHOLD = CConfigValue<Hyprlang::INT>("binds:drag_threshold");
-
- // Window invalid or drag begin size 0,0 meaning we rejected it.
- if ((!validMapped(DRAGGINGWINDOW) || m_beginDragSizeXY == Vector2D())) {
- g_pKeybindManager->changeMouseBindMode(MBIND_INVALID);
- return;
- }
-
- // Yoink dragged window here instead if using drag_threshold and it has been reached
- if (*PDRAGTHRESHOLD > 0 && !g_pInputManager->m_dragThresholdReached) {
- if ((m_beginDragXY.distanceSq(mousePos) <= std::pow(*PDRAGTHRESHOLD, 2) && m_beginDragXY == m_lastDragXY))
- return;
- g_pInputManager->m_dragThresholdReached = true;
- if (updateDragWindow())
- return;
- }
-
- static auto TIMER = std::chrono::high_resolution_clock::now(), MSTIMER = TIMER;
-
- const auto SPECIAL = DRAGGINGWINDOW->onSpecialWorkspace();
-
- const auto DELTA = Vector2D(mousePos.x - m_beginDragXY.x, mousePos.y - m_beginDragXY.y);
- const auto TICKDELTA = Vector2D(mousePos.x - m_lastDragXY.x, mousePos.y - m_lastDragXY.y);
-
- static auto PANIMATEMOUSE = CConfigValue<Hyprlang::INT>("misc:animate_mouse_windowdragging");
- static auto PANIMATE = CConfigValue<Hyprlang::INT>("misc:animate_manual_resizes");
-
- static auto SNAPENABLED = CConfigValue<Hyprlang::INT>("general:snap:enabled");
-
- const auto TIMERDELTA = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - TIMER).count();
- const auto MSDELTA = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - MSTIMER).count();
- const auto MSMONITOR = 1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate;
- static int totalMs = 0;
- bool canSkipUpdate = true;
-
- MSTIMER = std::chrono::high_resolution_clock::now();
-
- if (m_mouseMoveEventCount == 1)
- totalMs = 0;
-
- if (MSMONITOR > 16.0) {
- totalMs += MSDELTA < MSMONITOR ? MSDELTA : std::round(totalMs * 1.0 / m_mouseMoveEventCount);
- m_mouseMoveEventCount += 1;
-
- // check if time-window is enough to skip update on 60hz monitor
- canSkipUpdate = std::clamp(MSMONITOR - TIMERDELTA, 0.0, MSMONITOR) > totalMs * 1.0 / m_mouseMoveEventCount;
- }
-
- if ((abs(TICKDELTA.x) < 1.f && abs(TICKDELTA.y) < 1.f) || (TIMERDELTA < MSMONITOR && canSkipUpdate && (g_pInputManager->m_dragMode != MBIND_MOVE || *PANIMATEMOUSE)))
- return;
-
- TIMER = std::chrono::high_resolution_clock::now();
-
- m_lastDragXY = mousePos;
-
- g_pHyprRenderer->damageWindow(DRAGGINGWINDOW);
-
- if (g_pInputManager->m_dragMode == MBIND_MOVE) {
-
- Vector2D newPos = m_beginDragPositionXY + DELTA;
- Vector2D newSize = DRAGGINGWINDOW->m_realSize->goal();
-
- if (*SNAPENABLED && !DRAGGINGWINDOW->m_draggingTiled)
- performSnap(newPos, newSize, DRAGGINGWINDOW, MBIND_MOVE, -1, m_beginDragSizeXY);
-
- newPos = newPos.round();
-
- if (*PANIMATEMOUSE)
- *DRAGGINGWINDOW->m_realPosition = newPos;
- else {
- DRAGGINGWINDOW->m_realPosition->setValueAndWarp(newPos);
- DRAGGINGWINDOW->sendWindowSize();
- }
-
- 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) {
-
- Vector2D MINSIZE = DRAGGINGWINDOW->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE});
- Vector2D MAXSIZE = DRAGGINGWINDOW->maxSize().value_or(Math::VECTOR2D_MAX);
-
- Vector2D newSize = m_beginDragSizeXY;
- Vector2D newPos = m_beginDragPositionXY;
-
- if (m_grabbedCorner == CORNER_BOTTOMRIGHT)
- newSize = newSize + DELTA;
- else if (m_grabbedCorner == CORNER_TOPLEFT)
- newSize = newSize - DELTA;
- else if (m_grabbedCorner == CORNER_TOPRIGHT)
- newSize = newSize + Vector2D(DELTA.x, -DELTA.y);
- else if (m_grabbedCorner == CORNER_BOTTOMLEFT)
- newSize = newSize + Vector2D(-DELTA.x, DELTA.y);
-
- eMouseBindMode mode = g_pInputManager->m_dragMode;
- if (DRAGGINGWINDOW->m_ruleApplicator->keepAspectRatio().valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO)
- mode = MBIND_RESIZE_FORCE_RATIO;
-
- if (m_beginDragSizeXY.x >= 1 && m_beginDragSizeXY.y >= 1 && mode == MBIND_RESIZE_FORCE_RATIO) {
-
- const float RATIO = m_beginDragSizeXY.y / m_beginDragSizeXY.x;
-
- if (MINSIZE.x * RATIO > MINSIZE.y)
- MINSIZE = Vector2D(MINSIZE.x, MINSIZE.x * RATIO);
- else
- MINSIZE = Vector2D(MINSIZE.y / RATIO, MINSIZE.y);
-
- if (MAXSIZE.x * RATIO < MAXSIZE.y)
- MAXSIZE = Vector2D(MAXSIZE.x, MAXSIZE.x * RATIO);
- else
- MAXSIZE = Vector2D(MAXSIZE.y / RATIO, MAXSIZE.y);
-
- if (newSize.x * RATIO > newSize.y)
- newSize = Vector2D(newSize.x, newSize.x * RATIO);
- else
- newSize = Vector2D(newSize.y / RATIO, newSize.y);
- }
-
- newSize = newSize.clamp(MINSIZE, MAXSIZE);
-
- if (m_grabbedCorner == CORNER_TOPLEFT)
- newPos = newPos - newSize + m_beginDragSizeXY;
- else if (m_grabbedCorner == CORNER_TOPRIGHT)
- newPos = newPos + Vector2D(0.0, (m_beginDragSizeXY - newSize).y);
- else if (m_grabbedCorner == CORNER_BOTTOMLEFT)
- newPos = newPos + Vector2D((m_beginDragSizeXY - newSize).x, 0.0);
-
- if (*SNAPENABLED) {
- performSnap(newPos, newSize, DRAGGINGWINDOW, mode, m_grabbedCorner, m_beginDragSizeXY);
- newSize = newSize.clamp(MINSIZE, MAXSIZE);
- }
-
- CBox wb = {newPos, newSize};
- wb.round();
-
- if (*PANIMATE) {
- *DRAGGINGWINDOW->m_realSize = wb.size();
- *DRAGGINGWINDOW->m_realPosition = wb.pos();
- } else {
- DRAGGINGWINDOW->m_realSize->setValueAndWarp(wb.size());
- DRAGGINGWINDOW->m_realPosition->setValueAndWarp(wb.pos());
- DRAGGINGWINDOW->sendWindowSize();
- }
-
- DRAGGINGWINDOW->m_position = wb.pos();
- DRAGGINGWINDOW->m_size = wb.size();
- } else {
- resizeActiveWindow(TICKDELTA, m_grabbedCorner, DRAGGINGWINDOW);
- }
- }
-
- // get middle point
- Vector2D middle = DRAGGINGWINDOW->m_realPosition->value() + DRAGGINGWINDOW->m_realSize->value() / 2.f;
-
- // and check its monitor
- const auto PMONITOR = g_pCompositor->getMonitorFromVector(middle);
-
- if (PMONITOR && !SPECIAL) {
- DRAGGINGWINDOW->m_monitor = PMONITOR;
- DRAGGINGWINDOW->moveToWorkspace(PMONITOR->m_activeWorkspace);
- DRAGGINGWINDOW->updateGroupOutputs();
-
- DRAGGINGWINDOW->updateToplevel();
- }
-
- DRAGGINGWINDOW->updateWindowDecos();
-
- g_pHyprRenderer->damageWindow(DRAGGINGWINDOW);
-}
-
-void IHyprLayout::changeWindowFloatingMode(PHLWINDOW pWindow) {
-
- if (pWindow->isFullscreen()) {
- Log::logger->log(Log::DEBUG, "changeWindowFloatingMode: fullscreen");
- g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE_NONE);
- }
-
- pWindow->m_pinned = false;
-
- g_pHyprRenderer->damageWindow(pWindow, true);
-
- const auto TILED = isWindowTiled(pWindow);
-
- // event
- g_pEventManager->postEvent(SHyprIPCEvent{"changefloatingmode", std::format("{:x},{}", rc<uintptr_t>(pWindow.get()), sc<int>(TILED))});
- EMIT_HOOK_EVENT("changeFloatingMode", pWindow);
-
- if (!TILED) {
- const auto PNEWMON = g_pCompositor->getMonitorFromVector(pWindow->m_realPosition->value() + pWindow->m_realSize->value() / 2.f);
- pWindow->m_monitor = PNEWMON;
- pWindow->moveToWorkspace(PNEWMON->m_activeSpecialWorkspace ? PNEWMON->m_activeSpecialWorkspace : PNEWMON->m_activeWorkspace);
- pWindow->updateGroupOutputs();
-
- const auto PWORKSPACE = PNEWMON->m_activeSpecialWorkspace ? PNEWMON->m_activeSpecialWorkspace : PNEWMON->m_activeWorkspace;
-
- if (PWORKSPACE->m_hasFullscreenWindow)
- g_pCompositor->setWindowFullscreenInternal(PWORKSPACE->getFullscreenWindow(), FSMODE_NONE);
-
- // save real pos cuz the func applies the default 5,5 mid
- const auto PSAVEDPOS = pWindow->m_realPosition->goal();
- const auto PSAVEDSIZE = pWindow->m_realSize->goal();
-
- // if the window is pseudo, update its size
- if (!pWindow->m_draggingTiled)
- pWindow->m_pseudoSize = pWindow->m_realSize->goal();
-
- pWindow->m_lastFloatingSize = PSAVEDSIZE;
-
- // move to narnia because we don't wanna find our own node. onWindowCreatedTiling should apply the coords back.
- pWindow->m_position = Vector2D(-999999, -999999);
-
- onWindowCreatedTiling(pWindow);
-
- pWindow->m_realPosition->setValue(PSAVEDPOS);
- pWindow->m_realSize->setValue(PSAVEDSIZE);
-
- // fix pseudo leaving artifacts
- g_pHyprRenderer->damageMonitor(pWindow->m_monitor.lock());
-
- if (pWindow == Desktop::focusState()->window())
- m_lastTiledWindow = pWindow;
- } else {
- onWindowRemovedTiling(pWindow);
-
- g_pCompositor->changeWindowZOrder(pWindow, true);
-
- CBox wb = {pWindow->m_realPosition->goal() + (pWindow->m_realSize->goal() - pWindow->m_lastFloatingSize) / 2.f, pWindow->m_lastFloatingSize};
- wb.round();
-
- if (!(pWindow->m_isFloating && pWindow->m_isPseudotiled) && DELTALESSTHAN(pWindow->m_realSize->goal().x, pWindow->m_lastFloatingSize.x, 10) &&
- DELTALESSTHAN(pWindow->m_realSize->goal().y, pWindow->m_lastFloatingSize.y, 10)) {
- wb = {wb.pos() + Vector2D{10, 10}, wb.size() - Vector2D{20, 20}};
- }
-
- pWindow->m_size = wb.size();
- pWindow->m_position = wb.pos();
-
- fitFloatingWindowOnMonitor(pWindow, wb);
-
- g_pHyprRenderer->damageMonitor(pWindow->m_monitor.lock());
-
- pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT);
- pWindow->updateWindowData();
-
- if (pWindow == m_lastTiledWindow)
- m_lastTiledWindow.reset();
- }
-
- pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE | Desktop::Rule::RULE_PROP_FLOATING);
- pWindow->updateDecorationValues();
- pWindow->updateToplevel();
- g_pHyprRenderer->damageWindow(pWindow);
-}
-
-void IHyprLayout::fitFloatingWindowOnMonitor(PHLWINDOW w, std::optional<CBox> tb) {
- if (!w->m_isFloating)
- return;
-
- const auto PMONITOR = w->m_monitor.lock();
-
- if (!PMONITOR)
- return;
-
- const auto EXTENTS = w->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS);
- CBox targetBoxMonLocal = tb.value_or(w->getWindowMainSurfaceBox()).translate(-PMONITOR->m_position).addExtents(EXTENTS);
- const auto MONITOR_LOCAL_BOX = PMONITOR->logicalBoxMinusReserved().translate(-PMONITOR->m_position);
-
- if (targetBoxMonLocal.w < MONITOR_LOCAL_BOX.w) {
- if (targetBoxMonLocal.x < MONITOR_LOCAL_BOX.x)
- targetBoxMonLocal.x = MONITOR_LOCAL_BOX.x;
- else if (targetBoxMonLocal.x + targetBoxMonLocal.w > MONITOR_LOCAL_BOX.w)
- targetBoxMonLocal.x = MONITOR_LOCAL_BOX.w - targetBoxMonLocal.w;
- }
-
- if (targetBoxMonLocal.h < MONITOR_LOCAL_BOX.h) {
- if (targetBoxMonLocal.y < MONITOR_LOCAL_BOX.y)
- targetBoxMonLocal.y = MONITOR_LOCAL_BOX.y;
- else if (targetBoxMonLocal.y + targetBoxMonLocal.h > MONITOR_LOCAL_BOX.h)
- targetBoxMonLocal.y = MONITOR_LOCAL_BOX.h - targetBoxMonLocal.h;
- }
-
- *w->m_realPosition = (targetBoxMonLocal.pos() + PMONITOR->m_position + EXTENTS.topLeft).round();
- *w->m_realSize = (targetBoxMonLocal.size() - EXTENTS.topLeft - EXTENTS.bottomRight).round();
-}
-
-void IHyprLayout::moveActiveWindow(const Vector2D& delta, PHLWINDOW pWindow) {
- const auto PWINDOW = pWindow ? pWindow : Desktop::focusState()->window();
-
- if (!validMapped(PWINDOW))
- return;
-
- if (!PWINDOW->m_isFloating) {
- Log::logger->log(Log::DEBUG, "Dwindle cannot move a tiled window in moveActiveWindow!");
- return;
- }
-
- PWINDOW->setAnimationsToMove();
-
- PWINDOW->m_position += delta;
- *PWINDOW->m_realPosition = PWINDOW->m_realPosition->goal() + delta;
-
- g_pHyprRenderer->damageWindow(PWINDOW);
-}
-
-void IHyprLayout::onWindowFocusChange(PHLWINDOW pNewFocus) {
- m_lastTiledWindow = pNewFocus && !pNewFocus->m_isFloating ? pNewFocus : m_lastTiledWindow;
-}
-
-PHLWINDOW IHyprLayout::getNextWindowCandidate(PHLWINDOW pWindow) {
- // although we don't expect nullptrs here, let's verify jic
- if (!pWindow)
- return nullptr;
-
- const auto PWORKSPACE = pWindow->m_workspace;
-
- // first of all, if this is a fullscreen workspace,
- if (PWORKSPACE->m_hasFullscreenWindow)
- return PWORKSPACE->getFullscreenWindow();
-
- if (pWindow->m_isFloating) {
-
- // find whether there is a floating window below this one
- for (auto const& w : g_pCompositor->m_windows) {
- if (w->m_isMapped && !w->isHidden() && w->m_isFloating && !w->isX11OverrideRedirect() && w->m_workspace == pWindow->m_workspace && !w->m_X11ShouldntFocus &&
- !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pWindow) {
- if (VECINRECT((pWindow->m_size / 2.f + pWindow->m_position), w->m_position.x, w->m_position.y, w->m_position.x + w->m_size.x, w->m_position.y + w->m_size.y)) {
- return w;
- }
- }
- }
-
- // let's try the last tiled window.
- if (m_lastTiledWindow.lock() && m_lastTiledWindow->m_workspace == pWindow->m_workspace)
- return m_lastTiledWindow.lock();
-
- // if we don't, let's try to find any window that is in the middle
- if (const auto PWINDOWCANDIDATE =
- g_pCompositor->vectorToWindowUnified(pWindow->middle(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);
- PWINDOWCANDIDATE && PWINDOWCANDIDATE != pWindow)
- return PWINDOWCANDIDATE;
-
- // if not, floating window
- for (auto const& w : g_pCompositor->m_windows) {
- if (w->m_isMapped && !w->isHidden() && w->m_isFloating && !w->isX11OverrideRedirect() && w->m_workspace == pWindow->m_workspace && !w->m_X11ShouldntFocus &&
- !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pWindow)
- return w;
- }
-
- // if there is no candidate, too bad
- return nullptr;
- }
-
- // if it was a tiled window, we first try to find the window that will replace it.
- auto pWindowCandidate = g_pCompositor->vectorToWindowUnified(pWindow->middle(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);
-
- if (!pWindowCandidate)
- pWindowCandidate = PWORKSPACE->getTopLeftWindow();
-
- if (!pWindowCandidate)
- pWindowCandidate = PWORKSPACE->getFirstWindow();
-
- if (!pWindowCandidate || pWindow == pWindowCandidate || !pWindowCandidate->m_isMapped || pWindowCandidate->isHidden() || pWindowCandidate->m_X11ShouldntFocus ||
- pWindowCandidate->isX11OverrideRedirect() || pWindowCandidate->m_monitor != Desktop::focusState()->monitor())
- return nullptr;
-
- return pWindowCandidate;
-}
-
-bool IHyprLayout::isWindowReachable(PHLWINDOW pWindow) {
- return pWindow && (!pWindow->isHidden() || pWindow->m_groupData.pNextWindow);
-}
-
-void IHyprLayout::bringWindowToTop(PHLWINDOW pWindow) {
- if (pWindow == nullptr)
- return;
-
- if (pWindow->isHidden() && pWindow->m_groupData.pNextWindow) {
- // grouped, change the current to this window
- pWindow->setGroupCurrent(pWindow);
- }
-}
-
-void IHyprLayout::requestFocusForWindow(PHLWINDOW pWindow) {
- bringWindowToTop(pWindow);
- Desktop::focusState()->fullWindowFocus(pWindow);
- g_pCompositor->warpCursorTo(pWindow->middle());
-}
-
-Vector2D IHyprLayout::predictSizeForNewWindowFloating(PHLWINDOW pWindow) { // get all rules, see if we have any size overrides.
- Vector2D sizeOverride = {};
- if (Desktop::focusState()->monitor()) {
-
- // If `persistentsize` is set, use the stored size if available.
- const bool HASPERSISTENTSIZE = pWindow->m_ruleApplicator->persistentSize().valueOrDefault();
-
- const auto STOREDSIZE = HASPERSISTENTSIZE ? g_pConfigManager->getStoredFloatingSize(pWindow) : std::nullopt;
-
- if (STOREDSIZE.has_value()) {
- Log::logger->log(Log::DEBUG, "using stored size {}x{} for new floating window {}::{}", STOREDSIZE->x, STOREDSIZE->y, pWindow->m_class, pWindow->m_title);
- return STOREDSIZE.value();
- }
-
- if (!pWindow->m_ruleApplicator->static_.size.empty()) {
- const auto SIZE = pWindow->calculateExpression(pWindow->m_ruleApplicator->static_.size);
- if (SIZE)
- return SIZE.value();
- }
- }
-
- return sizeOverride;
-}
-
-Vector2D IHyprLayout::predictSizeForNewWindow(PHLWINDOW pWindow) {
- bool shouldBeFloated = g_pXWaylandManager->shouldBeFloated(pWindow, true) || pWindow->m_ruleApplicator->static_.floating.value_or(false);
-
- Vector2D sizePredicted = {};
-
- if (!shouldBeFloated)
- sizePredicted = predictSizeForNewWindowTiled();
- else
- sizePredicted = predictSizeForNewWindowFloating(pWindow);
-
- Vector2D maxSize = pWindow->m_xdgSurface->m_toplevel->m_pending.maxSize;
-
- if ((maxSize.x > 0 && maxSize.x < sizePredicted.x) || (maxSize.y > 0 && maxSize.y < sizePredicted.y))
- sizePredicted = {};
-
- return sizePredicted;
-}
-
-bool IHyprLayout::updateDragWindow() {
- const auto DRAGGINGWINDOW = g_pInputManager->m_currentlyDraggedWindow.lock();
- const bool WAS_FULLSCREEN = DRAGGINGWINDOW->isFullscreen();
-
- if (g_pInputManager->m_dragThresholdReached) {
- if (WAS_FULLSCREEN) {
- Log::logger->log(Log::DEBUG, "Dragging a fullscreen window");
- g_pCompositor->setWindowFullscreenInternal(DRAGGINGWINDOW, FSMODE_NONE);
- }
-
- const auto PWORKSPACE = DRAGGINGWINDOW->m_workspace;
-
- if (PWORKSPACE->m_hasFullscreenWindow && (!DRAGGINGWINDOW->m_isFloating || (!DRAGGINGWINDOW->m_createdOverFullscreen && !DRAGGINGWINDOW->m_pinned))) {
- Log::logger->log(Log::DEBUG, "Rejecting drag on a fullscreen workspace. (window under fullscreen)");
- g_pKeybindManager->changeMouseBindMode(MBIND_INVALID);
- return true;
- }
- }
-
- DRAGGINGWINDOW->m_draggingTiled = false;
- m_draggingWindowOriginalFloatSize = DRAGGINGWINDOW->m_lastFloatingSize;
-
- if (WAS_FULLSCREEN && DRAGGINGWINDOW->m_isFloating) {
- const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();
- *DRAGGINGWINDOW->m_realPosition = MOUSECOORDS - DRAGGINGWINDOW->m_realSize->goal() / 2.f;
- } else if (!DRAGGINGWINDOW->m_isFloating && g_pInputManager->m_dragMode == MBIND_MOVE) {
- Vector2D MINSIZE = DRAGGINGWINDOW->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE});
- DRAGGINGWINDOW->m_lastFloatingSize = (DRAGGINGWINDOW->m_realSize->goal() * 0.8489).clamp(MINSIZE, Vector2D{}).floor();
- *DRAGGINGWINDOW->m_realPosition = g_pInputManager->getMouseCoordsInternal() - DRAGGINGWINDOW->m_realSize->goal() / 2.f;
- if (g_pInputManager->m_dragThresholdReached) {
- changeWindowFloatingMode(DRAGGINGWINDOW);
- DRAGGINGWINDOW->m_isFloating = true;
- DRAGGINGWINDOW->m_draggingTiled = true;
- }
- }
-
- m_beginDragXY = g_pInputManager->getMouseCoordsInternal();
- m_beginDragPositionXY = DRAGGINGWINDOW->m_realPosition->goal();
- m_beginDragSizeXY = DRAGGINGWINDOW->m_realSize->goal();
- m_lastDragXY = m_beginDragXY;
-
- return false;
-}
-
-CBox IHyprLayout::workAreaOnWorkspace(const PHLWORKSPACE& pWorkspace) {
- if (!pWorkspace || !pWorkspace->m_monitor)
- return {};
-
- const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(pWorkspace);
-
- auto workArea = pWorkspace->m_monitor->logicalBoxMinusReserved();
-
- static auto PGAPSOUTDATA = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_out");
- auto* const PGAPSOUT = sc<CCssGapData*>((PGAPSOUTDATA.ptr())->getData());
- auto gapsOut = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT);
-
- Desktop::CReservedArea reservedGaps{gapsOut.m_top, gapsOut.m_right, gapsOut.m_bottom, gapsOut.m_left};
-
- reservedGaps.applyip(workArea);
-
- return workArea;
-}
diff --git a/src/layout/IHyprLayout.hpp b/src/layout/IHyprLayout.hpp
deleted file mode 100644
index 0b23bc3b..00000000
--- a/src/layout/IHyprLayout.hpp
+++ /dev/null
@@ -1,248 +0,0 @@
-#pragma once
-
-#include "../defines.hpp"
-#include "../managers/input/InputManager.hpp"
-#include <any>
-
-class CGradientValueData;
-
-struct SWindowRenderLayoutHints {
- bool isBorderGradient = false;
- CGradientValueData* borderGradient = nullptr;
-};
-
-struct SLayoutMessageHeader {
- PHLWINDOW pWindow;
-};
-
-enum eFullscreenMode : int8_t;
-
-enum eRectCorner : uint8_t {
- CORNER_NONE = 0,
- CORNER_TOPLEFT = (1 << 0),
- CORNER_TOPRIGHT = (1 << 1),
- CORNER_BOTTOMRIGHT = (1 << 2),
- CORNER_BOTTOMLEFT = (1 << 3),
-};
-
-inline eRectCorner cornerFromBox(const CBox& box, const Vector2D& pos) {
- const auto CENTER = box.middle();
-
- if (pos.x < CENTER.x)
- return pos.y < CENTER.y ? CORNER_TOPLEFT : CORNER_BOTTOMLEFT;
- return pos.y < CENTER.y ? CORNER_TOPRIGHT : CORNER_BOTTOMRIGHT;
-}
-
-enum eSnapEdge : uint8_t {
- SNAP_INVALID = 0,
- SNAP_UP = (1 << 0),
- SNAP_DOWN = (1 << 1),
- SNAP_LEFT = (1 << 2),
- SNAP_RIGHT = (1 << 3),
-};
-
-enum eDirection : int8_t {
- DIRECTION_DEFAULT = -1,
- DIRECTION_UP = 0,
- DIRECTION_RIGHT,
- DIRECTION_DOWN,
- DIRECTION_LEFT
-};
-
-class IHyprLayout {
- public:
- virtual ~IHyprLayout() = default;
- virtual void onEnable() = 0;
- virtual void onDisable() = 0;
-
- /*
- Called when a window is created (mapped)
- The layout HAS TO set the goal pos and size (anim mgr will use it)
- If !animationinprogress, then the anim mgr will not apply an anim.
- */
- virtual void onWindowCreated(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT);
- virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT) = 0;
- virtual void onWindowCreatedFloating(PHLWINDOW);
- virtual bool onWindowCreatedAutoGroup(PHLWINDOW);
-
- /*
- Return tiled status
- */
- virtual bool isWindowTiled(PHLWINDOW) = 0;
-
- /*
- Called when a window is removed (unmapped)
- */
- virtual void onWindowRemoved(PHLWINDOW);
- virtual void onWindowRemovedTiling(PHLWINDOW) = 0;
- virtual void onWindowRemovedFloating(PHLWINDOW);
- /*
- Called when the monitor requires a layout recalculation
- this usually means reserved area changes
- */
- virtual void recalculateMonitor(const MONITORID&) = 0;
-
- /*
- Called when the compositor requests a window
- to be recalculated, e.g. when pseudo is toggled.
- */
- virtual void recalculateWindow(PHLWINDOW) = 0;
-
- /*
- Called when a window is requested to be floated
- */
- virtual void changeWindowFloatingMode(PHLWINDOW);
- /*
- Called when a window is clicked on, beginning a drag
- this might be a resize, move, whatever the layout defines it
- as.
- */
- virtual void onBeginDragWindow();
- /*
- Called when a user requests a resize of the current window by a vec
- Vector2D holds pixel values
- Optional pWindow for a specific window
- */
- virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr) = 0;
- /*
- Called when a user requests a move of the current window by a vec
- Vector2D holds pixel values
- Optional pWindow for a specific window
- */
- virtual void moveActiveWindow(const Vector2D&, PHLWINDOW pWindow = nullptr);
- /*
- Called when a window is ended being dragged
- (mouse up)
- */
- virtual void onEndDragWindow();
- /*
- Called whenever the mouse moves, should the layout want to
- do anything with it.
- Useful for dragging.
- */
- virtual void onMouseMove(const Vector2D&);
-
- /*
- Called when a window / the user requests to toggle the fullscreen state of a window
- The layout sets all the fullscreen flags.
- It can either accept or ignore.
- */
- virtual void fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE) = 0;
-
- /*
- Called when a dispatcher requests a custom message
- The layout is free to ignore.
- std::any is the reply. Can be empty.
- */
- virtual std::any layoutMessage(SLayoutMessageHeader, std::string) = 0;
-
- /*
- Required to be handled, but may return just SWindowRenderLayoutHints()
- Called when the renderer requests any special draw flags for
- a specific window, e.g. border color for groups.
- */
- virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW) = 0;
-
- /*
- Called when the user requests two windows to be swapped places.
- The layout is free to ignore.
- */
- virtual void switchWindows(PHLWINDOW, PHLWINDOW) = 0;
-
- /*
- Called when the user requests a window move in a direction.
- The layout is free to ignore.
- */
- virtual void moveWindowTo(PHLWINDOW, const std::string& direction, bool silent = false) = 0;
-
- /*
- Called when the user requests to change the splitratio by or to X
- on a window
- */
- virtual void alterSplitRatio(PHLWINDOW, float, bool exact = false) = 0;
-
- /*
- Called when something wants the current layout's name
- */
- virtual std::string getLayoutName() = 0;
-
- /*
- Called for getting the next candidate for a focus
- */
- virtual PHLWINDOW getNextWindowCandidate(PHLWINDOW);
-
- /*
- Internal: called when window focus changes
- */
- virtual void onWindowFocusChange(PHLWINDOW);
-
- /*
- Called for replacing any data a layout has for a new window
- */
- virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) = 0;
-
- /*
- Determines if a window can be focused. If hidden this usually means the window is part of a group.
- */
- virtual bool isWindowReachable(PHLWINDOW);
-
- /*
- Called before an attempt is made to focus a window.
- Brings the window to the top of any groups and ensures it is not hidden.
- If the window is unmapped following this call, the focus attempt will fail.
- */
- virtual void bringWindowToTop(PHLWINDOW);
-
- /*
- Called via the foreign toplevel activation protocol.
- Focuses a window, bringing it to the top of its group if applicable.
- May be ignored.
- */
- virtual void requestFocusForWindow(PHLWINDOW);
-
- /*
- Called to predict the size of a newly opened window to send it a configure.
- Return 0,0 if unpredictable
- */
- virtual Vector2D predictSizeForNewWindowTiled() = 0;
-
- /*
- Prefer not overriding, use predictSizeForNewWindowTiled.
- */
- virtual Vector2D predictSizeForNewWindow(PHLWINDOW pWindow);
- virtual Vector2D predictSizeForNewWindowFloating(PHLWINDOW pWindow);
-
- /*
- Called to try to pick up window for dragging.
- Updates drag related variables and floats window if threshold reached.
- Return true to reject
- */
- 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);
-
- /*
- Fits a floating window on its monitor
- */
- virtual void fitFloatingWindowOnMonitor(PHLWINDOW w, std::optional<CBox> targetBox = std::nullopt);
-
- /*
- Returns a logical box describing the work area on a workspace
- (monitor size - reserved - gapsOut)
- */
- virtual CBox workAreaOnWorkspace(const PHLWORKSPACE& pWorkspace);
-
- private:
- int m_mouseMoveEventCount;
- Vector2D m_beginDragXY;
- Vector2D m_lastDragXY;
- Vector2D m_beginDragPositionXY;
- Vector2D m_beginDragSizeXY;
- Vector2D m_draggingWindowOriginalFloatSize;
- eRectCorner m_grabbedCorner = CORNER_TOPLEFT;
-
- PHLWINDOWREF m_lastTiledWindow;
-};
diff --git a/src/layout/LayoutManager.cpp b/src/layout/LayoutManager.cpp
new file mode 100644
index 00000000..29caa0ea
--- /dev/null
+++ b/src/layout/LayoutManager.cpp
@@ -0,0 +1,344 @@
+#include "LayoutManager.hpp"
+
+#include "space/Space.hpp"
+#include "target/Target.hpp"
+
+#include "../config/ConfigManager.hpp"
+#include "../Compositor.hpp"
+#include "../managers/HookSystemManager.hpp"
+#include "../desktop/state/FocusState.hpp"
+#include "../desktop/view/Group.hpp"
+
+using namespace Layout;
+
+CLayoutManager::CLayoutManager() {
+ static auto P = g_pHookSystem->hookDynamic("monitorLayoutChanged", [](void* hk, SCallbackInfo& info, std::any param) {
+ for (const auto& ws : g_pCompositor->getWorkspaces()) {
+ ws->m_space->recheckWorkArea();
+ }
+ });
+}
+
+void CLayoutManager::newTarget(SP<ITarget> target, SP<CSpace> space) {
+ // on a new target: remember desired pos for float, if available
+ if (const auto DESIRED_GEOM = target->desiredGeometry(); DESIRED_GEOM)
+ target->rememberFloatingSize(DESIRED_GEOM->size);
+
+ target->assignToSpace(space);
+}
+
+void CLayoutManager::removeTarget(SP<ITarget> target) {
+ target->assignToSpace(nullptr);
+}
+
+void CLayoutManager::changeFloatingMode(SP<ITarget> target) {
+ if (!target->space())
+ return;
+
+ target->space()->toggleTargetFloating(target);
+}
+
+void CLayoutManager::beginDragTarget(SP<ITarget> target, eMouseBindMode mode) {
+ m_dragStateController->dragBegin(target, mode);
+}
+
+void CLayoutManager::moveMouse(const Vector2D& mousePos) {
+ m_dragStateController->mouseMove(mousePos);
+}
+
+void CLayoutManager::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
+ if (target->isPseudo()) {
+ auto fixedΔ = Δ;
+ if (corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT)
+ fixedΔ.x = -fixedΔ.x;
+ if (corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT)
+ fixedΔ.y = -fixedΔ.y;
+
+ auto newPseudoSize = target->pseudoSize() + fixedΔ;
+ const auto TARGET_TILE_SIZE = target->position().size();
+ newPseudoSize.x = std::clamp(newPseudoSize.x, MIN_WINDOW_SIZE, TARGET_TILE_SIZE.x);
+ newPseudoSize.y = std::clamp(newPseudoSize.y, MIN_WINDOW_SIZE, TARGET_TILE_SIZE.y);
+
+ target->setPseudoSize(newPseudoSize);
+
+ return;
+ }
+
+ target->space()->resizeTarget(Δ, target, corner);
+}
+
+std::expected<void, std::string> CLayoutManager::layoutMsg(const std::string_view& sv) {
+
+ const auto MONITOR = Desktop::focusState()->monitor();
+ // forward to the active workspace
+ if (!MONITOR)
+ return std::unexpected("No monitor, can't find ws to target");
+
+ auto ws = MONITOR->m_activeSpecialWorkspace ? MONITOR->m_activeSpecialWorkspace : MONITOR->m_activeWorkspace;
+
+ if (!ws)
+ return std::unexpected("No workspace, can't target");
+
+ return ws->m_space->layoutMsg(sv);
+}
+
+void CLayoutManager::moveTarget(const Vector2D& Δ, SP<ITarget> target) {
+ if (!target->floating())
+ return;
+
+ target->space()->moveTarget(Δ, target);
+}
+
+void CLayoutManager::endDragTarget() {
+ m_dragStateController->dragEnd();
+}
+
+void CLayoutManager::fullscreenRequestForTarget(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode) {
+ target->space()->setFullscreen(target, effectiveMode);
+}
+
+void CLayoutManager::switchTargets(SP<ITarget> a, SP<ITarget> b, bool preserveFocus) {
+
+ if (preserveFocus) {
+ a->swap(b);
+ return;
+ }
+
+ const auto IS_A_ACTIVE = Desktop::focusState()->window() == a->window();
+ const auto IS_B_ACTIVE = Desktop::focusState()->window() == b->window();
+
+ a->swap(b);
+
+ if (IS_A_ACTIVE && b->window())
+ Desktop::focusState()->fullWindowFocus(b->window(), Desktop::FOCUS_REASON_KEYBIND);
+
+ if (IS_B_ACTIVE && a->window())
+ Desktop::focusState()->fullWindowFocus(a->window(), Desktop::FOCUS_REASON_KEYBIND);
+}
+
+void CLayoutManager::moveInDirection(SP<ITarget> target, const std::string& direction, bool silent) {
+ Math::eDirection dir = Math::fromChar(direction.at(0));
+ if (dir == Math::DIRECTION_DEFAULT) {
+ Log::logger->log(Log::ERR, "invalid direction for moveInDirection: {}", direction);
+ return;
+ }
+
+ target->space()->moveTargetInDirection(target, dir, silent);
+}
+
+SP<ITarget> CLayoutManager::getNextCandidate(SP<CSpace> space, SP<ITarget> from) {
+ return space->getNextCandidate(from);
+}
+
+bool CLayoutManager::isReachable(SP<ITarget> target) {
+ return true;
+}
+
+void CLayoutManager::bringTargetToTop(SP<ITarget> target) {
+ if (!target)
+ return;
+
+ if (target->window()->m_group) {
+ // grouped, change the current to this window
+ target->window()->m_group->setCurrent(target->window());
+ }
+}
+
+std::optional<Vector2D> CLayoutManager::predictSizeForNewTiledTarget() {
+ const auto FOCUSED_MON = Desktop::focusState()->monitor();
+
+ if (!FOCUSED_MON || !FOCUSED_MON->m_activeWorkspace)
+ return std::nullopt;
+
+ if (FOCUSED_MON->m_activeSpecialWorkspace)
+ return FOCUSED_MON->m_activeSpecialWorkspace->m_space->predictSizeForNewTiledTarget();
+
+ return FOCUSED_MON->m_activeWorkspace->m_space->predictSizeForNewTiledTarget();
+}
+
+const UP<Supplementary::CDragStateController>& CLayoutManager::dragController() {
+ return m_dragStateController;
+}
+
+static inline bool canSnap(const double SIDEA, const double SIDEB, const double GAP) {
+ return std::abs(SIDEA - SIDEB) < GAP;
+}
+
+static void snapMove(double& start, double& end, const double P) {
+ end = P + (end - start);
+ start = P;
+}
+
+static void snapResize(double& start, double& end, const double P) {
+ start = P;
+}
+
+using SnapFn = std::function<void(double&, double&, const double)>;
+
+void CLayoutManager::performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP<ITarget> DRAGGINGTARGET, const eMouseBindMode MODE, const int CORNER, const Vector2D& BEGINSIZE) {
+
+ const auto DRAGGINGWINDOW = DRAGGINGTARGET->window();
+
+ if (!Desktop::View::validMapped(DRAGGINGWINDOW))
+ return;
+
+ static auto SNAPWINDOWGAP = CConfigValue<Hyprlang::INT>("general:snap:window_gap");
+ static auto SNAPMONITORGAP = CConfigValue<Hyprlang::INT>("general:snap:monitor_gap");
+ static auto SNAPBORDEROVERLAP = CConfigValue<Hyprlang::INT>("general:snap:border_overlap");
+ static auto SNAPRESPECTGAPS = CConfigValue<Hyprlang::INT>("general:snap:respect_gaps");
+
+ static auto PGAPSIN = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_in");
+ static auto PGAPSOUT = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_out");
+ const auto GAPSNONE = CCssGapData{0, 0, 0, 0};
+
+ const SnapFn SNAP = (MODE == MBIND_MOVE) ? snapMove : snapResize;
+ int snaps = 0;
+
+ struct SRange {
+ double start = 0;
+ double end = 0;
+ };
+ const auto EXTENTS = DRAGGINGWINDOW->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS);
+ SRange sourceX = {sourcePos.x - EXTENTS.topLeft.x, sourcePos.x + sourceSize.x + EXTENTS.bottomRight.x};
+ SRange sourceY = {sourcePos.y - EXTENTS.topLeft.y, sourcePos.y + sourceSize.y + EXTENTS.bottomRight.y};
+
+ if (*SNAPWINDOWGAP) {
+ const double GAPSIZE = *SNAPWINDOWGAP;
+ const auto WSID = DRAGGINGWINDOW->workspaceID();
+ const bool HASFULLSCREEN = DRAGGINGWINDOW->m_workspace && DRAGGINGWINDOW->m_workspace->m_hasFullscreenWindow;
+
+ const auto* GAPSIN = *SNAPRESPECTGAPS ? sc<CCssGapData*>(PGAPSIN.ptr()->getData()) : &GAPSNONE;
+ const double GAPSX = GAPSIN->m_left + GAPSIN->m_right;
+ const double GAPSY = GAPSIN->m_top + GAPSIN->m_bottom;
+
+ for (auto& other : g_pCompositor->m_windows) {
+ if ((HASFULLSCREEN && !other->m_createdOverFullscreen) || other == DRAGGINGWINDOW || other->workspaceID() != WSID || !other->m_isMapped || other->m_fadingOut ||
+ other->isX11OverrideRedirect())
+ continue;
+
+ const CBox SURF = other->getWindowBoxUnified(Desktop::View::RESERVED_EXTENTS);
+ const SRange SURFBX = {SURF.x - GAPSX, SURF.x + SURF.w + GAPSX};
+ const SRange SURFBY = {SURF.y - GAPSY, SURF.y + SURF.h + GAPSY};
+
+ // only snap windows if their ranges overlap in the opposite axis
+ if (sourceY.start <= SURFBY.end && SURFBY.start <= sourceY.end) {
+ if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && canSnap(sourceX.start, SURFBX.end, GAPSIZE)) {
+ SNAP(sourceX.start, sourceX.end, SURFBX.end);
+ snaps |= SNAP_LEFT;
+ } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && canSnap(sourceX.end, SURFBX.start, GAPSIZE)) {
+ SNAP(sourceX.end, sourceX.start, SURFBX.start);
+ snaps |= SNAP_RIGHT;
+ }
+ }
+ if (sourceX.start <= SURFBX.end && SURFBX.start <= sourceX.end) {
+ if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && canSnap(sourceY.start, SURFBY.end, GAPSIZE)) {
+ SNAP(sourceY.start, sourceY.end, SURFBY.end);
+ snaps |= SNAP_UP;
+ } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && canSnap(sourceY.end, SURFBY.start, GAPSIZE)) {
+ SNAP(sourceY.end, sourceY.start, SURFBY.start);
+ snaps |= SNAP_DOWN;
+ }
+ }
+
+ // corner snapping
+ if (sourceX.start == SURFBX.end || SURFBX.start == sourceX.end) {
+ const SRange SURFY = {SURFBY.start + GAPSY, SURFBY.end - GAPSY};
+ if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && !(snaps & SNAP_UP) && canSnap(sourceY.start, SURFY.start, GAPSIZE)) {
+ SNAP(sourceY.start, sourceY.end, SURFY.start);
+ snaps |= SNAP_UP;
+ } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_DOWN) && canSnap(sourceY.end, SURFY.end, GAPSIZE)) {
+ SNAP(sourceY.end, sourceY.start, SURFY.end);
+ snaps |= SNAP_DOWN;
+ }
+ }
+ if (sourceY.start == SURFBY.end || SURFBY.start == sourceY.end) {
+ const SRange SURFX = {SURFBX.start + GAPSX, SURFBX.end - GAPSX};
+ if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && !(snaps & SNAP_LEFT) && canSnap(sourceX.start, SURFX.start, GAPSIZE)) {
+ SNAP(sourceX.start, sourceX.end, SURFX.start);
+ snaps |= SNAP_LEFT;
+ } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_RIGHT) && canSnap(sourceX.end, SURFX.end, GAPSIZE)) {
+ SNAP(sourceX.end, sourceX.start, SURFX.end);
+ snaps |= SNAP_RIGHT;
+ }
+ }
+ }
+ }
+
+ if (*SNAPMONITORGAP) {
+ const double GAPSIZE = *SNAPMONITORGAP;
+ const auto EXTENTNONE = SBoxExtents{{0, 0}, {0, 0}};
+ const auto* EXTENTDIFF = *SNAPBORDEROVERLAP ? &EXTENTS : &EXTENTNONE;
+ const auto MON = DRAGGINGWINDOW->m_monitor.lock();
+
+ const auto* GAPSOUT = *SNAPRESPECTGAPS ? sc<CCssGapData*>(PGAPSOUT.ptr()->getData()) : &GAPSNONE;
+ const auto WORK_AREA = Desktop::CReservedArea{GAPSOUT->m_top, GAPSOUT->m_right, GAPSOUT->m_bottom, GAPSOUT->m_left}.apply(MON->logicalBoxMinusReserved());
+
+ SRange monX = {WORK_AREA.x, WORK_AREA.x + WORK_AREA.w};
+ SRange monY = {WORK_AREA.y, WORK_AREA.y + WORK_AREA.h};
+
+ const bool HAS_LEFT = MON->m_reservedArea.left() > 0;
+ const bool HAS_TOP = MON->m_reservedArea.top() > 0;
+ const bool HAS_BOTTOM = MON->m_reservedArea.bottom() > 0;
+ const bool HAS_RIGHT = MON->m_reservedArea.right() > 0;
+
+ if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) &&
+ ((HAS_LEFT && canSnap(sourceX.start, monX.start, GAPSIZE)) || canSnap(sourceX.start, (monX.start -= MON->m_reservedArea.left() + EXTENTDIFF->topLeft.x), GAPSIZE))) {
+ SNAP(sourceX.start, sourceX.end, monX.start);
+ snaps |= SNAP_LEFT;
+ }
+ if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) &&
+ ((HAS_RIGHT && canSnap(sourceX.end, monX.end, GAPSIZE)) || canSnap(sourceX.end, (monX.end += MON->m_reservedArea.right() + EXTENTDIFF->bottomRight.x), GAPSIZE))) {
+ SNAP(sourceX.end, sourceX.start, monX.end);
+ snaps |= SNAP_RIGHT;
+ }
+ if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) &&
+ ((HAS_TOP && canSnap(sourceY.start, monY.start, GAPSIZE)) || canSnap(sourceY.start, (monY.start -= MON->m_reservedArea.top() + EXTENTDIFF->topLeft.y), GAPSIZE))) {
+ SNAP(sourceY.start, sourceY.end, monY.start);
+ snaps |= SNAP_UP;
+ }
+ if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) &&
+ ((HAS_BOTTOM && canSnap(sourceY.end, monY.end, GAPSIZE)) || canSnap(sourceY.end, (monY.end += MON->m_reservedArea.bottom() + EXTENTDIFF->bottomRight.y), GAPSIZE))) {
+ SNAP(sourceY.end, sourceY.start, monY.end);
+ snaps |= SNAP_DOWN;
+ }
+ }
+
+ // remove extents from main surface
+ sourceX = {sourceX.start + EXTENTS.topLeft.x, sourceX.end - EXTENTS.bottomRight.x};
+ sourceY = {sourceY.start + EXTENTS.topLeft.y, sourceY.end - EXTENTS.bottomRight.y};
+
+ if (MODE == MBIND_RESIZE_FORCE_RATIO) {
+ if ((CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && snaps & SNAP_LEFT) || (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && snaps & SNAP_RIGHT)) {
+ const double SIZEY = (sourceX.end - sourceX.start) * (BEGINSIZE.y / BEGINSIZE.x);
+ if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT))
+ sourceY.start = sourceY.end - SIZEY;
+ else
+ sourceY.end = sourceY.start + SIZEY;
+ } else if ((CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && snaps & SNAP_UP) || (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && snaps & SNAP_DOWN)) {
+ const double SIZEX = (sourceY.end - sourceY.start) * (BEGINSIZE.x / BEGINSIZE.y);
+ if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT))
+ sourceX.start = sourceX.end - SIZEX;
+ else
+ sourceX.end = sourceX.start + SIZEX;
+ }
+ }
+
+ sourcePos = {sourceX.start, sourceY.start};
+ sourceSize = {sourceX.end - sourceX.start, sourceY.end - sourceY.start};
+}
+
+void CLayoutManager::recalculateMonitor(PHLMONITOR m) {
+ if (m->m_activeSpecialWorkspace)
+ m->m_activeSpecialWorkspace->m_space->recalculate();
+ if (m->m_activeWorkspace)
+ m->m_activeWorkspace->m_space->recalculate();
+}
+
+void CLayoutManager::invalidateMonitorGeometries(PHLMONITOR m) {
+ for (const auto& ws : g_pCompositor->getWorkspaces()) {
+ if (ws && ws->m_monitor == m) {
+ ws->m_space->recheckWorkArea();
+ ws->m_space->recalculate();
+ }
+ }
+}
diff --git a/src/layout/LayoutManager.hpp b/src/layout/LayoutManager.hpp
new file mode 100644
index 00000000..638c9f4c
--- /dev/null
+++ b/src/layout/LayoutManager.hpp
@@ -0,0 +1,86 @@
+#pragma once
+
+#include "../helpers/memory/Memory.hpp"
+#include "../helpers/math/Math.hpp"
+#include "../managers/input/InputManager.hpp"
+
+#include "supplementary/DragController.hpp"
+
+#include <optional>
+#include <expected>
+
+enum eFullscreenMode : int8_t;
+
+namespace Layout {
+ class ITarget;
+ class CSpace;
+
+ enum eRectCorner : uint8_t {
+ CORNER_NONE = 0,
+ CORNER_TOPLEFT = (1 << 0),
+ CORNER_TOPRIGHT = (1 << 1),
+ CORNER_BOTTOMRIGHT = (1 << 2),
+ CORNER_BOTTOMLEFT = (1 << 3),
+ };
+
+ inline eRectCorner cornerFromBox(const CBox& box, const Vector2D& pos) {
+ const auto CENTER = box.middle();
+
+ if (pos.x < CENTER.x)
+ return pos.y < CENTER.y ? CORNER_TOPLEFT : CORNER_BOTTOMLEFT;
+ return pos.y < CENTER.y ? CORNER_TOPRIGHT : CORNER_BOTTOMRIGHT;
+ }
+
+ enum eSnapEdge : uint8_t {
+ SNAP_INVALID = 0,
+ SNAP_UP = (1 << 0),
+ SNAP_DOWN = (1 << 1),
+ SNAP_LEFT = (1 << 2),
+ SNAP_RIGHT = (1 << 3),
+ };
+
+ class CLayoutManager {
+ public:
+ CLayoutManager();
+ ~CLayoutManager() = default;
+
+ void newTarget(SP<ITarget> target, SP<CSpace> space);
+ void removeTarget(SP<ITarget> target);
+
+ void changeFloatingMode(SP<ITarget> target);
+
+ void beginDragTarget(SP<ITarget> target, eMouseBindMode mode);
+ void moveMouse(const Vector2D& mousePos);
+ void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
+ void moveTarget(const Vector2D& Δ, SP<ITarget> target);
+ void endDragTarget();
+
+ std::expected<void, std::string> layoutMsg(const std::string_view& sv);
+
+ void fullscreenRequestForTarget(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode);
+
+ void switchTargets(SP<ITarget> a, SP<ITarget> b, bool preserveFocus = true);
+
+ void moveInDirection(SP<ITarget> target, const std::string& direction, bool silent = false);
+
+ SP<ITarget> getNextCandidate(SP<CSpace> space, SP<ITarget> from);
+
+ bool isReachable(SP<ITarget> target);
+
+ void bringTargetToTop(SP<ITarget> target);
+
+ std::optional<Vector2D> predictSizeForNewTiledTarget();
+
+ void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP<ITarget> target, eMouseBindMode mode, int corner, const Vector2D& beginSize);
+
+ void invalidateMonitorGeometries(PHLMONITOR);
+ void recalculateMonitor(PHLMONITOR);
+
+ const UP<Supplementary::CDragStateController>& dragController();
+
+ private:
+ UP<Supplementary::CDragStateController> m_dragStateController = makeUnique<Supplementary::CDragStateController>();
+ };
+}
+
+inline UP<Layout::CLayoutManager> g_layoutManager; \ No newline at end of file
diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp
deleted file mode 100644
index 8c6376ab..00000000
--- a/src/layout/MasterLayout.cpp
+++ /dev/null
@@ -1,1526 +0,0 @@
-#include "MasterLayout.hpp"
-#include "../Compositor.hpp"
-#include "../render/decorations/CHyprGroupBarDecoration.hpp"
-#include "config/ConfigDataValues.hpp"
-#include <ranges>
-#include "../config/ConfigValue.hpp"
-#include "../config/ConfigManager.hpp"
-#include "../render/Renderer.hpp"
-#include "../managers/input/InputManager.hpp"
-#include "../managers/LayoutManager.hpp"
-#include "../managers/EventManager.hpp"
-#include "../desktop/state/FocusState.hpp"
-#include "xwayland/XWayland.hpp"
-
-SMasterNodeData* CHyprMasterLayout::getNodeFromWindow(PHLWINDOW pWindow) {
- for (auto& nd : m_masterNodesData) {
- if (nd.pWindow.lock() == pWindow)
- return &nd;
- }
-
- return nullptr;
-}
-
-int CHyprMasterLayout::getNodesOnWorkspace(const WORKSPACEID& ws) {
- int no = 0;
- for (auto const& n : m_masterNodesData) {
- if (n.workspaceID == ws)
- no++;
- }
-
- return no;
-}
-
-int CHyprMasterLayout::getMastersOnWorkspace(const WORKSPACEID& ws) {
- int no = 0;
- for (auto const& n : m_masterNodesData) {
- if (n.workspaceID == ws && n.isMaster)
- no++;
- }
-
- return no;
-}
-
-SMasterWorkspaceData* CHyprMasterLayout::getMasterWorkspaceData(const WORKSPACEID& ws) {
- for (auto& n : m_masterWorkspacesData) {
- if (n.workspaceID == ws)
- return &n;
- }
-
- //create on the fly if it doesn't exist yet
- const auto PWORKSPACEDATA = &m_masterWorkspacesData.emplace_back();
- PWORKSPACEDATA->workspaceID = ws;
- static auto PORIENTATION = CConfigValue<std::string>("master:orientation");
-
- if (*PORIENTATION == "top")
- PWORKSPACEDATA->orientation = ORIENTATION_TOP;
- else if (*PORIENTATION == "right")
- PWORKSPACEDATA->orientation = ORIENTATION_RIGHT;
- else if (*PORIENTATION == "bottom")
- PWORKSPACEDATA->orientation = ORIENTATION_BOTTOM;
- else if (*PORIENTATION == "center")
- PWORKSPACEDATA->orientation = ORIENTATION_CENTER;
- else
- PWORKSPACEDATA->orientation = ORIENTATION_LEFT;
-
- return PWORKSPACEDATA;
-}
-
-std::string CHyprMasterLayout::getLayoutName() {
- return "Master";
-}
-
-SMasterNodeData* CHyprMasterLayout::getMasterNodeOnWorkspace(const WORKSPACEID& ws) {
- for (auto& n : m_masterNodesData) {
- if (n.isMaster && n.workspaceID == ws)
- return &n;
- }
-
- return nullptr;
-}
-
-void CHyprMasterLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection direction) {
- if (pWindow->m_isFloating)
- return;
-
- static auto PNEWONACTIVE = CConfigValue<std::string>("master:new_on_active");
- static auto PNEWONTOP = CConfigValue<Hyprlang::INT>("master:new_on_top");
- static auto PNEWSTATUS = CConfigValue<std::string>("master:new_status");
-
- const auto PMONITOR = pWindow->m_monitor.lock();
-
- const bool BNEWBEFOREACTIVE = *PNEWONACTIVE == "before";
- const bool BNEWISMASTER = *PNEWSTATUS == "master";
-
- const auto PNODE = [&]() {
- if (*PNEWONACTIVE != "none" && !BNEWISMASTER) {
- const auto pLastNode = getNodeFromWindow(Desktop::focusState()->window());
- if (pLastNode && !(pLastNode->isMaster && (getMastersOnWorkspace(pWindow->workspaceID()) == 1 || *PNEWSTATUS == "slave"))) {
- auto it = std::ranges::find(m_masterNodesData, *pLastNode);
- if (!BNEWBEFOREACTIVE)
- ++it;
- return &(*m_masterNodesData.emplace(it));
- }
- }
- return *PNEWONTOP ? &m_masterNodesData.emplace_front() : &m_masterNodesData.emplace_back();
- }();
-
- PNODE->workspaceID = pWindow->workspaceID();
- PNODE->pWindow = pWindow;
-
- const auto WINDOWSONWORKSPACE = getNodesOnWorkspace(PNODE->workspaceID);
- static auto PMFACT = CConfigValue<Hyprlang::FLOAT>("master:mfact");
- float lastSplitPercent = *PMFACT;
-
- auto OPENINGON = isWindowTiled(Desktop::focusState()->window()) && Desktop::focusState()->window()->m_workspace == pWindow->m_workspace ?
- getNodeFromWindow(Desktop::focusState()->window()) :
- getMasterNodeOnWorkspace(pWindow->workspaceID());
-
- const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();
- static auto PDROPATCURSOR = CConfigValue<Hyprlang::INT>("master:drop_at_cursor");
- eOrientation orientation = getDynamicOrientation(pWindow->m_workspace);
- const auto NODEIT = std::ranges::find(m_masterNodesData, *PNODE);
-
- bool forceDropAsMaster = false;
- // if dragging window to move, drop it at the cursor position instead of bottom/top of stack
- if (*PDROPATCURSOR && g_pInputManager->m_dragMode == MBIND_MOVE) {
- if (WINDOWSONWORKSPACE > 2) {
- for (auto it = m_masterNodesData.begin(); it != m_masterNodesData.end(); ++it) {
- if (it->workspaceID != pWindow->workspaceID())
- continue;
- const CBox box = it->pWindow->getWindowIdealBoundingBoxIgnoreReserved();
- if (box.containsPoint(MOUSECOORDS)) {
- switch (orientation) {
- case ORIENTATION_LEFT:
- case ORIENTATION_RIGHT:
- if (MOUSECOORDS.y > it->pWindow->middle().y)
- ++it;
- break;
- case ORIENTATION_TOP:
- case ORIENTATION_BOTTOM:
- if (MOUSECOORDS.x > it->pWindow->middle().x)
- ++it;
- break;
- case ORIENTATION_CENTER: break;
- default: UNREACHABLE();
- }
- m_masterNodesData.splice(it, m_masterNodesData, NODEIT);
- break;
- }
- }
- } else if (WINDOWSONWORKSPACE == 2) {
- // when dropping as the second tiled window in the workspace,
- // make it the master only if the cursor is on the master side of the screen
- for (auto const& nd : m_masterNodesData) {
- if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) {
- switch (orientation) {
- case ORIENTATION_LEFT:
- case ORIENTATION_CENTER:
- if (MOUSECOORDS.x < nd.pWindow->middle().x)
- forceDropAsMaster = true;
- break;
- case ORIENTATION_RIGHT:
- if (MOUSECOORDS.x > nd.pWindow->middle().x)
- forceDropAsMaster = true;
- break;
- case ORIENTATION_TOP:
- if (MOUSECOORDS.y < nd.pWindow->middle().y)
- forceDropAsMaster = true;
- break;
- case ORIENTATION_BOTTOM:
- if (MOUSECOORDS.y > nd.pWindow->middle().y)
- forceDropAsMaster = true;
- break;
- default: UNREACHABLE();
- }
- break;
- }
- }
- }
- }
-
- if ((BNEWISMASTER && g_pInputManager->m_dragMode != MBIND_MOVE) //
- || WINDOWSONWORKSPACE == 1 //
- || (WINDOWSONWORKSPACE > 2 && !pWindow->m_firstMap && OPENINGON && OPENINGON->isMaster) //
- || forceDropAsMaster //
- || (*PNEWSTATUS == "inherit" && OPENINGON && OPENINGON->isMaster && g_pInputManager->m_dragMode != MBIND_MOVE)) {
-
- if (BNEWBEFOREACTIVE) {
- for (auto& nd : m_masterNodesData | std::views::reverse) {
- if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) {
- nd.isMaster = false;
- lastSplitPercent = nd.percMaster;
- break;
- }
- }
- } else {
- for (auto& nd : m_masterNodesData) {
- if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) {
- nd.isMaster = false;
- lastSplitPercent = nd.percMaster;
- break;
- }
- }
- }
-
- PNODE->isMaster = true;
- PNODE->percMaster = lastSplitPercent;
-
- // first, check if it isn't too big.
- if (const auto MAXSIZE = pWindow->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PMONITOR->m_size.x * lastSplitPercent || MAXSIZE.y < PMONITOR->m_size.y) {
- // we can't continue. make it floating.
- pWindow->m_isFloating = true;
- m_masterNodesData.remove(*PNODE);
- g_pLayoutManager->getCurrentLayout()->onWindowCreatedFloating(pWindow);
- return;
- }
- } else {
- PNODE->isMaster = false;
- PNODE->percMaster = lastSplitPercent;
-
- // first, check if it isn't too big.
- if (const auto MAXSIZE = pWindow->maxSize().value_or(Math::VECTOR2D_MAX);
- MAXSIZE.x < PMONITOR->m_size.x * (1 - lastSplitPercent) || MAXSIZE.y < PMONITOR->m_size.y * (1.f / (WINDOWSONWORKSPACE - 1))) {
- // we can't continue. make it floating.
- pWindow->m_isFloating = true;
- m_masterNodesData.remove(*PNODE);
- g_pLayoutManager->getCurrentLayout()->onWindowCreatedFloating(pWindow);
- return;
- }
- }
-
- // recalc
- recalculateMonitor(pWindow->monitorID());
- pWindow->m_workspace->updateWindows();
-}
-
-void CHyprMasterLayout::onWindowRemovedTiling(PHLWINDOW pWindow) {
- const auto PNODE = getNodeFromWindow(pWindow);
-
- if (!PNODE)
- return;
-
- const auto WORKSPACEID = PNODE->workspaceID;
- const auto MASTERSLEFT = getMastersOnWorkspace(WORKSPACEID);
- static auto SMALLSPLIT = CConfigValue<Hyprlang::INT>("master:allow_small_split");
-
- pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT);
- pWindow->updateWindowData();
-
- if (pWindow->isFullscreen())
- g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE_NONE);
-
- if (PNODE->isMaster && (MASTERSLEFT <= 1 || *SMALLSPLIT == 1)) {
- // find a new master from top of the list
- for (auto& nd : m_masterNodesData) {
- if (!nd.isMaster && nd.workspaceID == WORKSPACEID) {
- nd.isMaster = true;
- nd.percMaster = PNODE->percMaster;
- break;
- }
- }
- }
-
- m_masterNodesData.remove(*PNODE);
-
- if (getMastersOnWorkspace(WORKSPACEID) == getNodesOnWorkspace(WORKSPACEID) && MASTERSLEFT > 1) {
- for (auto& nd : m_masterNodesData | std::views::reverse) {
- if (nd.workspaceID == WORKSPACEID) {
- nd.isMaster = false;
- break;
- }
- }
- }
- // BUGFIX: correct bug where closing one master in a stack of 2 would leave
- // the screen half bare, and make it difficult to select remaining window
- if (getNodesOnWorkspace(WORKSPACEID) == 1) {
- for (auto& nd : m_masterNodesData) {
- if (nd.workspaceID == WORKSPACEID && !nd.isMaster) {
- nd.isMaster = true;
- break;
- }
- }
- }
- recalculateMonitor(pWindow->monitorID());
- pWindow->m_workspace->updateWindows();
-}
-
-void CHyprMasterLayout::recalculateMonitor(const MONITORID& monid) {
- const auto PMONITOR = g_pCompositor->getMonitorFromID(monid);
-
- if (!PMONITOR || !PMONITOR->m_activeWorkspace)
- return;
-
- g_pHyprRenderer->damageMonitor(PMONITOR);
-
- if (PMONITOR->m_activeSpecialWorkspace)
- calculateWorkspace(PMONITOR->m_activeSpecialWorkspace);
-
- calculateWorkspace(PMONITOR->m_activeWorkspace);
-
-#ifndef NO_XWAYLAND
- CBox box = g_pCompositor->calculateX11WorkArea();
- if (!g_pXWayland || !g_pXWayland->m_wm)
- return;
- g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h);
-#endif
-}
-
-void CHyprMasterLayout::calculateWorkspace(PHLWORKSPACE pWorkspace) {
- const auto PMONITOR = pWorkspace->m_monitor.lock();
-
- if (!PMONITOR)
- return;
-
- if (pWorkspace->m_hasFullscreenWindow) {
- // massive hack from the fullscreen func
- const auto PFULLWINDOW = pWorkspace->getFullscreenWindow();
-
- if (pWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) {
- *PFULLWINDOW->m_realPosition = PMONITOR->m_position;
- *PFULLWINDOW->m_realSize = PMONITOR->m_size;
- } else if (pWorkspace->m_fullscreenMode == FSMODE_MAXIMIZED) {
- SMasterNodeData fakeNode;
- fakeNode.pWindow = PFULLWINDOW;
- const auto WORKAREA = workAreaOnWorkspace(pWorkspace);
- fakeNode.position = WORKAREA.pos();
- fakeNode.size = WORKAREA.size();
- fakeNode.workspaceID = pWorkspace->m_id;
- PFULLWINDOW->m_position = fakeNode.position;
- PFULLWINDOW->m_size = fakeNode.size;
- fakeNode.ignoreFullscreenChecks = true;
-
- applyNodeDataToWindow(&fakeNode);
- }
-
- // if has fullscreen, don't calculate the rest
- return;
- }
-
- const auto PMASTERNODE = getMasterNodeOnWorkspace(pWorkspace->m_id);
-
- if (!PMASTERNODE)
- return;
-
- eOrientation orientation = getDynamicOrientation(pWorkspace);
- bool centerMasterWindow = false;
- static auto SLAVECOUNTFORCENTER = CConfigValue<Hyprlang::INT>("master:slave_count_for_center_master");
- static auto CMFALLBACK = CConfigValue<std::string>("master:center_master_fallback");
- static auto PIGNORERESERVED = CConfigValue<Hyprlang::INT>("master:center_ignores_reserved");
- static auto PSMARTRESIZING = CConfigValue<Hyprlang::INT>("master:smart_resizing");
-
- const auto MASTERS = getMastersOnWorkspace(pWorkspace->m_id);
- const auto WINDOWS = getNodesOnWorkspace(pWorkspace->m_id);
- const auto STACKWINDOWS = WINDOWS - MASTERS;
- const auto WORKAREA = workAreaOnWorkspace(pWorkspace);
- const auto UNRESERVED_WIDTH = WORKAREA.width + PMONITOR->m_reservedArea.left() + PMONITOR->m_reservedArea.right();
-
- if (orientation == ORIENTATION_CENTER) {
- if (STACKWINDOWS >= *SLAVECOUNTFORCENTER)
- centerMasterWindow = true;
- else {
- if (*CMFALLBACK == "left")
- orientation = ORIENTATION_LEFT;
- else if (*CMFALLBACK == "right")
- orientation = ORIENTATION_RIGHT;
- else if (*CMFALLBACK == "top")
- orientation = ORIENTATION_TOP;
- else if (*CMFALLBACK == "bottom")
- orientation = ORIENTATION_BOTTOM;
- else
- orientation = ORIENTATION_LEFT;
- }
- }
-
- const float totalSize = (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) ? WORKAREA.w : WORKAREA.h;
- const float masterAverageSize = totalSize / MASTERS;
- const float slaveAverageSize = totalSize / STACKWINDOWS;
- float masterAccumulatedSize = 0;
- float slaveAccumulatedSize = 0;
-
- if (*PSMARTRESIZING) {
- // check the total width and height so that later
- // if larger/smaller than screen size them down/up
- for (auto const& nd : m_masterNodesData) {
- if (nd.workspaceID == pWorkspace->m_id) {
- if (nd.isMaster)
- masterAccumulatedSize += totalSize / MASTERS * nd.percSize;
- else
- slaveAccumulatedSize += totalSize / STACKWINDOWS * nd.percSize;
- }
- }
- }
-
- // compute placement of master window(s)
- if (WINDOWS == 1 && !centerMasterWindow) {
- static auto PALWAYSKEEPPOSITION = CConfigValue<Hyprlang::INT>("master:always_keep_position");
- if (*PALWAYSKEEPPOSITION) {
- const float WIDTH = WORKAREA.w * PMASTERNODE->percMaster;
- float nextX = 0;
-
- if (orientation == ORIENTATION_RIGHT)
- nextX = WORKAREA.w - WIDTH;
- else if (orientation == ORIENTATION_CENTER)
- nextX = (WORKAREA.w - WIDTH) / 2;
-
- PMASTERNODE->size = Vector2D(WIDTH, WORKAREA.h);
- PMASTERNODE->position = WORKAREA.pos() + Vector2D(nextX, 0.0);
- } else {
- PMASTERNODE->size = WORKAREA.size();
- PMASTERNODE->position = WORKAREA.pos();
- }
-
- applyNodeDataToWindow(PMASTERNODE);
- return;
- } else if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) {
- const float HEIGHT = STACKWINDOWS != 0 ? WORKAREA.h * PMASTERNODE->percMaster : WORKAREA.h;
- float widthLeft = WORKAREA.w;
- int mastersLeft = MASTERS;
- float nextX = 0;
- float nextY = 0;
-
- if (orientation == ORIENTATION_BOTTOM)
- nextY = WORKAREA.h - HEIGHT;
-
- for (auto& nd : m_masterNodesData) {
- if (nd.workspaceID != pWorkspace->m_id || !nd.isMaster)
- continue;
-
- float WIDTH = mastersLeft > 1 ? widthLeft / mastersLeft * nd.percSize : widthLeft;
- if (WIDTH > widthLeft * 0.9f && mastersLeft > 1)
- WIDTH = widthLeft * 0.9f;
-
- if (*PSMARTRESIZING) {
- nd.percSize *= WORKAREA.w / masterAccumulatedSize;
- WIDTH = masterAverageSize * nd.percSize;
- }
-
- nd.size = Vector2D(WIDTH, HEIGHT);
- nd.position = WORKAREA.pos() + Vector2D(nextX, nextY);
- applyNodeDataToWindow(&nd);
-
- mastersLeft--;
- widthLeft -= WIDTH;
- nextX += WIDTH;
- }
- } else { // orientation left, right or center
- const float TOTAL_WIDTH = *PIGNORERESERVED && centerMasterWindow ? UNRESERVED_WIDTH : WORKAREA.w;
- float WIDTH = TOTAL_WIDTH;
- float heightLeft = WORKAREA.h;
- int mastersLeft = MASTERS;
- float nextX = 0;
- float nextY = 0;
-
- if (STACKWINDOWS > 0 || centerMasterWindow)
- WIDTH *= PMASTERNODE->percMaster;
-
- if (orientation == ORIENTATION_RIGHT)
- nextX = WORKAREA.w - WIDTH;
- else if (centerMasterWindow)
- nextX += (TOTAL_WIDTH - WIDTH) / 2;
-
- for (auto& nd : m_masterNodesData) {
- if (nd.workspaceID != pWorkspace->m_id || !nd.isMaster)
- continue;
-
- float HEIGHT = mastersLeft > 1 ? heightLeft / mastersLeft * nd.percSize : heightLeft;
- if (HEIGHT > heightLeft * 0.9f && mastersLeft > 1)
- HEIGHT = heightLeft * 0.9f;
-
- if (*PSMARTRESIZING) {
- nd.percSize *= WORKAREA.h / masterAccumulatedSize;
- HEIGHT = masterAverageSize * nd.percSize;
- }
-
- nd.size = Vector2D(WIDTH, HEIGHT);
- nd.position = (*PIGNORERESERVED && centerMasterWindow ? WORKAREA.pos() - Vector2D(PMONITOR->m_reservedArea.left(), 0.0) : WORKAREA.pos()) + Vector2D(nextX, nextY);
- applyNodeDataToWindow(&nd);
-
- mastersLeft--;
- heightLeft -= HEIGHT;
- nextY += HEIGHT;
- }
- }
-
- if (STACKWINDOWS == 0)
- return;
-
- // compute placement of slave window(s)
- int slavesLeft = STACKWINDOWS;
- if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) {
- const float HEIGHT = WORKAREA.h - PMASTERNODE->size.y;
- float widthLeft = WORKAREA.w;
- float nextX = 0;
- float nextY = 0;
-
- if (orientation == ORIENTATION_TOP)
- nextY = PMASTERNODE->size.y;
-
- for (auto& nd : m_masterNodesData) {
- if (nd.workspaceID != pWorkspace->m_id || nd.isMaster)
- continue;
-
- float WIDTH = slavesLeft > 1 ? widthLeft / slavesLeft * nd.percSize : widthLeft;
- if (WIDTH > widthLeft * 0.9f && slavesLeft > 1)
- WIDTH = widthLeft * 0.9f;
-
- if (*PSMARTRESIZING) {
- nd.percSize *= WORKAREA.w / slaveAccumulatedSize;
- WIDTH = slaveAverageSize * nd.percSize;
- }
-
- nd.size = Vector2D(WIDTH, HEIGHT);
- nd.position = WORKAREA.pos() + Vector2D(nextX, nextY);
- applyNodeDataToWindow(&nd);
-
- slavesLeft--;
- widthLeft -= WIDTH;
- nextX += WIDTH;
- }
- } else if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) {
- const float WIDTH = WORKAREA.w - PMASTERNODE->size.x;
- float heightLeft = WORKAREA.h;
- float nextY = 0;
- float nextX = 0;
-
- if (orientation == ORIENTATION_LEFT)
- nextX = PMASTERNODE->size.x;
-
- for (auto& nd : m_masterNodesData) {
- if (nd.workspaceID != pWorkspace->m_id || nd.isMaster)
- continue;
-
- float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd.percSize : heightLeft;
- if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1)
- HEIGHT = heightLeft * 0.9f;
-
- if (*PSMARTRESIZING) {
- nd.percSize *= WORKAREA.h / slaveAccumulatedSize;
- HEIGHT = slaveAverageSize * nd.percSize;
- }
-
- nd.size = Vector2D(WIDTH, HEIGHT);
- nd.position = WORKAREA.pos() + Vector2D(nextX, nextY);
- applyNodeDataToWindow(&nd);
-
- slavesLeft--;
- heightLeft -= HEIGHT;
- nextY += HEIGHT;
- }
- } else { // slaves for centered master window(s)
- const float WIDTH = ((*PIGNORERESERVED ? UNRESERVED_WIDTH : WORKAREA.w) - PMASTERNODE->size.x) / 2.0;
- float heightLeft = 0;
- float heightLeftL = WORKAREA.h;
- float heightLeftR = WORKAREA.h;
- float nextX = 0;
- float nextY = 0;
- float nextYL = 0;
- float nextYR = 0;
- bool onRight = *CMFALLBACK == "right";
- int slavesLeftL = 1 + (slavesLeft - 1) / 2;
- int slavesLeftR = slavesLeft - slavesLeftL;
-
- if (onRight) {
- slavesLeftR = 1 + (slavesLeft - 1) / 2;
- slavesLeftL = slavesLeft - slavesLeftR;
- }
-
- const float slaveAverageHeightL = WORKAREA.h / slavesLeftL;
- const float slaveAverageHeightR = WORKAREA.h / slavesLeftR;
- float slaveAccumulatedHeightL = 0;
- float slaveAccumulatedHeightR = 0;
-
- if (*PSMARTRESIZING) {
- for (auto const& nd : m_masterNodesData) {
- if (nd.workspaceID != pWorkspace->m_id || nd.isMaster)
- continue;
-
- if (onRight) {
- slaveAccumulatedHeightR += slaveAverageHeightR * nd.percSize;
- } else {
- slaveAccumulatedHeightL += slaveAverageHeightL * nd.percSize;
- }
- onRight = !onRight;
- }
-
- onRight = *CMFALLBACK == "right";
- }
-
- for (auto& nd : m_masterNodesData) {
- if (nd.workspaceID != pWorkspace->m_id || nd.isMaster)
- continue;
-
- if (onRight) {
- nextX = WIDTH + PMASTERNODE->size.x - (*PIGNORERESERVED ? PMONITOR->m_reservedArea.left() : 0);
- nextY = nextYR;
- heightLeft = heightLeftR;
- slavesLeft = slavesLeftR;
- } else {
- nextX = 0;
- nextY = nextYL;
- heightLeft = heightLeftL;
- slavesLeft = slavesLeftL;
- }
-
- float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd.percSize : heightLeft;
- if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1)
- HEIGHT = heightLeft * 0.9f;
-
- if (*PSMARTRESIZING) {
- if (onRight) {
- nd.percSize *= WORKAREA.h / slaveAccumulatedHeightR;
- HEIGHT = slaveAverageHeightR * nd.percSize;
- } else {
- nd.percSize *= WORKAREA.h / slaveAccumulatedHeightL;
- HEIGHT = slaveAverageHeightL * nd.percSize;
- }
- }
-
- nd.size = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? PMONITOR->m_reservedArea.right() : PMONITOR->m_reservedArea.left())) : WIDTH, HEIGHT);
- nd.position = WORKAREA.pos() + Vector2D(nextX, nextY);
- applyNodeDataToWindow(&nd);
-
- if (onRight) {
- heightLeftR -= HEIGHT;
- nextYR += HEIGHT;
- slavesLeftR--;
- } else {
- heightLeftL -= HEIGHT;
- nextYL += HEIGHT;
- slavesLeftL--;
- }
-
- onRight = !onRight;
- }
- }
-}
-
-void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) {
- PHLMONITOR PMONITOR = nullptr;
-
- const auto WS = g_pCompositor->getWorkspaceByID(pNode->workspaceID);
-
- if (g_pCompositor->isWorkspaceSpecial(pNode->workspaceID)) {
- for (auto const& m : g_pCompositor->m_monitors) {
- if (m->activeSpecialWorkspaceID() == pNode->workspaceID) {
- PMONITOR = m;
- break;
- }
- }
- } else if (WS)
- PMONITOR = WS->m_monitor.lock();
-
- if (!PMONITOR || !WS) {
- Log::logger->log(Log::ERR, "Orphaned Node {}!!", pNode);
- return;
- }
-
- // for gaps outer
- const auto WORKAREA = workAreaOnWorkspace(WS);
- const bool DISPLAYLEFT = STICKS(pNode->position.x, WORKAREA.x);
- const bool DISPLAYRIGHT = STICKS(pNode->position.x + pNode->size.x, WORKAREA.x + WORKAREA.w);
- const bool DISPLAYTOP = STICKS(pNode->position.y, WORKAREA.y);
- const bool DISPLAYBOTTOM = STICKS(pNode->position.y + pNode->size.y, WORKAREA.y + WORKAREA.h);
-
- const auto PWINDOW = pNode->pWindow.lock();
- // get specific gaps and rules for this workspace,
- // if user specified them in config
- const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(PWINDOW->m_workspace);
-
- if (PWINDOW->isFullscreen() && !pNode->ignoreFullscreenChecks)
- return;
-
- PWINDOW->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT);
- PWINDOW->updateWindowData();
-
- static auto PANIMATE = CConfigValue<Hyprlang::INT>("misc:animate_manual_resizes");
- static auto PGAPSINDATA = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_in");
- auto* PGAPSIN = sc<CCssGapData*>((PGAPSINDATA.ptr())->getData());
-
- auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN);
-
- if (!validMapped(PWINDOW)) {
- Log::logger->log(Log::ERR, "Node {} holding invalid {}!!", pNode, PWINDOW);
- return;
- }
-
- PWINDOW->m_size = pNode->size;
- PWINDOW->m_position = pNode->position;
-
- PWINDOW->updateWindowDecos();
-
- auto calcPos = PWINDOW->m_position;
- auto calcSize = PWINDOW->m_size;
-
- const auto OFFSETTOPLEFT = Vector2D(sc<double>(DISPLAYLEFT ? 0 : gapsIn.m_left), sc<double>(DISPLAYTOP ? 0 : gapsIn.m_top));
-
- const auto OFFSETBOTTOMRIGHT = Vector2D(sc<double>(DISPLAYRIGHT ? 0 : gapsIn.m_right), sc<double>(DISPLAYBOTTOM ? 0 : gapsIn.m_bottom));
-
- calcPos = calcPos + OFFSETTOPLEFT;
- calcSize = calcSize - OFFSETTOPLEFT - OFFSETBOTTOMRIGHT;
-
- const auto RESERVED = PWINDOW->getFullWindowReservedArea();
- calcPos = calcPos + RESERVED.topLeft;
- calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight);
-
- Vector2D availableSpace = calcSize;
-
- static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>("misc:size_limits_tiled");
-
- if (*PCLAMP_TILED) {
- const auto borderSize = PWINDOW->getRealBorderSize();
- Vector2D monitorAvailable = WORKAREA.size() - Vector2D{2.0 * borderSize, 2.0 * borderSize};
-
- Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable);
- Vector2D maxSize = PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} :
- PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable);
- calcSize = calcSize.clamp(minSize, maxSize);
-
- calcPos += (availableSpace - calcSize) / 2.0;
-
- calcPos.x = std::clamp(calcPos.x, WORKAREA.x + borderSize, WORKAREA.x + WORKAREA.w - calcSize.x - borderSize);
- calcPos.y = std::clamp(calcPos.y, WORKAREA.y + borderSize, WORKAREA.y + WORKAREA.h - calcSize.y - borderSize);
- }
-
- if (PWINDOW->onSpecialWorkspace() && !PWINDOW->isFullscreen()) {
- static auto PSCALEFACTOR = CConfigValue<Hyprlang::FLOAT>("master:special_scale_factor");
-
- CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR};
- wb.round(); // avoid rounding mess
-
- *PWINDOW->m_realPosition = wb.pos();
- *PWINDOW->m_realSize = wb.size();
- } else {
- CBox wb = {calcPos, calcSize};
- wb.round(); // avoid rounding mess
-
- *PWINDOW->m_realPosition = wb.pos();
- *PWINDOW->m_realSize = wb.size();
- }
-
- if (m_forceWarps && !*PANIMATE) {
- g_pHyprRenderer->damageWindow(PWINDOW);
-
- PWINDOW->m_realPosition->warp();
- PWINDOW->m_realSize->warp();
-
- g_pHyprRenderer->damageWindow(PWINDOW);
- }
-
- PWINDOW->updateWindowDecos();
-}
-
-bool CHyprMasterLayout::isWindowTiled(PHLWINDOW pWindow) {
- return getNodeFromWindow(pWindow) != nullptr;
-}
-
-void CHyprMasterLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorner corner, PHLWINDOW pWindow) {
- const auto PWINDOW = pWindow ? pWindow : Desktop::focusState()->window();
-
- if (!validMapped(PWINDOW))
- return;
-
- const auto PNODE = getNodeFromWindow(PWINDOW);
-
- if (!PNODE) {
- *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + pixResize)
- .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}),
- PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}));
- PWINDOW->updateWindowDecos();
- return;
- }
-
- const auto PMONITOR = PWINDOW->m_monitor.lock();
- static auto SLAVECOUNTFORCENTER = CConfigValue<Hyprlang::INT>("master:slave_count_for_center_master");
- static auto PSMARTRESIZING = CConfigValue<Hyprlang::INT>("master:smart_resizing");
-
- const auto WORKAREA = PMONITOR->logicalBoxMinusReserved();
- const bool DISPLAYBOTTOM = STICKS(PWINDOW->m_position.y + PWINDOW->m_size.y, WORKAREA.y + WORKAREA.h);
- const bool DISPLAYRIGHT = STICKS(PWINDOW->m_position.x + PWINDOW->m_size.x, WORKAREA.x + WORKAREA.w);
- const bool DISPLAYTOP = STICKS(PWINDOW->m_position.y, WORKAREA.y);
- const bool DISPLAYLEFT = STICKS(PWINDOW->m_position.x, WORKAREA.x);
-
- const bool LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT;
- const bool TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT;
- const bool NONE = corner == CORNER_NONE;
-
- const auto MASTERS = getMastersOnWorkspace(PNODE->workspaceID);
- const auto WINDOWS = getNodesOnWorkspace(PNODE->workspaceID);
- const auto STACKWINDOWS = WINDOWS - MASTERS;
-
- eOrientation orientation = getDynamicOrientation(PWINDOW->m_workspace);
- bool centered = orientation == ORIENTATION_CENTER && (STACKWINDOWS >= *SLAVECOUNTFORCENTER);
- double delta = 0;
-
- if (getNodesOnWorkspace(PWINDOW->workspaceID()) == 1 && !centered)
- return;
-
- m_forceWarps = true;
-
- switch (orientation) {
- case ORIENTATION_LEFT: delta = pixResize.x / PMONITOR->m_size.x; break;
- case ORIENTATION_RIGHT: delta = -pixResize.x / PMONITOR->m_size.x; break;
- case ORIENTATION_BOTTOM: delta = -pixResize.y / PMONITOR->m_size.y; break;
- case ORIENTATION_TOP: delta = pixResize.y / PMONITOR->m_size.y; break;
- case ORIENTATION_CENTER:
- delta = pixResize.x / PMONITOR->m_size.x;
- if (STACKWINDOWS >= *SLAVECOUNTFORCENTER) {
- if (!NONE || !PNODE->isMaster)
- delta *= 2;
- if ((!PNODE->isMaster && DISPLAYLEFT) || (PNODE->isMaster && LEFT && *PSMARTRESIZING))
- delta = -delta;
- }
- break;
- default: UNREACHABLE();
- }
-
- const auto workspaceIdForResizing = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->activeSpecialWorkspaceID() : PMONITOR->activeWorkspaceID();
- for (auto& n : m_masterNodesData) {
- if (n.isMaster && n.workspaceID == workspaceIdForResizing)
- n.percMaster = std::clamp(n.percMaster + delta, 0.05, 0.95);
- }
-
- // check the up/down resize
- const bool isStackVertical = orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT || orientation == ORIENTATION_CENTER;
-
- const auto RESIZEDELTA = isStackVertical ? pixResize.y : pixResize.x;
-
- auto nodesInSameColumn = PNODE->isMaster ? MASTERS : STACKWINDOWS;
- if (orientation == ORIENTATION_CENTER && !PNODE->isMaster)
- nodesInSameColumn = DISPLAYRIGHT ? (nodesInSameColumn + 1) / 2 : nodesInSameColumn / 2;
-
- const auto SIZE = isStackVertical ? WORKAREA.h / nodesInSameColumn : WORKAREA.w / nodesInSameColumn;
-
- if (RESIZEDELTA != 0 && nodesInSameColumn > 1) {
- if (!*PSMARTRESIZING) {
- PNODE->percSize = std::clamp(PNODE->percSize + RESIZEDELTA / SIZE, 0.05, 1.95);
- } else {
- const auto NODEIT = std::ranges::find(m_masterNodesData, *PNODE);
- const auto REVNODEIT = std::ranges::find(m_masterNodesData | std::views::reverse, *PNODE);
-
- const float totalSize = isStackVertical ? WORKAREA.h : WORKAREA.w;
- const float minSize = totalSize / nodesInSameColumn * 0.2;
- const bool resizePrevNodes = isStackVertical ? (TOP || DISPLAYBOTTOM) && !DISPLAYTOP : (LEFT || DISPLAYRIGHT) && !DISPLAYLEFT;
-
- int nodesLeft = 0;
- float sizeLeft = 0;
- int nodeCount = 0;
- // check the sizes of all the nodes to be resized for later calculation
- auto checkNodesLeft = [&sizeLeft, &nodesLeft, orientation, isStackVertical, &nodeCount, PNODE](auto it) {
- if (it.isMaster != PNODE->isMaster || it.workspaceID != PNODE->workspaceID)
- return;
- nodeCount++;
- if (!it.isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1)
- return;
- sizeLeft += isStackVertical ? it.size.y : it.size.x;
- nodesLeft++;
- };
- float resizeDiff;
- if (resizePrevNodes) {
- std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), checkNodesLeft);
- resizeDiff = -RESIZEDELTA;
- } else {
- std::for_each(std::next(NODEIT), m_masterNodesData.end(), checkNodesLeft);
- resizeDiff = RESIZEDELTA;
- }
-
- const float nodeSize = isStackVertical ? PNODE->size.y : PNODE->size.x;
- const float maxSizeIncrease = sizeLeft - nodesLeft * minSize;
- const float maxSizeDecrease = minSize - nodeSize;
-
- // leaves enough room for the other nodes
- resizeDiff = std::clamp(resizeDiff, maxSizeDecrease, maxSizeIncrease);
- PNODE->percSize += resizeDiff / SIZE;
-
- // resize the other nodes
- nodeCount = 0;
- auto resizeNodesLeft = [maxSizeIncrease, resizeDiff, minSize, orientation, isStackVertical, SIZE, &nodeCount, nodesLeft, PNODE](auto& it) {
- if (it.isMaster != PNODE->isMaster || it.workspaceID != PNODE->workspaceID)
- return;
- nodeCount++;
- // if center orientation, only resize when on the same side
- if (!it.isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1)
- return;
- const float size = isStackVertical ? it.size.y : it.size.x;
- const float resizeDeltaForEach = maxSizeIncrease != 0 ? resizeDiff * (size - minSize) / maxSizeIncrease : resizeDiff / nodesLeft;
- it.percSize -= resizeDeltaForEach / SIZE;
- };
- if (resizePrevNodes) {
- std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), resizeNodesLeft);
- } else {
- std::for_each(std::next(NODEIT), m_masterNodesData.end(), resizeNodesLeft);
- }
- }
- }
-
- recalculateMonitor(PMONITOR->m_id);
-
- m_forceWarps = false;
-}
-
-void CHyprMasterLayout::fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE) {
- const auto PMONITOR = pWindow->m_monitor.lock();
- const auto PWORKSPACE = pWindow->m_workspace;
-
- // save position and size if floating
- if (pWindow->m_isFloating && CURRENT_EFFECTIVE_MODE == FSMODE_NONE) {
- pWindow->m_lastFloatingSize = pWindow->m_realSize->goal();
- pWindow->m_lastFloatingPosition = pWindow->m_realPosition->goal();
- pWindow->m_position = pWindow->m_realPosition->goal();
- pWindow->m_size = pWindow->m_realSize->goal();
- }
-
- if (EFFECTIVE_MODE == FSMODE_NONE) {
- // if it got its fullscreen disabled, set back its node if it had one
- const auto PNODE = getNodeFromWindow(pWindow);
- if (PNODE)
- applyNodeDataToWindow(PNODE);
- else {
- // get back its' dimensions from position and size
- *pWindow->m_realPosition = pWindow->m_lastFloatingPosition;
- *pWindow->m_realSize = pWindow->m_lastFloatingSize;
-
- pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT);
- pWindow->updateWindowData();
- }
- } else {
- // apply new pos and size being monitors' box
- if (EFFECTIVE_MODE == FSMODE_FULLSCREEN) {
- *pWindow->m_realPosition = PMONITOR->m_position;
- *pWindow->m_realSize = PMONITOR->m_size;
- } else {
- // This is a massive hack.
- // We make a fake "only" node and apply
- // To keep consistent with the settings without C+P code
-
- SMasterNodeData fakeNode;
- const auto WORKAREA = PMONITOR->logicalBoxMinusReserved();
- fakeNode.pWindow = pWindow;
- fakeNode.position = WORKAREA.pos();
- fakeNode.size = WORKAREA.size();
- fakeNode.workspaceID = pWindow->workspaceID();
- pWindow->m_position = fakeNode.position;
- pWindow->m_size = fakeNode.size;
- fakeNode.ignoreFullscreenChecks = true;
-
- applyNodeDataToWindow(&fakeNode);
- }
- }
-
- g_pCompositor->changeWindowZOrder(pWindow, true);
-}
-
-void CHyprMasterLayout::recalculateWindow(PHLWINDOW pWindow) {
- const auto PNODE = getNodeFromWindow(pWindow);
-
- if (!PNODE)
- return;
-
- recalculateMonitor(pWindow->monitorID());
-}
-
-SWindowRenderLayoutHints CHyprMasterLayout::requestRenderHints(PHLWINDOW pWindow) {
- // window should be valid, insallah
-
- SWindowRenderLayoutHints hints;
-
- return hints; // master doesn't have any hints
-}
-
-void CHyprMasterLayout::moveWindowTo(PHLWINDOW pWindow, const std::string& dir, bool silent) {
- if (!isDirection(dir))
- return;
-
- const auto PWINDOW2 = g_pCompositor->getWindowInDirection(pWindow, dir[0]);
-
- if (!PWINDOW2)
- return;
-
- pWindow->setAnimationsToMove();
-
- if (pWindow->m_workspace != PWINDOW2->m_workspace) {
- // if different monitors, send to monitor
- onWindowRemovedTiling(pWindow);
- pWindow->moveToWorkspace(PWINDOW2->m_workspace);
- pWindow->m_monitor = PWINDOW2->m_monitor;
- if (!silent) {
- const auto pMonitor = pWindow->m_monitor.lock();
- Desktop::focusState()->rawMonitorFocus(pMonitor);
- }
- onWindowCreatedTiling(pWindow);
- } else {
- // if same monitor, switch windows
- switchWindows(pWindow, PWINDOW2);
- if (silent)
- Desktop::focusState()->fullWindowFocus(PWINDOW2);
- }
-
- pWindow->updateGroupOutputs();
- if (!pWindow->m_groupData.pNextWindow.expired()) {
- PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock();
- while (next != pWindow) {
- next->updateToplevel();
- next = next->m_groupData.pNextWindow.lock();
- }
- }
-}
-
-void CHyprMasterLayout::switchWindows(PHLWINDOW pWindow, PHLWINDOW pWindow2) {
- // windows should be valid, insallah
-
- const auto PNODE = getNodeFromWindow(pWindow);
- const auto PNODE2 = getNodeFromWindow(pWindow2);
-
- if (!PNODE2 || !PNODE)
- return;
-
- if (PNODE->workspaceID != PNODE2->workspaceID) {
- std::swap(pWindow2->m_monitor, pWindow->m_monitor);
- std::swap(pWindow2->m_workspace, pWindow->m_workspace);
- }
-
- // massive hack: just swap window pointers, lol
- PNODE->pWindow = pWindow2;
- PNODE2->pWindow = pWindow;
-
- pWindow->setAnimationsToMove();
- pWindow2->setAnimationsToMove();
-
- recalculateMonitor(pWindow->monitorID());
- if (PNODE2->workspaceID != PNODE->workspaceID)
- recalculateMonitor(pWindow2->monitorID());
-
- g_pHyprRenderer->damageWindow(pWindow);
- g_pHyprRenderer->damageWindow(pWindow2);
-}
-
-void CHyprMasterLayout::alterSplitRatio(PHLWINDOW pWindow, float ratio, bool exact) {
- // window should be valid, insallah
-
- const auto PNODE = getNodeFromWindow(pWindow);
-
- if (!PNODE)
- return;
-
- const auto PMASTER = getMasterNodeOnWorkspace(pWindow->workspaceID());
-
- float newRatio = exact ? ratio : PMASTER->percMaster + ratio;
- PMASTER->percMaster = std::clamp(newRatio, 0.05f, 0.95f);
-
- recalculateMonitor(pWindow->monitorID());
-}
-
-PHLWINDOW CHyprMasterLayout::getNextWindow(PHLWINDOW pWindow, bool next, bool loop) {
- if (!isWindowTiled(pWindow))
- return nullptr;
-
- const auto PNODE = getNodeFromWindow(pWindow);
-
- auto nodes = m_masterNodesData;
- if (!next)
- std::ranges::reverse(nodes);
-
- const auto NODEIT = std::ranges::find(nodes, *PNODE);
-
- const bool ISMASTER = PNODE->isMaster;
-
- auto CANDIDATE = std::find_if(NODEIT, nodes.end(), [&](const auto& other) { return other != *PNODE && ISMASTER == other.isMaster && other.workspaceID == PNODE->workspaceID; });
- if (CANDIDATE == nodes.end())
- CANDIDATE = std::ranges::find_if(nodes, [&](const auto& other) { return other != *PNODE && ISMASTER != other.isMaster && other.workspaceID == PNODE->workspaceID; });
-
- if (CANDIDATE != nodes.end() && !loop) {
- if (CANDIDATE->isMaster && next)
- return nullptr;
- if (!CANDIDATE->isMaster && ISMASTER && !next)
- return nullptr;
- }
-
- return CANDIDATE == nodes.end() ? nullptr : CANDIDATE->pWindow.lock();
-}
-
-std::any CHyprMasterLayout::layoutMessage(SLayoutMessageHeader header, std::string message) {
- auto switchToWindow = [&](PHLWINDOW PWINDOWTOCHANGETO) {
- if (!validMapped(PWINDOWTOCHANGETO))
- return;
-
- Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO);
- g_pCompositor->warpCursorTo(PWINDOWTOCHANGETO->middle());
-
- g_pInputManager->m_forcedFocus = PWINDOWTOCHANGETO;
- g_pInputManager->simulateMouseMovement();
- g_pInputManager->m_forcedFocus.reset();
- };
-
- CVarList vars(message, 0, ' ');
-
- if (vars.size() < 1 || vars[0].empty()) {
- Log::logger->log(Log::ERR, "layoutmsg called without params");
- return 0;
- }
-
- auto command = vars[0];
-
- // swapwithmaster <master | child | auto> <ignoremaster>
- // first message argument can have the following values:
- // * master - keep the focus at the new master
- // * child - keep the focus at the new child
- // * auto (default) - swap the focus (keep the focus of the previously selected window)
- // * ignoremaster - ignore if master is focused
- if (command == "swapwithmaster") {
- const auto PWINDOW = header.pWindow;
-
- if (!PWINDOW)
- return 0;
-
- if (!isWindowTiled(PWINDOW))
- return 0;
-
- const auto PMASTER = getMasterNodeOnWorkspace(PWINDOW->workspaceID());
-
- if (!PMASTER)
- return 0;
-
- const auto NEWCHILD = PMASTER->pWindow.lock();
-
- const bool IGNORE_IF_MASTER = vars.size() >= 2 && std::ranges::any_of(vars, [](const auto& e) { return e == "ignoremaster"; });
-
- if (PMASTER->pWindow.lock() != PWINDOW) {
- const auto& NEWMASTER = PWINDOW;
- const bool newFocusToChild = vars.size() >= 2 && vars[1] == "child";
- switchWindows(NEWMASTER, NEWCHILD);
- const auto NEWFOCUS = newFocusToChild ? NEWCHILD : NEWMASTER;
- switchToWindow(NEWFOCUS);
- } else if (!IGNORE_IF_MASTER) {
- for (auto const& n : m_masterNodesData) {
- if (n.workspaceID == PMASTER->workspaceID && !n.isMaster) {
- const auto NEWMASTER = n.pWindow.lock();
- switchWindows(NEWMASTER, NEWCHILD);
- const bool newFocusToMaster = vars.size() >= 2 && vars[1] == "master";
- const auto NEWFOCUS = newFocusToMaster ? NEWMASTER : NEWCHILD;
- switchToWindow(NEWFOCUS);
- break;
- }
- }
- }
-
- return 0;
- }
- // focusmaster <master | previous | auto>
- // first message argument can have the following values:
- // * master - keep the focus at the new master, even if it was focused before
- // * previous - focus window which was previously switched from using `focusmaster previous` command, otherwise fallback to `auto`
- // * auto (default) - swap the focus with the first child, if the current focus was master, otherwise focus master
- else if (command == "focusmaster") {
- const auto PWINDOW = header.pWindow;
-
- if (!PWINDOW)
- return 0;
-
- const auto PMASTER = getMasterNodeOnWorkspace(PWINDOW->workspaceID());
-
- if (!PMASTER)
- return 0;
-
- const auto& ARG = vars[1]; // returns empty string if out of bounds
-
- if (PMASTER->pWindow.lock() != PWINDOW) {
- switchToWindow(PMASTER->pWindow.lock());
- // save previously focused window (only for `previous` mode)
- if (ARG == "previous")
- getMasterWorkspaceData(PWINDOW->workspaceID())->focusMasterPrev = PWINDOW;
- return 0;
- }
-
- const auto focusAuto = [&]() {
- // focus first non-master window
- for (auto const& n : m_masterNodesData) {
- if (n.workspaceID == PMASTER->workspaceID && !n.isMaster) {
- switchToWindow(n.pWindow.lock());
- break;
- }
- }
- };
-
- if (ARG == "master")
- return 0;
- // switch to previously saved window
- else if (ARG == "previous") {
- const auto PREVWINDOW = getMasterWorkspaceData(PWINDOW->workspaceID())->focusMasterPrev.lock();
- const bool VALID = validMapped(PREVWINDOW) && PWINDOW->workspaceID() == PREVWINDOW->workspaceID() && PWINDOW != PREVWINDOW;
- VALID ? switchToWindow(PREVWINDOW) : focusAuto();
- } else
- focusAuto();
- } else if (command == "cyclenext") {
- const auto PWINDOW = header.pWindow;
-
- if (!PWINDOW)
- return 0;
-
- const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop";
- const auto PNEXTWINDOW = getNextWindow(PWINDOW, true, !NOLOOP);
- switchToWindow(PNEXTWINDOW);
- } else if (command == "cycleprev") {
- const auto PWINDOW = header.pWindow;
-
- if (!PWINDOW)
- return 0;
-
- const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop";
- const auto PPREVWINDOW = getNextWindow(PWINDOW, false, !NOLOOP);
- switchToWindow(PPREVWINDOW);
- } else if (command == "swapnext") {
- if (!validMapped(header.pWindow))
- return 0;
-
- if (header.pWindow->m_isFloating) {
- g_pKeybindManager->m_dispatchers["swapnext"]("");
- return 0;
- }
-
- const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop";
- const auto PWINDOWTOSWAPWITH = getNextWindow(header.pWindow, true, !NOLOOP);
-
- if (PWINDOWTOSWAPWITH) {
- g_pCompositor->setWindowFullscreenInternal(header.pWindow, FSMODE_NONE);
- switchWindows(header.pWindow, PWINDOWTOSWAPWITH);
- switchToWindow(header.pWindow);
- }
- } else if (command == "swapprev") {
- if (!validMapped(header.pWindow))
- return 0;
-
- if (header.pWindow->m_isFloating) {
- g_pKeybindManager->m_dispatchers["swapnext"]("prev");
- return 0;
- }
-
- const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop";
- const auto PWINDOWTOSWAPWITH = getNextWindow(header.pWindow, false, !NOLOOP);
-
- if (PWINDOWTOSWAPWITH) {
- g_pCompositor->setWindowFullscreenClient(header.pWindow, FSMODE_NONE);
- switchWindows(header.pWindow, PWINDOWTOSWAPWITH);
- switchToWindow(header.pWindow);
- }
- } else if (command == "addmaster") {
- if (!validMapped(header.pWindow))
- return 0;
-
- if (header.pWindow->m_isFloating)
- return 0;
-
- const auto PNODE = getNodeFromWindow(header.pWindow);
-
- const auto WINDOWS = getNodesOnWorkspace(header.pWindow->workspaceID());
- const auto MASTERS = getMastersOnWorkspace(header.pWindow->workspaceID());
- static auto SMALLSPLIT = CConfigValue<Hyprlang::INT>("master:allow_small_split");
-
- if (MASTERS + 2 > WINDOWS && *SMALLSPLIT == 0)
- return 0;
-
- g_pCompositor->setWindowFullscreenInternal(header.pWindow, FSMODE_NONE);
-
- if (!PNODE || PNODE->isMaster) {
- // first non-master node
- for (auto& n : m_masterNodesData) {
- if (n.workspaceID == header.pWindow->workspaceID() && !n.isMaster) {
- n.isMaster = true;
- break;
- }
- }
- } else {
- PNODE->isMaster = true;
- }
-
- recalculateMonitor(header.pWindow->monitorID());
-
- } else if (command == "removemaster") {
-
- if (!validMapped(header.pWindow))
- return 0;
-
- if (header.pWindow->m_isFloating)
- return 0;
-
- const auto PNODE = getNodeFromWindow(header.pWindow);
-
- const auto WINDOWS = getNodesOnWorkspace(header.pWindow->workspaceID());
- const auto MASTERS = getMastersOnWorkspace(header.pWindow->workspaceID());
-
- if (WINDOWS < 2 || MASTERS < 2)
- return 0;
-
- g_pCompositor->setWindowFullscreenInternal(header.pWindow, FSMODE_NONE);
-
- if (!PNODE || !PNODE->isMaster) {
- // first non-master node
- for (auto& nd : m_masterNodesData | std::views::reverse) {
- if (nd.workspaceID == header.pWindow->workspaceID() && nd.isMaster) {
- nd.isMaster = false;
- break;
- }
- }
- } else {
- PNODE->isMaster = false;
- }
-
- recalculateMonitor(header.pWindow->monitorID());
- } else if (command == "orientationleft" || command == "orientationright" || command == "orientationtop" || command == "orientationbottom" || command == "orientationcenter") {
- const auto PWINDOW = header.pWindow;
-
- if (!PWINDOW)
- return 0;
-
- g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);
-
- const auto PWORKSPACEDATA = getMasterWorkspaceData(PWINDOW->workspaceID());
-
- if (command == "orientationleft")
- PWORKSPACEDATA->orientation = ORIENTATION_LEFT;
- else if (command == "orientationright")
- PWORKSPACEDATA->orientation = ORIENTATION_RIGHT;
- else if (command == "orientationtop")
- PWORKSPACEDATA->orientation = ORIENTATION_TOP;
- else if (command == "orientationbottom")
- PWORKSPACEDATA->orientation = ORIENTATION_BOTTOM;
- else if (command == "orientationcenter")
- PWORKSPACEDATA->orientation = ORIENTATION_CENTER;
-
- recalculateMonitor(header.pWindow->monitorID());
-
- } else if (command == "orientationnext") {
- runOrientationCycle(header, nullptr, 1);
- } else if (command == "orientationprev") {
- runOrientationCycle(header, nullptr, -1);
- } else if (command == "orientationcycle") {
- runOrientationCycle(header, &vars, 1);
- } else if (command == "mfact") {
- g_pKeybindManager->m_dispatchers["splitratio"](vars[1] + " " + vars[2]);
- } else if (command == "rollnext") {
- const auto PWINDOW = header.pWindow;
- const auto PNODE = getNodeFromWindow(PWINDOW);
-
- if (!PNODE)
- return 0;
-
- const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNodeOnWorkspace(PNODE->workspaceID);
- if (!OLDMASTER)
- return 0;
-
- const auto OLDMASTERIT = std::ranges::find(m_masterNodesData, *OLDMASTER);
-
- for (auto& nd : m_masterNodesData) {
- if (nd.workspaceID == PNODE->workspaceID && !nd.isMaster) {
- nd.isMaster = true;
- const auto NEWMASTERIT = std::ranges::find(m_masterNodesData, nd);
- m_masterNodesData.splice(OLDMASTERIT, m_masterNodesData, NEWMASTERIT);
- switchToWindow(nd.pWindow.lock());
- OLDMASTER->isMaster = false;
- m_masterNodesData.splice(m_masterNodesData.end(), m_masterNodesData, OLDMASTERIT);
- break;
- }
- }
-
- recalculateMonitor(PWINDOW->monitorID());
- } else if (command == "rollprev") {
- const auto PWINDOW = header.pWindow;
- const auto PNODE = getNodeFromWindow(PWINDOW);
-
- if (!PNODE)
- return 0;
-
- const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNodeOnWorkspace(PNODE->workspaceID);
- if (!OLDMASTER)
- return 0;
-
- const auto OLDMASTERIT = std::ranges::find(m_masterNodesData, *OLDMASTER);
-
- for (auto& nd : m_masterNodesData | std::views::reverse) {
- if (nd.workspaceID == PNODE->workspaceID && !nd.isMaster) {
- nd.isMaster = true;
- const auto NEWMASTERIT = std::ranges::find(m_masterNodesData, nd);
- m_masterNodesData.splice(OLDMASTERIT, m_masterNodesData, NEWMASTERIT);
- switchToWindow(nd.pWindow.lock());
- OLDMASTER->isMaster = false;
- m_masterNodesData.splice(m_masterNodesData.begin(), m_masterNodesData, OLDMASTERIT);
- break;
- }
- }
-
- recalculateMonitor(PWINDOW->monitorID());
- }
-
- return 0;
-}
-
-// If vars is null, we use the default list
-void CHyprMasterLayout::runOrientationCycle(SLayoutMessageHeader& header, CVarList* vars, int direction) {
- std::vector<eOrientation> cycle;
- if (vars != nullptr)
- buildOrientationCycleVectorFromVars(cycle, *vars);
-
- if (cycle.empty())
- buildOrientationCycleVectorFromEOperation(cycle);
-
- const auto PWINDOW = header.pWindow;
-
- if (!PWINDOW)
- return;
-
- g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);
-
- const auto PWORKSPACEDATA = getMasterWorkspaceData(PWINDOW->workspaceID());
-
- int nextOrPrev = 0;
- for (size_t i = 0; i < cycle.size(); ++i) {
- if (PWORKSPACEDATA->orientation == cycle[i]) {
- nextOrPrev = i + direction;
- break;
- }
- }
-
- if (nextOrPrev >= sc<int>(cycle.size()))
- nextOrPrev = nextOrPrev % sc<int>(cycle.size());
- else if (nextOrPrev < 0)
- nextOrPrev = cycle.size() + (nextOrPrev % sc<int>(cycle.size()));
-
- PWORKSPACEDATA->orientation = cycle.at(nextOrPrev);
- recalculateMonitor(header.pWindow->monitorID());
-}
-
-void CHyprMasterLayout::buildOrientationCycleVectorFromEOperation(std::vector<eOrientation>& cycle) {
- for (int i = 0; i <= ORIENTATION_CENTER; ++i) {
- cycle.push_back(sc<eOrientation>(i));
- }
-}
-
-void CHyprMasterLayout::buildOrientationCycleVectorFromVars(std::vector<eOrientation>& cycle, CVarList& vars) {
- for (size_t i = 1; i < vars.size(); ++i) {
- if (vars[i] == "top") {
- cycle.push_back(ORIENTATION_TOP);
- } else if (vars[i] == "right") {
- cycle.push_back(ORIENTATION_RIGHT);
- } else if (vars[i] == "bottom") {
- cycle.push_back(ORIENTATION_BOTTOM);
- } else if (vars[i] == "left") {
- cycle.push_back(ORIENTATION_LEFT);
- } else if (vars[i] == "center") {
- cycle.push_back(ORIENTATION_CENTER);
- }
- }
-}
-
-eOrientation CHyprMasterLayout::getDynamicOrientation(PHLWORKSPACE pWorkspace) {
- const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(pWorkspace);
- std::string orientationString;
- if (WORKSPACERULE.layoutopts.contains("orientation"))
- orientationString = WORKSPACERULE.layoutopts.at("orientation");
-
- eOrientation orientation = getMasterWorkspaceData(pWorkspace->m_id)->orientation;
- // override if workspace rule is set
- if (!orientationString.empty()) {
- if (orientationString == "top")
- orientation = ORIENTATION_TOP;
- else if (orientationString == "right")
- orientation = ORIENTATION_RIGHT;
- else if (orientationString == "bottom")
- orientation = ORIENTATION_BOTTOM;
- else if (orientationString == "center")
- orientation = ORIENTATION_CENTER;
- else
- orientation = ORIENTATION_LEFT;
- }
-
- return orientation;
-}
-
-void CHyprMasterLayout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) {
- const auto PNODE = getNodeFromWindow(from);
-
- if (!PNODE)
- return;
-
- PNODE->pWindow = to;
-
- applyNodeDataToWindow(PNODE);
-}
-
-Vector2D CHyprMasterLayout::predictSizeForNewWindowTiled() {
- static auto PNEWSTATUS = CConfigValue<std::string>("master:new_status");
-
- if (!Desktop::focusState()->monitor())
- return {};
-
- const int NODES = getNodesOnWorkspace(Desktop::focusState()->monitor()->m_activeWorkspace->m_id);
-
- if (NODES <= 0)
- return Desktop::focusState()->monitor()->m_size;
-
- const auto MASTER = getMasterNodeOnWorkspace(Desktop::focusState()->monitor()->m_activeWorkspace->m_id);
- if (!MASTER) // wtf
- return {};
-
- if (*PNEWSTATUS == "master") {
- return MASTER->size;
- } else {
- const auto SLAVES = NODES - getMastersOnWorkspace(Desktop::focusState()->monitor()->m_activeWorkspace->m_id);
-
- // TODO: make this better
- return {Desktop::focusState()->monitor()->m_size.x - MASTER->size.x, Desktop::focusState()->monitor()->m_size.y / (SLAVES + 1)};
- }
-
- return {};
-}
-
-void CHyprMasterLayout::onEnable() {
- for (auto const& w : g_pCompositor->m_windows) {
- if (w->m_isFloating || !w->m_isMapped || w->isHidden())
- continue;
-
- onWindowCreatedTiling(w);
- }
-}
-
-void CHyprMasterLayout::onDisable() {
- m_masterNodesData.clear();
-}
diff --git a/src/layout/MasterLayout.hpp b/src/layout/MasterLayout.hpp
deleted file mode 100644
index a5968916..00000000
--- a/src/layout/MasterLayout.hpp
+++ /dev/null
@@ -1,112 +0,0 @@
-#pragma once
-
-#include "IHyprLayout.hpp"
-#include "../desktop/DesktopTypes.hpp"
-#include "../helpers/varlist/VarList.hpp"
-#include <vector>
-#include <list>
-#include <any>
-
-enum eFullscreenMode : int8_t;
-
-//orientation determines which side of the screen the master area resides
-enum eOrientation : uint8_t {
- ORIENTATION_LEFT = 0,
- ORIENTATION_TOP,
- ORIENTATION_RIGHT,
- ORIENTATION_BOTTOM,
- ORIENTATION_CENTER
-};
-
-struct SMasterNodeData {
- bool isMaster = false;
- float percMaster = 0.5f;
-
- PHLWINDOWREF pWindow;
-
- Vector2D position;
- Vector2D size;
-
- float percSize = 1.f; // size multiplier for resizing children
-
- WORKSPACEID workspaceID = WORKSPACE_INVALID;
-
- bool ignoreFullscreenChecks = false;
-
- //
- bool operator==(const SMasterNodeData& rhs) const {
- return pWindow.lock() == rhs.pWindow.lock();
- }
-};
-
-struct SMasterWorkspaceData {
- WORKSPACEID workspaceID = WORKSPACE_INVALID;
- eOrientation orientation = ORIENTATION_LEFT;
- // Previously focused non-master window when `focusmaster previous` command was issued
- PHLWINDOWREF focusMasterPrev;
-
- //
- bool operator==(const SMasterWorkspaceData& rhs) const {
- return workspaceID == rhs.workspaceID;
- }
-};
-
-class CHyprMasterLayout : public IHyprLayout {
- public:
- virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT);
- virtual void onWindowRemovedTiling(PHLWINDOW);
- virtual bool isWindowTiled(PHLWINDOW);
- virtual void recalculateMonitor(const MONITORID&);
- virtual void recalculateWindow(PHLWINDOW);
- virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr);
- virtual void fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE);
- virtual std::any layoutMessage(SLayoutMessageHeader, std::string);
- virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW);
- virtual void switchWindows(PHLWINDOW, PHLWINDOW);
- virtual void moveWindowTo(PHLWINDOW, const std::string& dir, bool silent);
- virtual void alterSplitRatio(PHLWINDOW, float, bool);
- virtual std::string getLayoutName();
- virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to);
- virtual Vector2D predictSizeForNewWindowTiled();
-
- virtual void onEnable();
- virtual void onDisable();
-
- private:
- std::list<SMasterNodeData> m_masterNodesData;
- std::vector<SMasterWorkspaceData> m_masterWorkspacesData;
-
- bool m_forceWarps = false;
-
- void buildOrientationCycleVectorFromVars(std::vector<eOrientation>& cycle, CVarList& vars);
- void buildOrientationCycleVectorFromEOperation(std::vector<eOrientation>& cycle);
- void runOrientationCycle(SLayoutMessageHeader& header, CVarList* vars, int next);
- eOrientation getDynamicOrientation(PHLWORKSPACE);
- int getNodesOnWorkspace(const WORKSPACEID&);
- void applyNodeDataToWindow(SMasterNodeData*);
- SMasterNodeData* getNodeFromWindow(PHLWINDOW);
- SMasterNodeData* getMasterNodeOnWorkspace(const WORKSPACEID&);
- SMasterWorkspaceData* getMasterWorkspaceData(const WORKSPACEID&);
- void calculateWorkspace(PHLWORKSPACE);
- PHLWINDOW getNextWindow(PHLWINDOW, bool, bool);
- int getMastersOnWorkspace(const WORKSPACEID&);
-
- friend struct SMasterNodeData;
- friend struct SMasterWorkspaceData;
-};
-
-template <typename CharT>
-struct std::formatter<SMasterNodeData*, CharT> : std::formatter<CharT> {
- template <typename FormatContext>
- auto format(const SMasterNodeData* const& node, FormatContext& ctx) const {
- auto out = ctx.out();
- if (!node)
- return std::format_to(out, "[Node nullptr]");
- std::format_to(out, "[Node {:x}: workspace: {}, pos: {:j2}, size: {:j2}", rc<uintptr_t>(node), node->workspaceID, node->position, node->size);
- if (node->isMaster)
- std::format_to(out, ", master");
- if (!node->pWindow.expired())
- std::format_to(out, ", window: {:x}", node->pWindow.lock());
- return std::format_to(out, "]");
- }
-};
diff --git a/src/layout/algorithm/Algorithm.cpp b/src/layout/algorithm/Algorithm.cpp
new file mode 100644
index 00000000..cd8cfac4
--- /dev/null
+++ b/src/layout/algorithm/Algorithm.cpp
@@ -0,0 +1,264 @@
+#include "Algorithm.hpp"
+
+#include "FloatingAlgorithm.hpp"
+#include "TiledAlgorithm.hpp"
+#include "../target/WindowTarget.hpp"
+#include "../space/Space.hpp"
+#include "../../desktop/view/Window.hpp"
+#include "../../desktop/history/WindowHistoryTracker.hpp"
+#include "../../helpers/Monitor.hpp"
+#include "../../render/Renderer.hpp"
+
+#include "../../debug/log/Logger.hpp"
+
+using namespace Layout;
+
+SP<CAlgorithm> CAlgorithm::create(UP<ITiledAlgorithm>&& tiled, UP<IFloatingAlgorithm>&& floating, SP<CSpace> space) {
+ auto algo = SP<CAlgorithm>(new CAlgorithm(std::move(tiled), std::move(floating), space));
+ algo->m_self = algo;
+ algo->m_tiled->m_parent = algo;
+ algo->m_floating->m_parent = algo;
+ return algo;
+}
+
+CAlgorithm::CAlgorithm(UP<ITiledAlgorithm>&& tiled, UP<IFloatingAlgorithm>&& floating, SP<CSpace> space) :
+ m_tiled(std::move(tiled)), m_floating(std::move(floating)), m_space(space) {
+ ;
+}
+
+void CAlgorithm::addTarget(SP<ITarget> target) {
+ const bool SHOULD_FLOAT = target->floating();
+
+ if (SHOULD_FLOAT) {
+ m_floatingTargets.emplace_back(target);
+ m_floating->newTarget(target);
+ } else {
+ m_tiledTargets.emplace_back(target);
+ m_tiled->newTarget(target);
+ }
+}
+
+void CAlgorithm::removeTarget(SP<ITarget> target) {
+ const bool IS_FLOATING = std::ranges::contains(m_floatingTargets, target);
+
+ if (IS_FLOATING) {
+ m_floating->removeTarget(target);
+ std::erase(m_floatingTargets, target);
+ return;
+ }
+
+ const bool IS_TILED = std::ranges::contains(m_tiledTargets, target);
+
+ if (IS_TILED) {
+ m_tiled->removeTarget(target);
+ std::erase(m_tiledTargets, target);
+ return;
+ }
+
+ Log::logger->log(Log::ERR, "BUG THIS: CAlgorithm::removeTarget, but not found");
+}
+
+void CAlgorithm::moveTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint, bool reposition) {
+ const bool SHOULD_FLOAT = target->floating();
+
+ if (SHOULD_FLOAT) {
+ m_floatingTargets.emplace_back(target);
+ if (reposition)
+ m_floating->newTarget(target);
+ else
+ m_floating->movedTarget(target, focalPoint);
+ } else {
+ m_tiledTargets.emplace_back(target);
+ if (reposition)
+ m_tiled->newTarget(target);
+ else
+ m_tiled->movedTarget(target, focalPoint);
+ }
+}
+
+SP<CSpace> CAlgorithm::space() const {
+ return m_space.lock();
+}
+
+void CAlgorithm::setFloating(SP<ITarget> target, bool floating, bool reposition) {
+ removeTarget(target);
+
+ g_pHyprRenderer->damageWindow(target->window());
+
+ target->setFloating(floating);
+
+ moveTarget(target, std::nullopt, reposition);
+
+ g_pHyprRenderer->damageWindow(target->window());
+}
+
+size_t CAlgorithm::tiledTargets() const {
+ return m_tiledTargets.size();
+}
+
+size_t CAlgorithm::floatingTargets() const {
+ return m_floatingTargets.size();
+}
+
+void CAlgorithm::recalculate() {
+ m_tiled->recalculate();
+ m_floating->recalculate();
+
+ const auto PWORKSPACE = m_space->workspace();
+ const auto PMONITOR = PWORKSPACE->m_monitor;
+
+ if (PWORKSPACE->m_hasFullscreenWindow && PMONITOR) {
+ // massive hack from the fullscreen func
+ const auto PFULLWINDOW = PWORKSPACE->getFullscreenWindow();
+
+ if (PFULLWINDOW) {
+ if (PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) {
+ *PFULLWINDOW->m_realPosition = PMONITOR->m_position;
+ *PFULLWINDOW->m_realSize = PMONITOR->m_size;
+ } else if (PWORKSPACE->m_fullscreenMode == FSMODE_MAXIMIZED)
+ PFULLWINDOW->layoutTarget()->setPositionGlobal(m_space->workArea());
+ }
+
+ return;
+ }
+}
+
+void CAlgorithm::recenter(SP<ITarget> t) {
+ if (t->floating())
+ m_floating->recenter(t);
+}
+
+std::expected<void, std::string> CAlgorithm::layoutMsg(const std::string_view& sv) {
+ if (const auto ret = m_floating->layoutMsg(sv); !ret)
+ return ret;
+ return m_tiled->layoutMsg(sv);
+}
+
+std::optional<Vector2D> CAlgorithm::predictSizeForNewTiledTarget() {
+ return m_tiled->predictSizeForNewTarget();
+}
+
+void CAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
+ if (target->floating())
+ m_floating->resizeTarget(Δ, target, corner);
+ else
+ m_tiled->resizeTarget(Δ, target, corner);
+}
+
+void CAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> target) {
+ if (target->floating())
+ m_floating->moveTarget(Δ, target);
+}
+
+void CAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
+ auto swapFirst = [&a, &b](std::vector<WP<ITarget>>& targets) -> bool {
+ auto ia = std::ranges::find(targets, a);
+ auto ib = std::ranges::find(targets, b);
+
+ if (ia != std::ranges::end(targets) && ib != std::ranges::end(targets)) {
+ std::iter_swap(ia, ib);
+ return true;
+ } else if (ia != std::ranges::end(targets))
+ *ia = b;
+ else if (ib != std::ranges::end(targets))
+ *ib = a;
+
+ return false;
+ };
+
+ if (!swapFirst(m_tiledTargets))
+ swapFirst(m_floatingTargets);
+
+ const WP<IModeAlgorithm> algA = a->floating() ? WP<IModeAlgorithm>(m_floating) : WP<IModeAlgorithm>(m_tiled);
+ const WP<IModeAlgorithm> algB = b->floating() ? WP<IModeAlgorithm>(m_floating) : WP<IModeAlgorithm>(m_tiled);
+
+ algA->swapTargets(a, b);
+ if (algA != algB)
+ algB->swapTargets(b, a);
+}
+
+void CAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
+ if (t->floating())
+ m_floating->moveTargetInDirection(t, dir, silent);
+ else
+ m_tiled->moveTargetInDirection(t, dir, silent);
+}
+
+void CAlgorithm::updateFloatingAlgo(UP<IFloatingAlgorithm>&& algo) {
+ algo->m_parent = m_self;
+
+ for (const auto& t : m_floatingTargets) {
+ const auto TARGET = t.lock();
+ if (!TARGET)
+ continue;
+
+ // Unhide windows when switching layouts to prevent them from being permanently lost
+ const auto WINDOW = TARGET->window();
+ if (WINDOW)
+ WINDOW->setHidden(false);
+
+ m_floating->removeTarget(TARGET);
+ algo->newTarget(TARGET);
+ }
+
+ m_floating = std::move(algo);
+}
+
+void CAlgorithm::updateTiledAlgo(UP<ITiledAlgorithm>&& algo) {
+ algo->m_parent = m_self;
+
+ for (const auto& t : m_tiledTargets) {
+ const auto TARGET = t.lock();
+ if (!TARGET)
+ continue;
+
+ // Unhide windows when switching layouts to prevent them from being permanently lost
+ // This is a safeguard for layouts (including third-party plugins) that use setHidden
+ const auto WINDOW = TARGET->window();
+ if (WINDOW)
+ WINDOW->setHidden(false);
+
+ m_tiled->removeTarget(TARGET);
+ algo->newTarget(TARGET);
+ }
+
+ m_tiled = std::move(algo);
+}
+
+const UP<ITiledAlgorithm>& CAlgorithm::tiledAlgo() const {
+ return m_tiled;
+}
+
+const UP<IFloatingAlgorithm>& CAlgorithm::floatingAlgo() const {
+ return m_floating;
+}
+
+SP<ITarget> CAlgorithm::getNextCandidate(SP<ITarget> old) {
+ if (old->floating()) {
+ // use window history to determine best target
+ for (const auto& w : Desktop::History::windowTracker()->fullHistory() | std::views::reverse) {
+ if (!w->m_workspace || w->m_workspace->m_space != m_space || !w->layoutTarget() || !w->layoutTarget()->space())
+ continue;
+
+ return w->layoutTarget();
+ }
+
+ // no history, fall back
+ } else {
+ // ask the layout
+ const auto CANDIDATE = m_tiled->getNextCandidate(old);
+ if (CANDIDATE)
+ return CANDIDATE;
+
+ // no candidate, fall back
+ }
+
+ // fallback: try to focus anything
+ if (!m_tiledTargets.empty())
+ return m_tiledTargets.back().lock();
+ if (!m_floatingTargets.empty())
+ return m_floatingTargets.back().lock();
+
+ // god damn it, maybe empty?
+ return nullptr;
+}
diff --git a/src/layout/algorithm/Algorithm.hpp b/src/layout/algorithm/Algorithm.hpp
new file mode 100644
index 00000000..3ee26a3c
--- /dev/null
+++ b/src/layout/algorithm/Algorithm.hpp
@@ -0,0 +1,64 @@
+#pragma once
+
+#include "../../helpers/math/Math.hpp"
+#include "../../helpers/math/Direction.hpp"
+#include "../../helpers/memory/Memory.hpp"
+
+#include "../LayoutManager.hpp"
+
+#include <expected>
+#include <optional>
+
+namespace Layout {
+ class ITarget;
+ class IFloatingAlgorithm;
+ class ITiledAlgorithm;
+ class CSpace;
+
+ class CAlgorithm {
+ public:
+ static SP<CAlgorithm> create(UP<ITiledAlgorithm>&& tiled, UP<IFloatingAlgorithm>&& floating, SP<CSpace> space);
+ ~CAlgorithm() = default;
+
+ void addTarget(SP<ITarget> target);
+ void moveTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt, bool reposition = false);
+ void removeTarget(SP<ITarget> target);
+
+ void swapTargets(SP<ITarget> a, SP<ITarget> b);
+ void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
+
+ SP<ITarget> getNextCandidate(SP<ITarget> old);
+
+ void setFloating(SP<ITarget> target, bool floating, bool reposition = false);
+
+ std::expected<void, std::string> layoutMsg(const std::string_view& sv);
+ std::optional<Vector2D> predictSizeForNewTiledTarget();
+
+ void recalculate();
+ void recenter(SP<ITarget> t);
+
+ void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
+ void moveTarget(const Vector2D& Δ, SP<ITarget> target);
+
+ void updateFloatingAlgo(UP<IFloatingAlgorithm>&& algo);
+ void updateTiledAlgo(UP<ITiledAlgorithm>&& algo);
+
+ const UP<ITiledAlgorithm>& tiledAlgo() const;
+ const UP<IFloatingAlgorithm>& floatingAlgo() const;
+
+ SP<CSpace> space() const;
+
+ size_t tiledTargets() const;
+ size_t floatingTargets() const;
+
+ private:
+ CAlgorithm(UP<ITiledAlgorithm>&& tiled, UP<IFloatingAlgorithm>&& floating, SP<CSpace> space);
+
+ UP<ITiledAlgorithm> m_tiled;
+ UP<IFloatingAlgorithm> m_floating;
+ WP<CSpace> m_space;
+ WP<CAlgorithm> m_self;
+
+ std::vector<WP<ITarget>> m_tiledTargets, m_floatingTargets;
+ };
+} \ No newline at end of file
diff --git a/src/layout/algorithm/FloatingAlgorithm.cpp b/src/layout/algorithm/FloatingAlgorithm.cpp
new file mode 100644
index 00000000..058887bf
--- /dev/null
+++ b/src/layout/algorithm/FloatingAlgorithm.cpp
@@ -0,0 +1,18 @@
+#include "FloatingAlgorithm.hpp"
+#include "Algorithm.hpp"
+#include "../space/Space.hpp"
+
+using namespace Layout;
+
+void IFloatingAlgorithm::recalculate() {
+ ;
+}
+
+void IFloatingAlgorithm::recenter(SP<ITarget> t) {
+ const auto LAST = t->lastFloatingSize();
+
+ if (LAST.x <= 5 || LAST.y <= 5)
+ return;
+
+ t->setPositionGlobal({m_parent->space()->workArea().middle() - LAST / 2.F, LAST});
+}
diff --git a/src/layout/algorithm/FloatingAlgorithm.hpp b/src/layout/algorithm/FloatingAlgorithm.hpp
new file mode 100644
index 00000000..2c9ff14b
--- /dev/null
+++ b/src/layout/algorithm/FloatingAlgorithm.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "../../helpers/math/Math.hpp"
+#include "../../helpers/memory/Memory.hpp"
+
+#include "ModeAlgorithm.hpp"
+
+namespace Layout {
+
+ class ITarget;
+ class CAlgorithm;
+
+ class IFloatingAlgorithm : public IModeAlgorithm {
+ public:
+ virtual ~IFloatingAlgorithm() = default;
+
+ // a target is being moved by a delta
+ virtual void moveTarget(const Vector2D& Δ, SP<ITarget> target) = 0;
+
+ virtual void recenter(SP<ITarget> t);
+
+ virtual void recalculate();
+
+ protected:
+ IFloatingAlgorithm() = default;
+
+ WP<CAlgorithm> m_parent;
+
+ friend class Layout::CAlgorithm;
+ };
+} \ No newline at end of file
diff --git a/src/layout/algorithm/ModeAlgorithm.cpp b/src/layout/algorithm/ModeAlgorithm.cpp
new file mode 100644
index 00000000..261c54da
--- /dev/null
+++ b/src/layout/algorithm/ModeAlgorithm.cpp
@@ -0,0 +1,11 @@
+#include "ModeAlgorithm.hpp"
+
+using namespace Layout;
+
+std::expected<void, std::string> IModeAlgorithm::layoutMsg(const std::string_view& sv) {
+ return {};
+}
+
+std::optional<Vector2D> IModeAlgorithm::predictSizeForNewTarget() {
+ return std::nullopt;
+}
diff --git a/src/layout/algorithm/ModeAlgorithm.hpp b/src/layout/algorithm/ModeAlgorithm.hpp
new file mode 100644
index 00000000..90d7ce58
--- /dev/null
+++ b/src/layout/algorithm/ModeAlgorithm.hpp
@@ -0,0 +1,54 @@
+#pragma once
+
+#include "../../helpers/math/Math.hpp"
+#include "../../helpers/math/Direction.hpp"
+#include "../../helpers/memory/Memory.hpp"
+
+#include "../LayoutManager.hpp"
+
+#include <expected>
+
+namespace Layout {
+
+ class ITarget;
+ class CAlgorithm;
+
+ class IModeAlgorithm {
+ public:
+ virtual ~IModeAlgorithm() = default;
+
+ // a completely new target
+ virtual void newTarget(SP<ITarget> target) = 0;
+
+ // a target moved into the algorithm (from another)
+ virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt) = 0;
+
+ // a target removed
+ virtual void removeTarget(SP<ITarget> target) = 0;
+
+ // a target is being resized by a delta. Corner none likely means not interactive
+ virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE) = 0;
+
+ // recalculate layout
+ virtual void recalculate() = 0;
+
+ // swap targets
+ virtual void swapTargets(SP<ITarget> a, SP<ITarget> b) = 0;
+
+ // move a target in a given direction
+ virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) = 0;
+
+ // optional: handle layout messages
+ virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);
+
+ // optional: predict new window's size
+ virtual std::optional<Vector2D> predictSizeForNewTarget();
+
+ protected:
+ IModeAlgorithm() = default;
+
+ WP<CAlgorithm> m_parent;
+
+ friend class Layout::CAlgorithm;
+ };
+} \ No newline at end of file
diff --git a/src/layout/algorithm/TiledAlgorithm.hpp b/src/layout/algorithm/TiledAlgorithm.hpp
new file mode 100644
index 00000000..99d1bd99
--- /dev/null
+++ b/src/layout/algorithm/TiledAlgorithm.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "../../helpers/math/Math.hpp"
+#include "../../helpers/memory/Memory.hpp"
+
+#include "ModeAlgorithm.hpp"
+
+namespace Layout {
+
+ class ITarget;
+ class CAlgorithm;
+
+ class ITiledAlgorithm : public IModeAlgorithm {
+ public:
+ virtual ~ITiledAlgorithm() = default;
+
+ virtual SP<ITarget> getNextCandidate(SP<ITarget> old) = 0;
+
+ protected:
+ ITiledAlgorithm() = default;
+
+ WP<CAlgorithm> m_parent;
+
+ friend class Layout::CAlgorithm;
+ };
+} \ No newline at end of file
diff --git a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp
new file mode 100644
index 00000000..7fb8ec7e
--- /dev/null
+++ b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp
@@ -0,0 +1,223 @@
+#include "DefaultFloatingAlgorithm.hpp"
+
+#include "../../Algorithm.hpp"
+
+#include "../../../target/WindowTarget.hpp"
+#include "../../../space/Space.hpp"
+
+#include "../../../../Compositor.hpp"
+#include "../../../../helpers/Monitor.hpp"
+
+using namespace Layout;
+using namespace Layout::Floating;
+
+constexpr const Vector2D DEFAULT_SIZE = {640, 400};
+
+//
+void CDefaultFloatingAlgorithm::newTarget(SP<ITarget> target) {
+ const auto WORK_AREA = m_parent->space()->workArea(true);
+ const auto DESIRED_GEOM = target->desiredGeometry();
+ const auto MONITOR_POS = m_parent->space()->workspace()->m_monitor->logicalBox().pos();
+
+ CBox windowGeometry;
+
+ if (!DESIRED_GEOM) {
+ switch (DESIRED_GEOM.error()) {
+ case GEOMETRY_INVALID_DESIRED: {
+ // if the desired is invalid, we hide the window.
+ if (target->type() == TARGET_TYPE_WINDOW)
+ dynamicPointerCast<CWindowTarget>(target)->window()->setHidden(true);
+ return;
+ }
+ case GEOMETRY_NO_DESIRED: {
+ // add a default geom
+ windowGeometry = CBox{WORK_AREA.middle() - DEFAULT_SIZE / 2.F, DEFAULT_SIZE};
+ break;
+ }
+ }
+ } else {
+ if (DESIRED_GEOM->pos)
+ windowGeometry = CBox{DESIRED_GEOM->pos.value(), DESIRED_GEOM->size};
+ else
+ windowGeometry = CBox{WORK_AREA.middle() - DESIRED_GEOM->size / 2.F, DESIRED_GEOM->size};
+ }
+
+ bool posOverridden = false;
+
+ if (target->window() && target->window()->m_firstMap) {
+ const auto WINDOW = target->window();
+
+ // set this here so that expressions can use it. This could be wrong of course.
+ WINDOW->m_realSize->setValueAndWarp(DESIRED_GEOM ? DESIRED_GEOM->size : DEFAULT_SIZE);
+
+ if (!WINDOW->m_ruleApplicator->static_.size.empty()) {
+ const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.size);
+ if (!COMPUTED)
+ Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.size);
+ else {
+ windowGeometry.w = COMPUTED->x;
+ windowGeometry.h = COMPUTED->y;
+
+ // update for pos to work with size.
+ WINDOW->m_realPosition->setValueAndWarp(*COMPUTED);
+ }
+ }
+
+ if (!WINDOW->m_ruleApplicator->static_.position.empty()) {
+ const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.position);
+ if (!COMPUTED)
+ Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.position);
+ else {
+ windowGeometry.x = COMPUTED->x + MONITOR_POS.x;
+ windowGeometry.y = COMPUTED->y + MONITOR_POS.y;
+ posOverridden = true;
+ }
+ }
+
+ if (WINDOW->m_ruleApplicator->static_.center.value_or(false)) {
+ const auto POS = WORK_AREA.middle() - windowGeometry.size() / 2.f;
+ windowGeometry.x = POS.x;
+ windowGeometry.y = POS.y;
+ posOverridden = true;
+ }
+ } else if (target->lastFloatingSize().x > 5 && target->lastFloatingSize().y > 5) {
+ windowGeometry.w = target->lastFloatingSize().x;
+ windowGeometry.h = target->lastFloatingSize().y;
+ }
+
+ if (!posOverridden && (!DESIRED_GEOM || !DESIRED_GEOM->pos))
+ windowGeometry = CBox{WORK_AREA.middle() - windowGeometry.size() / 2.F, windowGeometry.size()};
+
+ if (posOverridden || WORK_AREA.containsPoint(windowGeometry.middle()))
+ target->setPositionGlobal(windowGeometry);
+ else {
+ const auto POS = WORK_AREA.middle() - windowGeometry.size() / 2.f;
+ windowGeometry.x = POS.x;
+ windowGeometry.y = POS.y;
+
+ target->setPositionGlobal(windowGeometry);
+ }
+
+ // TODO: not very OOP, is it?
+ if (const auto WTARGET = dynamicPointerCast<CWindowTarget>(target); WTARGET) {
+ static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling");
+
+ const auto PWINDOW = WTARGET->window();
+ const auto PMONITOR = WTARGET->space()->workspace()->m_monitor.lock();
+
+ if (*PXWLFORCESCALEZERO && PWINDOW->m_isX11)
+ *PWINDOW->m_realSize = PWINDOW->m_realSize->goal() / PMONITOR->m_scale;
+
+ if (PWINDOW->m_X11DoesntWantBorders || (PWINDOW->m_isX11 && PWINDOW->isX11OverrideRedirect())) {
+ PWINDOW->m_realPosition->warp();
+ PWINDOW->m_realSize->warp();
+ }
+
+ if (!PWINDOW->isX11OverrideRedirect())
+ g_pCompositor->changeWindowZOrder(PWINDOW, true);
+ else {
+ PWINDOW->m_pendingReportedSize = PWINDOW->m_realSize->goal();
+ PWINDOW->m_reportedSize = PWINDOW->m_pendingReportedSize;
+ }
+ }
+}
+
+void CDefaultFloatingAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {
+ auto LAST_SIZE = target->lastFloatingSize();
+ const auto CURRENT_SIZE = target->position().size();
+
+ // ignore positioning a dragged target
+ if (g_layoutManager->dragController()->target() == target)
+ return;
+
+ if (LAST_SIZE.x < 5 || LAST_SIZE.y < 5) {
+ const auto DESIRED = target->desiredGeometry();
+ LAST_SIZE = DESIRED ? DESIRED->size : DEFAULT_SIZE;
+ }
+
+ if (target->wasTiling()) {
+ // Avoid floating toggles that don't change size, they aren't easily visible to the user
+ if (std::abs(LAST_SIZE.x - CURRENT_SIZE.x) < 5 && std::abs(LAST_SIZE.y - CURRENT_SIZE.y) < 5)
+ LAST_SIZE += Vector2D{10, 10};
+
+ // calculate new position
+ const auto OLD_CENTER = target->position().middle();
+
+ // put around the current center, fit in workArea
+ target->setPositionGlobal(fitBoxInWorkArea(CBox{OLD_CENTER - LAST_SIZE / 2.F, LAST_SIZE}, target));
+
+ } else {
+ // calculate new position
+ const auto THIS_MON_POS = m_parent->space()->workspace()->m_monitor->m_position;
+ const auto OLD_POS = target->position().pos();
+ const auto MON_FROM_OLD = g_pCompositor->getMonitorFromVector(OLD_POS);
+ const auto NEW_POS = MON_FROM_OLD ? OLD_POS - MON_FROM_OLD->m_position + THIS_MON_POS : OLD_POS;
+
+ // put around the current center, fit in workArea
+ target->setPositionGlobal(fitBoxInWorkArea(CBox{NEW_POS, LAST_SIZE}, target));
+ }
+}
+
+CBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP<ITarget> t) {
+ const auto WORK_AREA = m_parent->space()->workArea(true);
+ const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS) : SBoxExtents{};
+ CBox targetBox = box.copy().addExtents(EXTENTS);
+
+ targetBox.x = std::max(targetBox.x, WORK_AREA.x);
+ targetBox.y = std::max(targetBox.y, WORK_AREA.y);
+
+ if (targetBox.x + targetBox.w > WORK_AREA.x + WORK_AREA.w)
+ targetBox.x = WORK_AREA.x + WORK_AREA.w - targetBox.w;
+
+ if (targetBox.y + targetBox.h > WORK_AREA.y + WORK_AREA.h)
+ targetBox.y = WORK_AREA.y + WORK_AREA.h - targetBox.h;
+
+ return targetBox.addExtents(SBoxExtents{.topLeft = -EXTENTS.topLeft, .bottomRight = -EXTENTS.bottomRight});
+}
+
+void CDefaultFloatingAlgorithm::removeTarget(SP<ITarget> target) {
+ target->rememberFloatingSize(target->position().size());
+}
+
+void CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
+ auto pos = target->position();
+ pos.w += Δ.x;
+ pos.h += Δ.y;
+ pos.translate(-Δ / 2.F);
+ target->setPositionGlobal(pos);
+
+ if (g_layoutManager->dragController()->target() == target)
+ target->warpPositionSize();
+}
+
+void CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> target) {
+ auto pos = target->position();
+ pos.translate(Δ);
+ target->setPositionGlobal(pos);
+
+ if (g_layoutManager->dragController()->target() == target)
+ target->warpPositionSize();
+}
+
+void CDefaultFloatingAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
+ auto posABackup = a->position();
+ a->setPositionGlobal(b->position());
+ b->setPositionGlobal(posABackup);
+}
+
+void CDefaultFloatingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
+ auto pos = t->position();
+ auto work = m_parent->space()->workArea(true);
+
+ const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS) : SBoxExtents{};
+
+ switch (dir) {
+ case Math::DIRECTION_LEFT: pos.x = work.x + EXTENTS.topLeft.x; break;
+ case Math::DIRECTION_RIGHT: pos.x = work.x + work.w - pos.w - EXTENTS.bottomRight.x; break;
+ case Math::DIRECTION_UP: pos.y = work.y + EXTENTS.topLeft.y; break;
+ case Math::DIRECTION_DOWN: pos.y = work.y + work.h - pos.h - EXTENTS.bottomRight.y; break;
+ default: Log::logger->log(Log::ERR, "Invalid direction in CDefaultFloatingAlgorithm::moveTargetInDirection"); break;
+ }
+
+ t->setPositionGlobal(pos);
+}
diff --git a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp
new file mode 100644
index 00000000..ef94e371
--- /dev/null
+++ b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp
@@ -0,0 +1,26 @@
+#include "../../FloatingAlgorithm.hpp"
+
+namespace Layout {
+ class CAlgorithm;
+}
+
+namespace Layout::Floating {
+ class CDefaultFloatingAlgorithm : public IFloatingAlgorithm {
+ public:
+ CDefaultFloatingAlgorithm() = default;
+ virtual ~CDefaultFloatingAlgorithm() = default;
+
+ virtual void newTarget(SP<ITarget> target);
+ virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
+ virtual void removeTarget(SP<ITarget> target);
+
+ virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
+ virtual void moveTarget(const Vector2D& Δ, SP<ITarget> target);
+
+ virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
+ virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
+
+ private:
+ CBox fitBoxInWorkArea(const CBox& box, SP<ITarget> t);
+ };
+}; \ No newline at end of file
diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp
new file mode 100644
index 00000000..5b90bb46
--- /dev/null
+++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp
@@ -0,0 +1,772 @@
+#include "DwindleAlgorithm.hpp"
+
+#include "../../Algorithm.hpp"
+#include "../../../space/Space.hpp"
+#include "../../../target/WindowTarget.hpp"
+#include "../../../LayoutManager.hpp"
+
+#include "../../../../config/ConfigValue.hpp"
+#include "../../../../desktop/state/FocusState.hpp"
+#include "../../../../helpers/Monitor.hpp"
+#include "../../../../Compositor.hpp"
+
+#include <hyprutils/utils/ScopeGuard.hpp>
+
+using namespace Layout;
+using namespace Layout::Tiled;
+
+struct Layout::Tiled::SDwindleNodeData {
+ WP<SDwindleNodeData> pParent;
+ bool isNode = false;
+ WP<ITarget> pTarget;
+ std::array<WP<SDwindleNodeData>, 2> children = {};
+ WP<SDwindleNodeData> self;
+ bool splitTop = false; // for preserve_split
+ CBox box = {0};
+ float splitRatio = 1.f;
+ bool valid = true;
+ bool ignoreFullscreenChecks = false;
+
+ // For list lookup
+ bool operator==(const SDwindleNodeData& rhs) const {
+ return pTarget.lock() == rhs.pTarget.lock() && box == rhs.box && pParent == rhs.pParent && children[0] == rhs.children[0] && children[1] == rhs.children[1];
+ }
+
+ void recalcSizePosRecursive(bool force = false, bool horizontalOverride = false, bool verticalOverride = false) {
+ if (children[0]) {
+ static auto PSMARTSPLIT = CConfigValue<Hyprlang::INT>("dwindle:smart_split");
+ static auto PPRESERVESPLIT = CConfigValue<Hyprlang::INT>("dwindle:preserve_split");
+ static auto PFLMULT = CConfigValue<Hyprlang::FLOAT>("dwindle:split_width_multiplier");
+
+ if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0)
+ splitTop = box.h * *PFLMULT > box.w;
+
+ if (verticalOverride)
+ splitTop = true;
+ else if (horizontalOverride)
+ splitTop = false;
+
+ const auto SPLITSIDE = !splitTop;
+
+ if (SPLITSIDE) {
+ // split left/right
+ const float FIRSTSIZE = box.w / 2.0 * splitRatio;
+ children[0]->box = CBox{box.x, box.y, FIRSTSIZE, box.h}.noNegativeSize();
+ children[1]->box = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}.noNegativeSize();
+ } else {
+ // split top/bottom
+ const float FIRSTSIZE = box.h / 2.0 * splitRatio;
+ children[0]->box = CBox{box.x, box.y, box.w, FIRSTSIZE}.noNegativeSize();
+ children[1]->box = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}.noNegativeSize();
+ }
+
+ children[0]->recalcSizePosRecursive(force);
+ children[1]->recalcSizePosRecursive(force);
+ } else
+ pTarget->setPositionGlobal(box);
+ }
+};
+
+void CDwindleAlgorithm::newTarget(SP<ITarget> target) {
+ addTarget(target);
+}
+
+void CDwindleAlgorithm::addTarget(SP<ITarget> target, bool newTarget) {
+ const auto WORK_AREA = m_parent->space()->workArea();
+
+ const auto PNODE = m_dwindleNodesData.emplace_back(makeShared<SDwindleNodeData>());
+ PNODE->self = PNODE;
+
+ const auto PMONITOR = m_parent->space()->workspace()->m_monitor;
+ const auto PWORKSPACE = m_parent->space()->workspace();
+
+ static auto PUSEACTIVE = CConfigValue<Hyprlang::INT>("dwindle:use_active_for_splits");
+ static auto PDEFAULTSPLIT = CConfigValue<Hyprlang::FLOAT>("dwindle:default_split_ratio");
+
+ // Populate the node with our window's data
+ PNODE->pTarget = target;
+ PNODE->isNode = false;
+
+ SP<SDwindleNodeData> OPENINGON;
+
+ const auto MOUSECOORDS = m_overrideFocalPoint.value_or(g_pInputManager->getMouseCoordsInternal());
+ const auto ACTIVE_MON = Desktop::focusState()->monitor();
+
+ if ((PWORKSPACE == ACTIVE_MON->m_activeWorkspace || (PWORKSPACE->m_isSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace)) && !*PUSEACTIVE) {
+ OPENINGON = getNodeFromWindow(
+ g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::SKIP_FULLSCREEN_PRIORITY));
+
+ if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, ACTIVE_MON))
+ OPENINGON = getClosestNode(MOUSECOORDS);
+
+ } else if (*PUSEACTIVE) {
+ if (Desktop::focusState()->window() && !Desktop::focusState()->window()->m_isFloating && Desktop::focusState()->window() != target->window() &&
+ Desktop::focusState()->window()->m_workspace == PWORKSPACE && Desktop::focusState()->window()->m_isMapped) {
+ OPENINGON = getNodeFromWindow(Desktop::focusState()->window());
+ } else {
+ OPENINGON = getNodeFromWindow(g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS));
+ }
+
+ if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, ACTIVE_MON))
+ OPENINGON = getClosestNode(MOUSECOORDS);
+
+ } else
+ OPENINGON = getFirstNode();
+
+ // first, check if OPENINGON isn't too big.
+ const auto PREDSIZEMAX = OPENINGON ? Vector2D(OPENINGON->box.w, OPENINGON->box.h) : PMONITOR->m_size;
+ if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PREDSIZEMAX.x || MAXSIZE.y < PREDSIZEMAX.y) {
+ // we can't continue. make it floating.
+ std::erase(m_dwindleNodesData, PNODE);
+ m_parent->setFloating(target, true, true);
+ return;
+ }
+
+ // last fail-safe to avoid duplicate fullscreens
+ if ((!OPENINGON || OPENINGON->pTarget.lock() == target) && getNodes() > 1) {
+ for (auto& node : m_dwindleNodesData) {
+ if (node->pTarget.lock() && node->pTarget.lock() != target) {
+ OPENINGON = node;
+ break;
+ }
+ }
+ }
+
+ // if it's the first, it's easy. Make it fullscreen.
+ if (!OPENINGON || OPENINGON->pTarget.lock() == target) {
+ PNODE->box = WORK_AREA;
+ PNODE->pTarget->setPositionGlobal(PNODE->box);
+ return;
+ }
+
+ // get the node under our cursor
+
+ const auto NEWPARENT = m_dwindleNodesData.emplace_back(makeShared<SDwindleNodeData>());
+
+ // make the parent have the OPENINGON's stats
+ NEWPARENT->box = OPENINGON->box;
+ NEWPARENT->pParent = OPENINGON->pParent;
+ NEWPARENT->isNode = true; // it is a node
+ NEWPARENT->splitRatio = std::clamp(*PDEFAULTSPLIT, 0.1F, 1.9F);
+
+ static auto PWIDTHMULTIPLIER = CConfigValue<Hyprlang::FLOAT>("dwindle:split_width_multiplier");
+
+ // if cursor over first child, make it first, etc
+ const auto SIDEBYSIDE = NEWPARENT->box.w > NEWPARENT->box.h * *PWIDTHMULTIPLIER;
+ NEWPARENT->splitTop = !SIDEBYSIDE;
+
+ static auto PFORCESPLIT = CConfigValue<Hyprlang::INT>("dwindle:force_split");
+ static auto PERMANENTDIRECTIONOVERRIDE = CConfigValue<Hyprlang::INT>("dwindle:permanent_direction_override");
+ static auto PSMARTSPLIT = CConfigValue<Hyprlang::INT>("dwindle:smart_split");
+ static auto PSPLITBIAS = CConfigValue<Hyprlang::INT>("dwindle:split_bias");
+
+ bool horizontalOverride = false;
+ bool verticalOverride = false;
+
+ // let user select position -> top, right, bottom, left
+ if (m_overrideDirection != Math::DIRECTION_DEFAULT) {
+
+ // this is horizontal
+ if (m_overrideDirection % 2 == 0)
+ verticalOverride = true;
+ else
+ horizontalOverride = true;
+
+ // 0 -> top and left | 1,2 -> right and bottom
+ if (m_overrideDirection % 3 == 0) {
+ NEWPARENT->children[1] = OPENINGON;
+ NEWPARENT->children[0] = PNODE;
+ } else {
+ NEWPARENT->children[0] = OPENINGON;
+ NEWPARENT->children[1] = PNODE;
+ }
+
+ // whether or not the override persists after opening one window
+ if (*PERMANENTDIRECTIONOVERRIDE == 0)
+ m_overrideDirection = Math::DIRECTION_DEFAULT;
+ } else if (*PSMARTSPLIT == 1) {
+ const auto PARENT_CENTER = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2;
+ const auto PARENT_PROPORTIONS = NEWPARENT->box.h / NEWPARENT->box.w;
+ const auto DELTA = MOUSECOORDS - PARENT_CENTER;
+ const auto DELTA_SLOPE = DELTA.y / DELTA.x;
+
+ if (abs(DELTA_SLOPE) < PARENT_PROPORTIONS) {
+ if (DELTA.x > 0) {
+ // right
+ NEWPARENT->splitTop = false;
+ NEWPARENT->children[0] = OPENINGON;
+ NEWPARENT->children[1] = PNODE;
+ } else {
+ // left
+ NEWPARENT->splitTop = false;
+ NEWPARENT->children[0] = PNODE;
+ NEWPARENT->children[1] = OPENINGON;
+ }
+ } else {
+ if (DELTA.y > 0) {
+ // bottom
+ NEWPARENT->splitTop = true;
+ NEWPARENT->children[0] = OPENINGON;
+ NEWPARENT->children[1] = PNODE;
+ } else {
+ // top
+ NEWPARENT->splitTop = true;
+ NEWPARENT->children[0] = PNODE;
+ NEWPARENT->children[1] = OPENINGON;
+ }
+ }
+ } else if (*PFORCESPLIT == 0 || !newTarget) {
+ if ((SIDEBYSIDE &&
+ VECINRECT(MOUSECOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y + NEWPARENT->box.h)) ||
+ (!SIDEBYSIDE &&
+ VECINRECT(MOUSECOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w, NEWPARENT->box.y + NEWPARENT->box.h / 2.f))) {
+ // we are hovering over the first node, make PNODE first.
+ NEWPARENT->children[1] = OPENINGON;
+ NEWPARENT->children[0] = PNODE;
+ } else {
+ // we are hovering over the second node, make PNODE second.
+ NEWPARENT->children[0] = OPENINGON;
+ NEWPARENT->children[1] = PNODE;
+ }
+ } else {
+ if (*PFORCESPLIT == 1) {
+ NEWPARENT->children[1] = OPENINGON;
+ NEWPARENT->children[0] = PNODE;
+ } else {
+ NEWPARENT->children[0] = OPENINGON;
+ NEWPARENT->children[1] = PNODE;
+ }
+ }
+
+ // split in favor of a specific window
+ if (*PSPLITBIAS && NEWPARENT->children[0] == PNODE)
+ NEWPARENT->splitRatio = 2.f - NEWPARENT->splitRatio;
+
+ // and update the previous parent if it exists
+ if (OPENINGON->pParent) {
+ if (OPENINGON->pParent->children[0] == OPENINGON) {
+ OPENINGON->pParent->children[0] = NEWPARENT;
+ } else {
+ OPENINGON->pParent->children[1] = NEWPARENT;
+ }
+ }
+
+ // Update the children
+ if (!verticalOverride && (NEWPARENT->box.w * *PWIDTHMULTIPLIER > NEWPARENT->box.h || horizontalOverride)) {
+ // split left/right -> forced
+ OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)};
+ PNODE->box = {Vector2D(NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)};
+ } else {
+ // split top/bottom
+ OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)};
+ PNODE->box = {Vector2D(NEWPARENT->box.x, NEWPARENT->box.y + NEWPARENT->box.h / 2.f), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)};
+ }
+
+ OPENINGON->pParent = NEWPARENT;
+ PNODE->pParent = NEWPARENT;
+
+ NEWPARENT->recalcSizePosRecursive(false, horizontalOverride, verticalOverride);
+
+ calculateWorkspace();
+}
+
+void CDwindleAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {
+ m_overrideFocalPoint = focalPoint;
+ addTarget(target, false);
+ m_overrideFocalPoint.reset();
+}
+
+void CDwindleAlgorithm::removeTarget(SP<ITarget> target) {
+ const auto PNODE = getNodeFromTarget(target);
+
+ if (!PNODE) {
+ Log::logger->log(Log::ERR, "onWindowRemovedTiling node null?");
+ return;
+ }
+
+ if (target->fullscreenMode() != FSMODE_NONE)
+ g_pCompositor->setWindowFullscreenInternal(target->window(), FSMODE_NONE);
+
+ const auto PPARENT = PNODE->pParent;
+
+ if (!PPARENT) {
+ Log::logger->log(Log::DEBUG, "Removing last node (dwindle)");
+ std::erase(m_dwindleNodesData, PNODE);
+ return;
+ }
+
+ const auto PSIBLING = PPARENT->children[0] == PNODE ? PPARENT->children[1] : PPARENT->children[0];
+
+ PSIBLING->pParent = PPARENT->pParent;
+
+ if (PPARENT->pParent != nullptr) {
+ if (PPARENT->pParent->children[0] == PPARENT)
+ PPARENT->pParent->children[0] = PSIBLING;
+ else
+ PPARENT->pParent->children[1] = PSIBLING;
+ }
+
+ PPARENT->valid = false;
+ PNODE->valid = false;
+
+ std::erase(m_dwindleNodesData, PPARENT);
+ std::erase(m_dwindleNodesData, PNODE);
+
+ recalculate();
+}
+
+void CDwindleAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
+ if (!validMapped(target->window()))
+ return;
+
+ const auto PNODE = getNodeFromTarget(target);
+
+ if (!PNODE)
+ return;
+
+ static auto PANIMATE = CConfigValue<Hyprlang::INT>("misc:animate_manual_resizes");
+ static auto PSMARTRESIZING = CConfigValue<Hyprlang::INT>("dwindle:smart_resizing");
+
+ // get some data about our window
+ const auto PMONITOR = m_parent->space()->workspace()->m_monitor;
+ const auto MONITOR_WORKAREA = PMONITOR->logicalBoxMinusReserved();
+ const auto BOX = target->position();
+ const bool DISPLAYLEFT = STICKS(BOX.x, MONITOR_WORKAREA.x);
+ const bool DISPLAYRIGHT = STICKS(BOX.x + BOX.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);
+ const bool DISPLAYTOP = STICKS(BOX.y, MONITOR_WORKAREA.y);
+ const bool DISPLAYBOTTOM = STICKS(BOX.y + BOX.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h);
+
+ // construct allowed movement
+ Vector2D allowedMovement = Δ;
+ if (DISPLAYLEFT && DISPLAYRIGHT)
+ allowedMovement.x = 0;
+
+ if (DISPLAYBOTTOM && DISPLAYTOP)
+ allowedMovement.y = 0;
+
+ if (*PSMARTRESIZING == 1) {
+ // Identify inner and outer nodes for both directions
+ SP<SDwindleNodeData> PVOUTER = nullptr;
+ SP<SDwindleNodeData> PVINNER = nullptr;
+ SP<SDwindleNodeData> PHOUTER = nullptr;
+ SP<SDwindleNodeData> PHINNER = nullptr;
+
+ const auto LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT || DISPLAYRIGHT;
+ const auto TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || DISPLAYBOTTOM;
+ const auto RIGHT = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || DISPLAYLEFT;
+ const auto BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || DISPLAYTOP;
+ const auto NONE = corner == CORNER_NONE;
+
+ for (auto PCURRENT = PNODE; PCURRENT && PCURRENT->pParent; PCURRENT = PCURRENT->pParent.lock()) {
+ const auto PPARENT = PCURRENT->pParent;
+
+ if (!PVOUTER && PPARENT->splitTop && (NONE || (TOP && PPARENT->children[1] == PCURRENT) || (BOTTOM && PPARENT->children[0] == PCURRENT)))
+ PVOUTER = PCURRENT;
+ else if (!PVOUTER && !PVINNER && PPARENT->splitTop)
+ PVINNER = PCURRENT;
+ else if (!PHOUTER && !PPARENT->splitTop && (NONE || (LEFT && PPARENT->children[1] == PCURRENT) || (RIGHT && PPARENT->children[0] == PCURRENT)))
+ PHOUTER = PCURRENT;
+ else if (!PHOUTER && !PHINNER && !PPARENT->splitTop)
+ PHINNER = PCURRENT;
+
+ if (PVOUTER && PHOUTER)
+ break;
+ }
+
+ if (PHOUTER) {
+ PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9);
+
+ if (PHINNER) {
+ const auto ORIGINAL = PHINNER->box.w;
+ PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
+ if (PHINNER->pParent->children[0] == PHINNER)
+ PHINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9);
+ else
+ PHINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9);
+ PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
+ } else
+ PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
+ }
+
+ if (PVOUTER) {
+ PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9);
+
+ if (PVINNER) {
+ const auto ORIGINAL = PVINNER->box.h;
+ PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
+ if (PVINNER->pParent->children[0] == PVINNER)
+ PVINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9);
+ else
+ PVINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9);
+ PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
+ } else
+ PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
+ }
+ } else {
+ // get the correct containers to apply splitratio to
+ const auto PPARENT = PNODE->pParent;
+
+ if (!PPARENT)
+ return; // the only window on a workspace, ignore
+
+ const bool PARENTSIDEBYSIDE = !PPARENT->splitTop;
+
+ // Get the parent's parent
+ auto PPARENT2 = PPARENT->pParent;
+
+ Hyprutils::Utils::CScopeGuard x([target, this] {
+ // snap all windows, don't animate resizes if they are manual
+ if (target == g_layoutManager->dragController()->target()) {
+ for (const auto& w : m_dwindleNodesData) {
+ if (w->isNode)
+ continue;
+
+ w->pTarget->warpPositionSize();
+ }
+ }
+ });
+
+ // No parent means we have only 2 windows, and thus one axis of freedom
+ if (!PPARENT2) {
+ if (PARENTSIDEBYSIDE) {
+ allowedMovement.x *= 2.f / PPARENT->box.w;
+ PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9);
+ PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
+ } else {
+ allowedMovement.y *= 2.f / PPARENT->box.h;
+ PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9);
+ PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
+ }
+
+ return;
+ }
+
+ // Get first parent with other split
+ while (PPARENT2 && PPARENT2->splitTop == !PARENTSIDEBYSIDE)
+ PPARENT2 = PPARENT2->pParent;
+
+ // no parent, one axis of freedom
+ if (!PPARENT2) {
+ if (PARENTSIDEBYSIDE) {
+ allowedMovement.x *= 2.f / PPARENT->box.w;
+ PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9);
+ PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
+ } else {
+ allowedMovement.y *= 2.f / PPARENT->box.h;
+ PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9);
+ PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
+ }
+
+ return;
+ }
+
+ // 2 axes of freedom
+ const auto SIDECONTAINER = PARENTSIDEBYSIDE ? PPARENT : PPARENT2;
+ const auto TOPCONTAINER = PARENTSIDEBYSIDE ? PPARENT2 : PPARENT;
+
+ allowedMovement.x *= 2.f / SIDECONTAINER->box.w;
+ allowedMovement.y *= 2.f / TOPCONTAINER->box.h;
+
+ SIDECONTAINER->splitRatio = std::clamp(SIDECONTAINER->splitRatio + allowedMovement.x, 0.1, 1.9);
+ TOPCONTAINER->splitRatio = std::clamp(TOPCONTAINER->splitRatio + allowedMovement.y, 0.1, 1.9);
+ SIDECONTAINER->recalcSizePosRecursive(*PANIMATE == 0);
+ TOPCONTAINER->recalcSizePosRecursive(*PANIMATE == 0);
+ }
+
+ // snap all windows, don't animate resizes if they are manual
+ if (target == g_layoutManager->dragController()->target()) {
+ for (const auto& w : m_dwindleNodesData) {
+ if (w->isNode)
+ continue;
+
+ w->pTarget->warpPositionSize();
+ }
+ }
+}
+
+SP<ITarget> CDwindleAlgorithm::getNextCandidate(SP<ITarget> old) {
+ const auto MIDDLE = old->position().middle();
+
+ if (const auto NODE = getClosestNode(MIDDLE); NODE)
+ return NODE->pTarget.lock();
+
+ if (const auto NODE = getFirstNode(); NODE)
+ return NODE->pTarget.lock();
+
+ return nullptr;
+}
+
+void CDwindleAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
+ auto nodeA = getNodeFromTarget(a);
+ auto nodeB = getNodeFromTarget(b);
+
+ if (nodeA)
+ nodeA->pTarget = b;
+ if (nodeB)
+ nodeB->pTarget = a;
+}
+
+void CDwindleAlgorithm::recalculate() {
+ calculateWorkspace();
+}
+
+std::optional<Vector2D> CDwindleAlgorithm::predictSizeForNewTarget() {
+ // get window candidate
+ PHLWINDOW candidate = Desktop::focusState()->window();
+
+ if (!candidate || candidate->m_workspace != m_parent->space()->workspace())
+ candidate = m_parent->space()->workspace()->getFirstWindow();
+
+ // create a fake node
+ SDwindleNodeData node;
+
+ if (!candidate)
+ return Desktop::focusState()->monitor()->m_size;
+ else {
+ const auto PNODE = getNodeFromWindow(candidate);
+
+ if (!PNODE)
+ return {};
+
+ node = *PNODE;
+ node.pTarget.reset();
+
+ CBox box = PNODE->box;
+
+ static auto PFLMULT = CConfigValue<Hyprlang::FLOAT>("dwindle:split_width_multiplier");
+
+ bool splitTop = box.h * *PFLMULT > box.w;
+
+ const auto SPLITSIDE = !splitTop;
+
+ if (SPLITSIDE)
+ node.box = {{}, {box.w / 2.0, box.h}};
+ else
+ node.box = {{}, {box.w, box.h / 2.0}};
+
+ // TODO: make this better and more accurate
+
+ return node.box.size();
+ }
+
+ return {};
+}
+
+void CDwindleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
+ const auto PNODE = getNodeFromTarget(t);
+ const Vector2D originalPos = t->position().middle();
+
+ if (!PNODE || !t->window())
+ return;
+
+ Vector2D focalPoint;
+
+ const auto WINDOWIDEALBB =
+ t->fullscreenMode() != FSMODE_NONE ? m_parent->space()->workspace()->m_monitor->logicalBox() : t->window()->getWindowIdealBoundingBoxIgnoreReserved();
+
+ switch (dir) {
+ case Math::DIRECTION_UP: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, -1.0}; break;
+ case Math::DIRECTION_DOWN: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, WINDOWIDEALBB.size().y + 1.0}; break;
+ case Math::DIRECTION_LEFT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{-1.0, WINDOWIDEALBB.size().y / 2.0}; break;
+ case Math::DIRECTION_RIGHT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x + 1.0, WINDOWIDEALBB.size().y / 2.0}; break;
+ default: return;
+ }
+
+ t->window()->setAnimationsToMove();
+
+ removeTarget(t);
+
+ const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(focalPoint);
+
+ if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor) {
+ // move with a focal point
+
+ if (PMONITORFOCAL->m_activeWorkspace)
+ t->assignToSpace(PMONITORFOCAL->m_activeWorkspace->m_space);
+
+ return;
+ }
+
+ movedTarget(t, focalPoint);
+
+ // restore focus to the previous position
+ if (silent) {
+ const auto PNODETOFOCUS = getClosestNode(originalPos);
+ if (PNODETOFOCUS && PNODETOFOCUS->pTarget)
+ Desktop::focusState()->fullWindowFocus(PNODETOFOCUS->pTarget->window(), Desktop::FOCUS_REASON_KEYBIND);
+ }
+}
+
+// --------- internal --------- //
+
+void CDwindleAlgorithm::calculateWorkspace() {
+ const auto PWORKSPACE = m_parent->space()->workspace();
+ const auto PMONITOR = PWORKSPACE->m_monitor;
+
+ if (!PMONITOR || PWORKSPACE->m_hasFullscreenWindow)
+ return;
+
+ const auto TOPNODE = getMasterNode();
+
+ if (TOPNODE) {
+ TOPNODE->box = m_parent->space()->workArea();
+ TOPNODE->recalcSizePosRecursive();
+ }
+}
+
+SP<SDwindleNodeData> CDwindleAlgorithm::getNodeFromTarget(SP<ITarget> t) {
+ for (const auto& n : m_dwindleNodesData) {
+ if (n->pTarget == t)
+ return n;
+ }
+
+ return nullptr;
+}
+
+SP<SDwindleNodeData> CDwindleAlgorithm::getNodeFromWindow(PHLWINDOW w) {
+ return w ? getNodeFromTarget(w->layoutTarget()) : nullptr;
+}
+
+int CDwindleAlgorithm::getNodes() {
+ return m_dwindleNodesData.size();
+}
+
+SP<SDwindleNodeData> CDwindleAlgorithm::getFirstNode() {
+ return m_dwindleNodesData.empty() ? nullptr : m_dwindleNodesData.at(0);
+}
+
+SP<SDwindleNodeData> CDwindleAlgorithm::getClosestNode(const Vector2D& point) {
+ SP<SDwindleNodeData> res = nullptr;
+ double distClosest = -1;
+ for (auto& n : m_dwindleNodesData) {
+ if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) {
+ auto distAnother = vecToRectDistanceSquared(point, n->box.pos(), n->box.pos() + n->box.size());
+ if (!res || distAnother < distClosest) {
+ res = n;
+ distClosest = distAnother;
+ }
+ }
+ }
+ return res;
+}
+
+SP<SDwindleNodeData> CDwindleAlgorithm::getMasterNode() {
+ for (auto& n : m_dwindleNodesData) {
+ if (!n->pParent)
+ return n;
+ }
+ return nullptr;
+}
+
+std::expected<void, std::string> CDwindleAlgorithm::layoutMsg(const std::string_view& sv) {
+ const auto ARGS = CVarList2(std::string{sv}, 0, ' ');
+
+ const auto CURRENT_NODE = getNodeFromWindow(Desktop::focusState()->window());
+
+ if (ARGS[0] == "togglesplit") {
+ if (CURRENT_NODE)
+ toggleSplit(CURRENT_NODE);
+ } else if (ARGS[0] == "swapsplit") {
+ if (CURRENT_NODE)
+ swapSplit(CURRENT_NODE);
+ } else if (ARGS[0] == "movetoroot") {
+ auto node = CURRENT_NODE;
+ if (!ARGS[1].empty()) {
+ auto w = g_pCompositor->getWindowByRegex(std::string{ARGS[1]});
+ if (w)
+ node = getNodeFromWindow(w);
+ }
+
+ const auto STABLE = ARGS[2].empty() || ARGS[2] != "unstable";
+ moveToRoot(node, STABLE);
+ } else if (ARGS[0] == "preselect") {
+ auto direction = ARGS[1];
+
+ if (direction.empty()) {
+ Log::logger->log(Log::ERR, "Expected direction for preselect");
+ return std::unexpected("No direction for preselect");
+ }
+
+ switch (direction.front()) {
+ case 'u':
+ case 't': {
+ m_overrideDirection = Math::DIRECTION_UP;
+ break;
+ }
+ case 'd':
+ case 'b': {
+ m_overrideDirection = Math::DIRECTION_DOWN;
+ break;
+ }
+ case 'r': {
+ m_overrideDirection = Math::DIRECTION_RIGHT;
+ break;
+ }
+ case 'l': {
+ m_overrideDirection = Math::DIRECTION_LEFT;
+ break;
+ }
+ default: {
+ // any other character resets the focus direction
+ // needed for the persistent mode
+ m_overrideDirection = Math::DIRECTION_DEFAULT;
+ break;
+ }
+ }
+ }
+
+ return {};
+}
+
+void CDwindleAlgorithm::toggleSplit(SP<SDwindleNodeData> x) {
+ if (!x || !x->pParent)
+ return;
+
+ if (x->pTarget->fullscreenMode() != FSMODE_NONE)
+ return;
+
+ x->pParent->splitTop = !x->pParent->splitTop;
+
+ x->pParent->recalcSizePosRecursive();
+}
+
+void CDwindleAlgorithm::swapSplit(SP<SDwindleNodeData> x) {
+ if (x->pTarget->fullscreenMode() != FSMODE_NONE)
+ return;
+
+ std::swap(x->pParent->children[0], x->pParent->children[1]);
+
+ x->pParent->recalcSizePosRecursive();
+}
+
+void CDwindleAlgorithm::moveToRoot(SP<SDwindleNodeData> x, bool stable) {
+ if (!x || !x->pParent)
+ return;
+
+ if (x->pTarget->fullscreenMode() != FSMODE_NONE)
+ return;
+
+ // already at root
+ if (!x->pParent->pParent)
+ return;
+
+ auto& pNode = x->pParent->children[0] == x ? x->pParent->children[0] : x->pParent->children[1];
+
+ // instead of [getMasterNodeOnWorkspace], we walk back to root since we need
+ // to know which children of root is our ancestor
+ auto pAncestor = x, pRoot = x->pParent.lock();
+ while (pRoot->pParent) {
+ pAncestor = pRoot;
+ pRoot = pRoot->pParent.lock();
+ }
+
+ auto& pSwap = pRoot->children[0] == pAncestor ? pRoot->children[1] : pRoot->children[0];
+ std::swap(pNode, pSwap);
+ std::swap(pNode->pParent, pSwap->pParent);
+
+ // [stable] in that the focused window occupies same side of screen
+ if (stable)
+ std::swap(pRoot->children[0], pRoot->children[1]);
+
+ pRoot->recalcSizePosRecursive();
+}
diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp
new file mode 100644
index 00000000..27c905a4
--- /dev/null
+++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp
@@ -0,0 +1,57 @@
+#include "../../TiledAlgorithm.hpp"
+
+namespace Layout {
+ class CAlgorithm;
+}
+
+namespace Layout::Tiled {
+ struct SDwindleNodeData;
+
+ class CDwindleAlgorithm : public ITiledAlgorithm {
+ public:
+ CDwindleAlgorithm() = default;
+ virtual ~CDwindleAlgorithm() = default;
+
+ virtual void newTarget(SP<ITarget> target);
+ virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
+ virtual void removeTarget(SP<ITarget> target);
+
+ virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
+ virtual void recalculate();
+
+ virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
+
+ virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);
+ virtual std::optional<Vector2D> predictSizeForNewTarget();
+
+ virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
+ virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
+
+ private:
+ std::vector<SP<SDwindleNodeData>> m_dwindleNodesData;
+
+ struct {
+ bool started = false;
+ bool pseudo = false;
+ bool xExtent = false;
+ bool yExtent = false;
+ } m_pseudoDragFlags;
+
+ std::optional<Vector2D> m_overrideFocalPoint; // for onWindowCreatedTiling.
+
+ void addTarget(SP<ITarget> target, bool newTarget = true);
+ void calculateWorkspace();
+ SP<SDwindleNodeData> getNodeFromTarget(SP<ITarget>);
+ SP<SDwindleNodeData> getNodeFromWindow(PHLWINDOW w);
+ int getNodes();
+ SP<SDwindleNodeData> getFirstNode();
+ SP<SDwindleNodeData> getClosestNode(const Vector2D&);
+ SP<SDwindleNodeData> getMasterNode();
+
+ void toggleSplit(SP<SDwindleNodeData>);
+ void swapSplit(SP<SDwindleNodeData>);
+ void moveToRoot(SP<SDwindleNodeData>, bool stable = true);
+
+ Math::eDirection m_overrideDirection = Math::DIRECTION_DEFAULT;
+ };
+}; \ No newline at end of file
diff --git a/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp b/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp
new file mode 100644
index 00000000..7a6c6768
--- /dev/null
+++ b/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp
@@ -0,0 +1,1292 @@
+#include "MasterAlgorithm.hpp"
+
+#include "../../Algorithm.hpp"
+#include "../../../space/Space.hpp"
+#include "../../../target/WindowTarget.hpp"
+
+#include "../../../../config/ConfigValue.hpp"
+#include "../../../../config/ConfigManager.hpp"
+#include "../../../../desktop/state/FocusState.hpp"
+#include "../../../../helpers/Monitor.hpp"
+#include "../../../../Compositor.hpp"
+#include "../../../../render/Renderer.hpp"
+
+#include <hyprutils/utils/ScopeGuard.hpp>
+
+using namespace Layout;
+using namespace Layout::Tiled;
+
+struct Layout::Tiled::SMasterNodeData {
+ bool isMaster = false;
+ float percMaster = 0.5f;
+
+ WP<ITarget> pTarget;
+
+ Vector2D position;
+ Vector2D size;
+
+ float percSize = 1.f; // size multiplier for resizing children
+
+ bool ignoreFullscreenChecks = false;
+
+ //
+ bool operator==(const SMasterNodeData& rhs) const {
+ return pTarget.lock() == rhs.pTarget.lock();
+ }
+};
+
+void CMasterAlgorithm::newTarget(SP<ITarget> target) {
+ addTarget(target, true);
+}
+
+void CMasterAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {
+ addTarget(target, false);
+}
+
+void CMasterAlgorithm::addTarget(SP<ITarget> target, bool firstMap) {
+ static auto PNEWONACTIVE = CConfigValue<std::string>("master:new_on_active");
+ static auto PNEWONTOP = CConfigValue<Hyprlang::INT>("master:new_on_top");
+ static auto PNEWSTATUS = CConfigValue<std::string>("master:new_status");
+
+ const auto PWORKSPACE = m_parent->space()->workspace();
+ const auto PMONITOR = PWORKSPACE->m_monitor;
+
+ bool dragOntoMaster = false;
+
+ if (g_layoutManager->dragController()->wasDraggingWindow()) {
+ if (const auto n = getClosestNode(g_pInputManager->getMouseCoordsInternal()); n && n->isMaster)
+ dragOntoMaster = true;
+ }
+
+ const bool BNEWBEFOREACTIVE = *PNEWONACTIVE == "before";
+ const bool BNEWISMASTER = dragOntoMaster || *PNEWSTATUS == "master";
+
+ const auto PNODE = [&]() -> SP<SMasterNodeData> {
+ if (*PNEWONACTIVE != "none" && !BNEWISMASTER) {
+ const auto pLastNode = getNodeFromWindow(Desktop::focusState()->window());
+ if (pLastNode && !(pLastNode->isMaster && (getMastersNo() == 1 || *PNEWSTATUS == "slave"))) {
+ auto it = std::ranges::find(m_masterNodesData, pLastNode);
+ if (!BNEWBEFOREACTIVE)
+ ++it;
+ return *m_masterNodesData.emplace(it, makeShared<SMasterNodeData>());
+ }
+ }
+ return *PNEWONTOP ? *m_masterNodesData.emplace(m_masterNodesData.begin(), makeShared<SMasterNodeData>()) : m_masterNodesData.emplace_back(makeShared<SMasterNodeData>());
+ }();
+
+ PNODE->pTarget = target;
+
+ const auto WINDOWSONWORKSPACE = getNodesNo();
+ static auto PMFACT = CConfigValue<Hyprlang::FLOAT>("master:mfact");
+ float lastSplitPercent = *PMFACT;
+
+ auto OPENINGON = isWindowTiled(Desktop::focusState()->window()) && Desktop::focusState()->window()->m_workspace == PWORKSPACE ?
+ getNodeFromWindow(Desktop::focusState()->window()) :
+ getMasterNode();
+
+ const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();
+ static auto PDROPATCURSOR = CConfigValue<Hyprlang::INT>("master:drop_at_cursor");
+ eOrientation orientation = getDynamicOrientation();
+ const auto NODEIT = std::ranges::find(m_masterNodesData, PNODE);
+
+ bool forceDropAsMaster = false;
+ // if dragging window to move, drop it at the cursor position instead of bottom/top of stack
+ if (*PDROPATCURSOR && g_layoutManager->dragController()->mode() == MBIND_MOVE) {
+ if (WINDOWSONWORKSPACE > 2) {
+ auto& v = m_masterNodesData;
+
+ const std::size_t srcIndex = static_cast<std::size_t>(std::distance(v.begin(), NODEIT));
+
+ for (std::size_t i = 0; i < v.size(); ++i) {
+ const CBox box = v[i]->pTarget->position();
+ if (!box.containsPoint(MOUSECOORDS))
+ continue;
+
+ std::size_t insertIndex = i;
+
+ switch (orientation) {
+ case ORIENTATION_LEFT:
+ case ORIENTATION_RIGHT:
+ if (MOUSECOORDS.y > box.middle().y)
+ ++insertIndex; // insert after
+ break;
+
+ case ORIENTATION_TOP:
+ case ORIENTATION_BOTTOM:
+ if (MOUSECOORDS.x > box.middle().x)
+ ++insertIndex; // insert after
+ break;
+
+ case ORIENTATION_CENTER: break;
+
+ default: UNREACHABLE();
+ }
+
+ if (insertIndex > srcIndex)
+ --insertIndex;
+
+ if (insertIndex == srcIndex)
+ break;
+
+ auto node = std::move(v[srcIndex]);
+ v.erase(v.begin() + static_cast<std::ptrdiff_t>(srcIndex));
+ v.insert(v.begin() + static_cast<std::ptrdiff_t>(insertIndex), std::move(node));
+
+ break;
+ }
+ } else if (WINDOWSONWORKSPACE == 2) {
+ // when dropping as the second tiled window in the workspace,
+ // make it the master only if the cursor is on the master side of the screen
+ for (auto const& nd : m_masterNodesData) {
+ if (nd->isMaster) {
+ const auto MIDDLE = nd->pTarget->position().middle();
+ switch (orientation) {
+ case ORIENTATION_LEFT:
+ case ORIENTATION_CENTER:
+ if (MOUSECOORDS.x < MIDDLE.x)
+ forceDropAsMaster = true;
+ break;
+ case ORIENTATION_RIGHT:
+ if (MOUSECOORDS.x > MIDDLE.x)
+ forceDropAsMaster = true;
+ break;
+ case ORIENTATION_TOP:
+ if (MOUSECOORDS.y < MIDDLE.y)
+ forceDropAsMaster = true;
+ break;
+ case ORIENTATION_BOTTOM:
+ if (MOUSECOORDS.y > MIDDLE.y)
+ forceDropAsMaster = true;
+ break;
+ default: UNREACHABLE();
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ if (BNEWISMASTER //
+ || WINDOWSONWORKSPACE == 1 //
+ || (WINDOWSONWORKSPACE > 2 && !firstMap && OPENINGON && OPENINGON->isMaster) //
+ || forceDropAsMaster //
+ || (*PNEWSTATUS == "inherit" && OPENINGON && OPENINGON->isMaster && g_layoutManager->dragController()->mode() != MBIND_MOVE)) {
+
+ if (BNEWBEFOREACTIVE) {
+ for (auto& nd : m_masterNodesData | std::views::reverse) {
+ if (nd->isMaster) {
+ nd->isMaster = false;
+ lastSplitPercent = nd->percMaster;
+ break;
+ }
+ }
+ } else {
+ for (auto& nd : m_masterNodesData) {
+ if (nd->isMaster) {
+ nd->isMaster = false;
+ lastSplitPercent = nd->percMaster;
+ break;
+ }
+ }
+ }
+
+ PNODE->isMaster = true;
+ PNODE->percMaster = lastSplitPercent;
+
+ // first, check if it isn't too big.
+ if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PMONITOR->m_size.x * lastSplitPercent || MAXSIZE.y < PMONITOR->m_size.y) {
+ // we can't continue. make it floating.
+ m_parent->setFloating(target, true, true);
+ std::erase(m_masterNodesData, PNODE);
+ return;
+ }
+ } else {
+ PNODE->isMaster = false;
+ PNODE->percMaster = lastSplitPercent;
+
+ // first, check if it isn't too big.
+ if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX);
+ MAXSIZE.x < PMONITOR->m_size.x * (1 - lastSplitPercent) || MAXSIZE.y < PMONITOR->m_size.y * (1.f / (WINDOWSONWORKSPACE - 1))) {
+ // we can't continue. make it floating.
+ m_parent->setFloating(target, true);
+ std::erase(m_masterNodesData, PNODE);
+ return;
+ }
+ }
+
+ // recalc
+ calculateWorkspace();
+}
+
+void CMasterAlgorithm::removeTarget(SP<ITarget> target) {
+ const auto MASTERSLEFT = getMastersNo();
+ static auto SMALLSPLIT = CConfigValue<Hyprlang::INT>("master:allow_small_split");
+
+ const auto PNODE = getNodeFromTarget(target);
+
+ if (target->fullscreenMode() != FSMODE_NONE)
+ g_pCompositor->setWindowFullscreenInternal(target->window(), FSMODE_NONE);
+
+ if (PNODE->isMaster && (MASTERSLEFT <= 1 || *SMALLSPLIT == 1)) {
+ // find a new master from top of the list
+ for (auto& nd : m_masterNodesData) {
+ if (!nd->isMaster) {
+ nd->isMaster = true;
+ nd->percMaster = PNODE->percMaster;
+ break;
+ }
+ }
+ }
+
+ std::erase(m_masterNodesData, PNODE);
+
+ if (getMastersNo() == getNodesNo() && MASTERSLEFT > 1) {
+ for (auto& nd : m_masterNodesData | std::views::reverse) {
+ nd->isMaster = false;
+ break;
+ }
+ }
+ // BUGFIX: correct bug where closing one master in a stack of 2 would leave
+ // the screen half bare, and make it difficult to select remaining window
+ if (getNodesNo() == 1) {
+ for (auto& nd : m_masterNodesData) {
+ if (!nd->isMaster) {
+ nd->isMaster = true;
+ break;
+ }
+ }
+ }
+
+ calculateWorkspace();
+}
+
+void CMasterAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
+ const auto PNODE = getNodeFromTarget(target);
+
+ if (!PNODE)
+ return;
+
+ const auto PMONITOR = m_parent->space()->workspace()->m_monitor;
+ static auto SLAVECOUNTFORCENTER = CConfigValue<Hyprlang::INT>("master:slave_count_for_center_master");
+ static auto PSMARTRESIZING = CConfigValue<Hyprlang::INT>("master:smart_resizing");
+
+ const auto WORKAREA = PMONITOR->logicalBoxMinusReserved();
+ const bool DISPLAYBOTTOM = STICKS(PNODE->position.y + PNODE->size.y, WORKAREA.y + WORKAREA.h);
+ const bool DISPLAYRIGHT = STICKS(PNODE->position.x + PNODE->size.x, WORKAREA.x + WORKAREA.w);
+ const bool DISPLAYTOP = STICKS(PNODE->position.y, WORKAREA.y);
+ const bool DISPLAYLEFT = STICKS(PNODE->position.x, WORKAREA.x);
+
+ const bool LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT;
+ const bool TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT;
+ const bool NONE = corner == CORNER_NONE;
+
+ const auto MASTERS = getMastersNo();
+ const auto WINDOWS = getNodesNo();
+ const auto STACKWINDOWS = WINDOWS - MASTERS;
+
+ eOrientation orientation = getDynamicOrientation();
+ bool centered = orientation == ORIENTATION_CENTER && (STACKWINDOWS >= *SLAVECOUNTFORCENTER);
+ double delta = 0;
+
+ if (getNodesNo() == 1 && !centered)
+ return;
+
+ m_forceWarps = true;
+
+ switch (orientation) {
+ case ORIENTATION_LEFT: delta = Δ.x / PMONITOR->m_size.x; break;
+ case ORIENTATION_RIGHT: delta = -Δ.x / PMONITOR->m_size.x; break;
+ case ORIENTATION_BOTTOM: delta = -Δ.y / PMONITOR->m_size.y; break;
+ case ORIENTATION_TOP: delta = Δ.y / PMONITOR->m_size.y; break;
+ case ORIENTATION_CENTER:
+ delta = Δ.x / PMONITOR->m_size.x;
+ if (STACKWINDOWS >= *SLAVECOUNTFORCENTER) {
+ if (!NONE || !PNODE->isMaster)
+ delta *= 2;
+ if ((!PNODE->isMaster && DISPLAYLEFT) || (PNODE->isMaster && LEFT && *PSMARTRESIZING))
+ delta = -delta;
+ }
+ break;
+ default: UNREACHABLE();
+ }
+
+ for (auto& n : m_masterNodesData) {
+ if (n->isMaster)
+ n->percMaster = std::clamp(n->percMaster + delta, 0.05, 0.95);
+ }
+
+ // check the up/down resize
+ const bool isStackVertical = orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT || orientation == ORIENTATION_CENTER;
+
+ const auto RESIZEDELTA = isStackVertical ? Δ.y : Δ.x;
+
+ auto nodesInSameColumn = PNODE->isMaster ? MASTERS : STACKWINDOWS;
+ if (orientation == ORIENTATION_CENTER && !PNODE->isMaster)
+ nodesInSameColumn = DISPLAYRIGHT ? (nodesInSameColumn + 1) / 2 : nodesInSameColumn / 2;
+
+ const auto SIZE = isStackVertical ? WORKAREA.h / nodesInSameColumn : WORKAREA.w / nodesInSameColumn;
+
+ if (RESIZEDELTA != 0 && nodesInSameColumn > 1) {
+ if (!*PSMARTRESIZING) {
+ PNODE->percSize = std::clamp(PNODE->percSize + RESIZEDELTA / SIZE, 0.05, 1.95);
+ } else {
+ const auto NODEIT = std::ranges::find(m_masterNodesData, PNODE);
+ const auto REVNODEIT = std::ranges::find(m_masterNodesData | std::views::reverse, PNODE);
+
+ const float totalSize = isStackVertical ? WORKAREA.h : WORKAREA.w;
+ const float minSize = totalSize / nodesInSameColumn * 0.2;
+ const bool resizePrevNodes = isStackVertical ? (TOP || DISPLAYBOTTOM) && !DISPLAYTOP : (LEFT || DISPLAYRIGHT) && !DISPLAYLEFT;
+
+ int nodesLeft = 0;
+ float sizeLeft = 0;
+ int nodeCount = 0;
+ // check the sizes of all the nodes to be resized for later calculation
+ auto checkNodesLeft = [&sizeLeft, &nodesLeft, orientation, isStackVertical, &nodeCount, PNODE](auto it) {
+ if (it->isMaster != PNODE->isMaster)
+ return;
+ nodeCount++;
+ if (!it->isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1)
+ return;
+ sizeLeft += isStackVertical ? it->size.y : it->size.x;
+ nodesLeft++;
+ };
+ float resizeDiff;
+ if (resizePrevNodes) {
+ std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), checkNodesLeft);
+ resizeDiff = -RESIZEDELTA;
+ } else {
+ std::for_each(std::next(NODEIT), m_masterNodesData.end(), checkNodesLeft);
+ resizeDiff = RESIZEDELTA;
+ }
+
+ const float nodeSize = isStackVertical ? PNODE->size.y : PNODE->size.x;
+ const float maxSizeIncrease = sizeLeft - nodesLeft * minSize;
+ const float maxSizeDecrease = minSize - nodeSize;
+
+ // leaves enough room for the other nodes
+ resizeDiff = std::clamp(resizeDiff, maxSizeDecrease, maxSizeIncrease);
+ PNODE->percSize += resizeDiff / SIZE;
+
+ // resize the other nodes
+ nodeCount = 0;
+ auto resizeNodesLeft = [maxSizeIncrease, resizeDiff, minSize, orientation, isStackVertical, SIZE, &nodeCount, nodesLeft, PNODE](auto& it) {
+ if (it->isMaster != PNODE->isMaster)
+ return;
+ nodeCount++;
+ // if center orientation, only resize when on the same side
+ if (!it->isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1)
+ return;
+ const float size = isStackVertical ? it->size.y : it->size.x;
+ const float resizeDeltaForEach = maxSizeIncrease != 0 ? resizeDiff * (size - minSize) / maxSizeIncrease : resizeDiff / nodesLeft;
+ it->percSize -= resizeDeltaForEach / SIZE;
+ };
+ if (resizePrevNodes)
+ std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), resizeNodesLeft);
+ else
+ std::for_each(std::next(NODEIT), m_masterNodesData.end(), resizeNodesLeft);
+ }
+ }
+
+ recalculate();
+
+ m_forceWarps = false;
+}
+
+void CMasterAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
+ auto nodeA = getNodeFromTarget(a);
+ auto nodeB = getNodeFromTarget(b);
+
+ if (nodeA)
+ nodeA->pTarget = b;
+ if (nodeB)
+ nodeB->pTarget = a;
+}
+
+void CMasterAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
+ const auto PWINDOW2 = g_pCompositor->getWindowInDirection(t->window(), dir);
+
+ if (!t->window())
+ return;
+
+ PHLWORKSPACE targetWs;
+
+ if (!PWINDOW2 && t->space() && t->space()->workspace()) {
+ // try to find a monitor in dir
+ const auto PMONINDIR = g_pCompositor->getMonitorInDirection(t->space()->workspace()->m_monitor.lock(), dir);
+ if (PMONINDIR)
+ targetWs = PMONINDIR->m_activeWorkspace;
+ } else
+ targetWs = PWINDOW2->m_workspace;
+
+ if (!targetWs)
+ return;
+
+ t->window()->setAnimationsToMove();
+
+ if (t->window()->m_workspace != targetWs) {
+ t->assignToSpace(targetWs->m_space);
+ } else if (PWINDOW2) {
+ // if same monitor, switch windows
+ g_layoutManager->switchTargets(t, PWINDOW2->layoutTarget());
+ if (silent)
+ Desktop::focusState()->fullWindowFocus(PWINDOW2, Desktop::FOCUS_REASON_KEYBIND);
+
+ recalculate();
+ }
+}
+
+void CMasterAlgorithm::recalculate() {
+ calculateWorkspace();
+}
+
+std::expected<void, std::string> CMasterAlgorithm::layoutMsg(const std::string_view& sv) {
+ auto switchToWindow = [&](SP<ITarget> target) {
+ if (!target || !validMapped(target->window()))
+ return;
+
+ Desktop::focusState()->fullWindowFocus(target->window(), Desktop::FOCUS_REASON_KEYBIND);
+ g_pCompositor->warpCursorTo(target->position().middle());
+
+ g_pInputManager->m_forcedFocus = target->window();
+ g_pInputManager->simulateMouseMovement();
+ g_pInputManager->m_forcedFocus.reset();
+ };
+
+ CVarList2 vars(std::string{sv}, 0, 's');
+
+ if (vars.size() < 1 || vars[0].empty()) {
+ Log::logger->log(Log::ERR, "layoutmsg called without params");
+ return std::unexpected("layoutmsg without params");
+ }
+
+ auto command = vars[0];
+
+ // swapwithmaster <master | child | auto> <ignoremaster>
+ // first message argument can have the following values:
+ // * master - keep the focus at the new master
+ // * child - keep the focus at the new child
+ // * auto (default) - swap the focus (keep the focus of the previously selected window)
+ // * ignoremaster - ignore if master is focused
+
+ const auto PWINDOW = Desktop::focusState()->window();
+
+ if (command == "swapwithmaster") {
+ if (!PWINDOW)
+ return std::unexpected("No focused window");
+
+ if (!isWindowTiled(PWINDOW))
+ return std::unexpected("focused window isn't tiled");
+
+ const auto PMASTER = getMasterNode();
+
+ if (!PMASTER)
+ return std::unexpected("no master node");
+
+ const auto NEWCHILD = PMASTER->pTarget.lock();
+
+ const bool IGNORE_IF_MASTER = vars.size() >= 2 && std::ranges::any_of(vars, [](const auto& e) { return e == "ignoremaster"; });
+
+ if (PMASTER->pTarget.lock() != PWINDOW->layoutTarget()) {
+ const auto& NEWMASTER = PWINDOW->layoutTarget();
+ const bool newFocusToChild = vars.size() >= 2 && vars[1] == "child";
+ g_layoutManager->switchTargets(NEWMASTER, NEWCHILD);
+ const auto NEWFOCUS = newFocusToChild ? NEWCHILD : NEWMASTER;
+ switchToWindow(NEWFOCUS);
+ } else if (!IGNORE_IF_MASTER) {
+ for (auto const& n : m_masterNodesData) {
+ if (!n->isMaster) {
+ const auto NEWMASTER = n->pTarget.lock();
+ g_layoutManager->switchTargets(NEWMASTER, NEWCHILD);
+ const bool newFocusToMaster = vars.size() >= 2 && vars[1] == "master";
+ const auto NEWFOCUS = newFocusToMaster ? NEWMASTER : NEWCHILD;
+ switchToWindow(NEWFOCUS);
+ break;
+ }
+ }
+ }
+
+ return {};
+ }
+ // focusmaster <master | previous | auto>
+ // first message argument can have the following values:
+ // * master - keep the focus at the new master, even if it was focused before
+ // * previous - focus window which was previously switched from using `focusmaster previous` command, otherwise fallback to `auto`
+ // * auto (default) - swap the focus with the first child, if the current focus was master, otherwise focus master
+ else if (command == "focusmaster") {
+ if (!PWINDOW)
+ return std::unexpected("no focused window");
+
+ const auto PMASTER = getMasterNode();
+
+ if (!PMASTER)
+ return std::unexpected("no master");
+
+ const auto& ARG = vars[1]; // returns empty string if out of bounds
+
+ if (PMASTER->pTarget.lock() != PWINDOW->layoutTarget()) {
+ switchToWindow(PMASTER->pTarget.lock());
+ // save previously focused window (only for `previous` mode)
+ if (ARG == "previous")
+ m_workspaceData.focusMasterPrev = PWINDOW->layoutTarget();
+ return {};
+ }
+
+ const auto focusAuto = [&]() {
+ // focus first non-master window
+ for (auto const& n : m_masterNodesData) {
+ if (!n->isMaster) {
+ switchToWindow(n->pTarget.lock());
+ break;
+ }
+ }
+ };
+
+ if (ARG == "master")
+ return {};
+ // switch to previously saved window
+ else if (ARG == "previous") {
+ const auto PREVWINDOW = m_workspaceData.focusMasterPrev.lock();
+ const bool VALID = PREVWINDOW && getNodeFromWindow(PREVWINDOW->window()) && (PWINDOW != PREVWINDOW->window());
+ VALID ? switchToWindow(PREVWINDOW) : focusAuto();
+ } else
+ focusAuto();
+ } else if (command == "cyclenext") {
+ if (!PWINDOW)
+ return std::unexpected("no window");
+
+ const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop";
+ const auto PNEXTWINDOW = getNextTarget(PWINDOW->layoutTarget(), true, !NOLOOP);
+ switchToWindow(PNEXTWINDOW);
+ } else if (command == "cycleprev") {
+ if (!PWINDOW)
+ return std::unexpected("no window");
+
+ const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop";
+ const auto PPREVWINDOW = getNextTarget(PWINDOW->layoutTarget(), false, !NOLOOP);
+ switchToWindow(PPREVWINDOW);
+ } else if (command == "swapnext") {
+ if (!validMapped(PWINDOW))
+ return std::unexpected("no window");
+
+ if (PWINDOW->layoutTarget()->floating()) {
+ g_pKeybindManager->m_dispatchers["swapnext"]("");
+ return {};
+ }
+
+ const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop";
+ const auto PWINDOWTOSWAPWITH = getNextTarget(PWINDOW->layoutTarget(), true, !NOLOOP);
+
+ if (PWINDOWTOSWAPWITH) {
+ g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);
+ g_layoutManager->switchTargets(PWINDOW->layoutTarget(), PWINDOWTOSWAPWITH);
+ switchToWindow(PWINDOW->layoutTarget());
+ }
+ } else if (command == "swapprev") {
+ if (!validMapped(PWINDOW))
+ return std::unexpected("no window");
+
+ if (PWINDOW->layoutTarget()->floating()) {
+ g_pKeybindManager->m_dispatchers["swapnext"]("prev");
+ return {};
+ }
+
+ const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop";
+ const auto PWINDOWTOSWAPWITH = getNextTarget(PWINDOW->layoutTarget(), false, !NOLOOP);
+
+ if (PWINDOWTOSWAPWITH) {
+ g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);
+ g_layoutManager->switchTargets(PWINDOW->layoutTarget(), PWINDOWTOSWAPWITH);
+ switchToWindow(PWINDOW->layoutTarget());
+ }
+ } else if (command == "addmaster") {
+ if (!validMapped(PWINDOW))
+ return std::unexpected("no window");
+
+ if (PWINDOW->layoutTarget()->floating())
+ return std::unexpected("window is floating");
+
+ const auto PNODE = getNodeFromTarget(PWINDOW->layoutTarget());
+
+ const auto WINDOWS = getNodesNo();
+ const auto MASTERS = getMastersNo();
+ static auto SMALLSPLIT = CConfigValue<Hyprlang::INT>("master:allow_small_split");
+
+ if (MASTERS + 2 > WINDOWS && *SMALLSPLIT == 0)
+ return std::unexpected("nothing to do");
+
+ g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);
+
+ if (!PNODE || PNODE->isMaster) {
+ // first non-master node
+ for (auto& n : m_masterNodesData) {
+ if (!n->isMaster) {
+ n->isMaster = true;
+ break;
+ }
+ }
+ } else {
+ PNODE->isMaster = true;
+ }
+
+ calculateWorkspace();
+
+ } else if (command == "removemaster") {
+
+ if (!validMapped(PWINDOW))
+ return std::unexpected("no window");
+
+ if (PWINDOW->layoutTarget()->floating())
+ return std::unexpected("window isnt tiled");
+
+ const auto PNODE = getNodeFromTarget(PWINDOW->layoutTarget());
+
+ const auto WINDOWS = getNodesNo();
+ const auto MASTERS = getMastersNo();
+
+ if (WINDOWS < 2 || MASTERS < 2)
+ return std::unexpected("nothing to do");
+
+ g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);
+
+ if (!PNODE || !PNODE->isMaster) {
+ // first non-master node
+ for (auto& nd : m_masterNodesData | std::views::reverse) {
+ if (nd->isMaster) {
+ nd->isMaster = false;
+ break;
+ }
+ }
+ } else {
+ PNODE->isMaster = false;
+ }
+
+ calculateWorkspace();
+ } else if (command == "orientationleft" || command == "orientationright" || command == "orientationtop" || command == "orientationbottom" || command == "orientationcenter") {
+ if (!PWINDOW)
+ return std::unexpected("no window");
+
+ g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);
+
+ if (command == "orientationleft")
+ m_workspaceData.orientation = ORIENTATION_LEFT;
+ else if (command == "orientationright")
+ m_workspaceData.orientation = ORIENTATION_RIGHT;
+ else if (command == "orientationtop")
+ m_workspaceData.orientation = ORIENTATION_TOP;
+ else if (command == "orientationbottom")
+ m_workspaceData.orientation = ORIENTATION_BOTTOM;
+ else if (command == "orientationcenter")
+ m_workspaceData.orientation = ORIENTATION_CENTER;
+
+ calculateWorkspace();
+ } else if (command == "orientationnext") {
+ runOrientationCycle(nullptr, 1);
+ } else if (command == "orientationprev") {
+ runOrientationCycle(nullptr, -1);
+ } else if (command == "orientationcycle") {
+ runOrientationCycle(&vars, 1);
+ } else if (command == "mfact") {
+
+ if (!PWINDOW)
+ return std::unexpected("no window");
+
+ const bool exact = vars[1] == "exact";
+
+ float ratio = 0.F;
+
+ try {
+ ratio = std::stof(std::string{exact ? vars[2] : vars[1]});
+ } catch (...) { return std::unexpected("bad ratio"); }
+
+ const auto PNODE = getNodeFromWindow(PWINDOW);
+
+ const auto PMASTER = getMasterNode();
+
+ float newRatio = exact ? ratio : PMASTER->percMaster + ratio;
+ PMASTER->percMaster = std::clamp(newRatio, 0.05f, 0.95f);
+
+ recalculate();
+ } else if (command == "rollnext") {
+ const auto PNODE = getNodeFromWindow(PWINDOW);
+
+ if (!PNODE)
+ return std::unexpected("window couldnt be found");
+
+ const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNode();
+ if (!OLDMASTER)
+ return std::unexpected("no old master");
+
+ auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);
+
+ for (auto& nd : m_masterNodesData) {
+ if (!nd->isMaster) {
+ const auto newMaster = nd;
+ newMaster->isMaster = true;
+
+ auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster);
+
+ if (newMasterIt < oldMasterIt)
+ std::ranges::rotate(newMasterIt, std::next(newMasterIt), oldMasterIt);
+ else if (newMasterIt > oldMasterIt)
+ std::ranges::rotate(oldMasterIt, newMasterIt, std::next(newMasterIt));
+
+ switchToWindow(newMaster->pTarget.lock());
+ OLDMASTER->isMaster = false;
+
+ oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);
+ if (oldMasterIt != m_masterNodesData.end())
+ std::ranges::rotate(oldMasterIt, std::next(oldMasterIt), m_masterNodesData.end());
+
+ break;
+ }
+ }
+
+ calculateWorkspace();
+ } else if (command == "rollprev") {
+ const auto PNODE = getNodeFromWindow(PWINDOW);
+
+ if (!PNODE)
+ return std::unexpected("window couldnt be found");
+
+ const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNode();
+ if (!OLDMASTER)
+ return std::unexpected("no old master");
+
+ auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);
+
+ for (auto& nd : m_masterNodesData | std::views::reverse) {
+ if (!nd->isMaster) {
+ const auto newMaster = nd;
+ newMaster->isMaster = true;
+
+ auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster);
+
+ if (newMasterIt < oldMasterIt)
+ std::ranges::rotate(newMasterIt, std::next(newMasterIt), oldMasterIt);
+ else if (newMasterIt > oldMasterIt)
+ std::ranges::rotate(oldMasterIt, newMasterIt, std::next(newMasterIt));
+
+ switchToWindow(newMaster->pTarget.lock());
+ OLDMASTER->isMaster = false;
+
+ oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);
+ if (oldMasterIt != m_masterNodesData.begin())
+ std::ranges::rotate(m_masterNodesData.begin(), oldMasterIt, std::next(oldMasterIt));
+
+ break;
+ }
+ }
+
+ calculateWorkspace();
+ }
+
+ return {};
+}
+
+std::optional<Vector2D> CMasterAlgorithm::predictSizeForNewTarget() {
+ static auto PNEWSTATUS = CConfigValue<std::string>("master:new_status");
+
+ const auto MONITOR = m_parent->space()->workspace()->m_monitor;
+
+ if (!MONITOR)
+ return std::nullopt;
+
+ const int NODES = getNodesNo();
+
+ if (NODES <= 0)
+ return Desktop::focusState()->monitor()->m_size;
+
+ const auto MASTER = getMasterNode();
+ if (!MASTER) // wtf
+ return std::nullopt;
+
+ if (*PNEWSTATUS == "master") {
+ return MASTER->size;
+ } else {
+ const auto SLAVES = NODES - getMastersNo();
+
+ // TODO: make this better
+ if (SLAVES == 0)
+ return Vector2D{MONITOR->m_size.x / 2.F, MONITOR->m_size.y};
+ else
+ return Vector2D{MONITOR->m_size.x - MASTER->size.x, MONITOR->m_size.y / (SLAVES + 1)};
+ }
+
+ return std::nullopt;
+}
+
+void CMasterAlgorithm::buildOrientationCycleVectorFromVars(std::vector<eOrientation>& cycle, Hyprutils::String::CVarList2* vars) {
+ for (size_t i = 1; i < vars->size(); ++i) {
+ if ((*vars)[i] == "top") {
+ cycle.emplace_back(ORIENTATION_TOP);
+ } else if ((*vars)[i] == "right") {
+ cycle.emplace_back(ORIENTATION_RIGHT);
+ } else if ((*vars)[i] == "bottom") {
+ cycle.emplace_back(ORIENTATION_BOTTOM);
+ } else if ((*vars)[i] == "left") {
+ cycle.emplace_back(ORIENTATION_LEFT);
+ } else if ((*vars)[i] == "center") {
+ cycle.emplace_back(ORIENTATION_CENTER);
+ }
+ }
+}
+
+void CMasterAlgorithm::buildOrientationCycleVectorFromEOperation(std::vector<eOrientation>& cycle) {
+ for (int i = 0; i <= ORIENTATION_CENTER; ++i) {
+ cycle.push_back(sc<eOrientation>(i));
+ }
+}
+
+void CMasterAlgorithm::runOrientationCycle(Hyprutils::String::CVarList2* vars, int next) {
+ std::vector<eOrientation> cycle;
+ if (vars != nullptr)
+ buildOrientationCycleVectorFromVars(cycle, vars);
+
+ if (cycle.empty())
+ buildOrientationCycleVectorFromEOperation(cycle);
+
+ const auto PWINDOW = Desktop::focusState()->window();
+
+ if (!PWINDOW)
+ return;
+
+ g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);
+
+ int nextOrPrev = 0;
+ for (size_t i = 0; i < cycle.size(); ++i) {
+ if (m_workspaceData.orientation == cycle[i]) {
+ nextOrPrev = i + next;
+ break;
+ }
+ }
+
+ if (nextOrPrev >= sc<int>(cycle.size()))
+ nextOrPrev = nextOrPrev % sc<int>(cycle.size());
+ else if (nextOrPrev < 0)
+ nextOrPrev = cycle.size() + (nextOrPrev % sc<int>(cycle.size()));
+
+ m_workspaceData.orientation = cycle.at(nextOrPrev);
+ calculateWorkspace();
+}
+
+eOrientation CMasterAlgorithm::getDynamicOrientation() {
+ const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent->space()->workspace());
+ std::string orientationString;
+ if (WORKSPACERULE.layoutopts.contains("orientation"))
+ orientationString = WORKSPACERULE.layoutopts.at("orientation");
+
+ eOrientation orientation = m_workspaceData.orientation;
+ // override if workspace rule is set
+ if (!orientationString.empty()) {
+ if (orientationString == "top")
+ orientation = ORIENTATION_TOP;
+ else if (orientationString == "right")
+ orientation = ORIENTATION_RIGHT;
+ else if (orientationString == "bottom")
+ orientation = ORIENTATION_BOTTOM;
+ else if (orientationString == "center")
+ orientation = ORIENTATION_CENTER;
+ else
+ orientation = ORIENTATION_LEFT;
+ }
+
+ return orientation;
+}
+
+int CMasterAlgorithm::getNodesNo() {
+ return m_masterNodesData.size();
+}
+
+SP<SMasterNodeData> CMasterAlgorithm::getNodeFromWindow(PHLWINDOW x) {
+ return x ? getNodeFromTarget(x->layoutTarget()) : nullptr;
+}
+
+SP<SMasterNodeData> CMasterAlgorithm::getNodeFromTarget(SP<ITarget> x) {
+ for (const auto& n : m_masterNodesData) {
+ if (n->pTarget == x)
+ return n;
+ }
+
+ return nullptr;
+}
+
+SP<SMasterNodeData> CMasterAlgorithm::getMasterNode() {
+ for (const auto& n : m_masterNodesData) {
+ if (n->isMaster)
+ return n;
+ }
+
+ return nullptr;
+}
+
+void CMasterAlgorithm::calculateWorkspace() {
+ const auto PMASTERNODE = getMasterNode();
+
+ if (!PMASTERNODE)
+ return;
+
+ Hyprutils::Utils::CScopeGuard x([this] {
+ g_pHyprRenderer->damageMonitor(m_parent->space()->workspace()->m_monitor.lock());
+
+ if (!m_forceWarps)
+ return;
+
+ for (const auto& n : m_masterNodesData) {
+ n->pTarget->warpPositionSize();
+ }
+ });
+
+ eOrientation orientation = getDynamicOrientation();
+ bool centerMasterWindow = false;
+ static auto SLAVECOUNTFORCENTER = CConfigValue<Hyprlang::INT>("master:slave_count_for_center_master");
+ static auto CMFALLBACK = CConfigValue<std::string>("master:center_master_fallback");
+ static auto PIGNORERESERVED = CConfigValue<Hyprlang::INT>("master:center_ignores_reserved");
+ static auto PSMARTRESIZING = CConfigValue<Hyprlang::INT>("master:smart_resizing");
+
+ const auto MASTERS = getMastersNo();
+ const auto WINDOWS = getNodesNo();
+ const auto STACKWINDOWS = WINDOWS - MASTERS;
+ const auto WORKAREA = m_parent->space()->workArea();
+ const auto PMONITOR = m_parent->space()->workspace()->m_monitor;
+ const auto UNRESERVED_WIDTH = WORKAREA.width + PMONITOR->m_reservedArea.left() + PMONITOR->m_reservedArea.right();
+
+ if (orientation == ORIENTATION_CENTER) {
+ if (STACKWINDOWS >= *SLAVECOUNTFORCENTER)
+ centerMasterWindow = true;
+ else {
+ if (*CMFALLBACK == "left")
+ orientation = ORIENTATION_LEFT;
+ else if (*CMFALLBACK == "right")
+ orientation = ORIENTATION_RIGHT;
+ else if (*CMFALLBACK == "top")
+ orientation = ORIENTATION_TOP;
+ else if (*CMFALLBACK == "bottom")
+ orientation = ORIENTATION_BOTTOM;
+ else
+ orientation = ORIENTATION_LEFT;
+ }
+ }
+
+ const float totalSize = (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) ? WORKAREA.w : WORKAREA.h;
+ const float masterAverageSize = totalSize / MASTERS;
+ const float slaveAverageSize = totalSize / STACKWINDOWS;
+ float masterAccumulatedSize = 0;
+ float slaveAccumulatedSize = 0;
+
+ if (*PSMARTRESIZING) {
+ // check the total width and height so that later
+ // if larger/smaller than screen size them down/up
+ for (auto const& nd : m_masterNodesData) {
+ if (nd->isMaster)
+ masterAccumulatedSize += totalSize / MASTERS * nd->percSize;
+ else
+ slaveAccumulatedSize += totalSize / STACKWINDOWS * nd->percSize;
+ }
+ }
+
+ // compute placement of master window(s)
+ if (WINDOWS == 1 && !centerMasterWindow) {
+ static auto PALWAYSKEEPPOSITION = CConfigValue<Hyprlang::INT>("master:always_keep_position");
+ if (*PALWAYSKEEPPOSITION) {
+ const float WIDTH = WORKAREA.w * PMASTERNODE->percMaster;
+ float nextX = 0;
+
+ if (orientation == ORIENTATION_RIGHT)
+ nextX = WORKAREA.w - WIDTH;
+ else if (orientation == ORIENTATION_CENTER)
+ nextX = (WORKAREA.w - WIDTH) / 2;
+
+ PMASTERNODE->size = Vector2D(WIDTH, WORKAREA.h);
+ PMASTERNODE->position = WORKAREA.pos() + Vector2D(nextX, 0.0);
+ } else {
+ PMASTERNODE->size = WORKAREA.size();
+ PMASTERNODE->position = WORKAREA.pos();
+ }
+
+ PMASTERNODE->pTarget->setPositionGlobal({PMASTERNODE->position, PMASTERNODE->size});
+ return;
+ } else if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) {
+ const float HEIGHT = STACKWINDOWS != 0 ? WORKAREA.h * PMASTERNODE->percMaster : WORKAREA.h;
+ float widthLeft = WORKAREA.w;
+ int mastersLeft = MASTERS;
+ float nextX = 0;
+ float nextY = 0;
+
+ if (orientation == ORIENTATION_BOTTOM)
+ nextY = WORKAREA.h - HEIGHT;
+
+ for (auto& nd : m_masterNodesData) {
+ if (!nd->isMaster)
+ continue;
+
+ float WIDTH = mastersLeft > 1 ? widthLeft / mastersLeft * nd->percSize : widthLeft;
+ if (WIDTH > widthLeft * 0.9f && mastersLeft > 1)
+ WIDTH = widthLeft * 0.9f;
+
+ if (*PSMARTRESIZING) {
+ nd->percSize *= WORKAREA.w / masterAccumulatedSize;
+ WIDTH = masterAverageSize * nd->percSize;
+ }
+
+ nd->size = Vector2D(WIDTH, HEIGHT);
+ nd->position = WORKAREA.pos() + Vector2D(nextX, nextY);
+ nd->pTarget->setPositionGlobal({nd->position, nd->size});
+
+ mastersLeft--;
+ widthLeft -= WIDTH;
+ nextX += WIDTH;
+ }
+ } else { // orientation left, right or center
+ const float TOTAL_WIDTH = *PIGNORERESERVED && centerMasterWindow ? UNRESERVED_WIDTH : WORKAREA.w;
+ float WIDTH = TOTAL_WIDTH;
+ float heightLeft = WORKAREA.h;
+ int mastersLeft = MASTERS;
+ float nextX = 0;
+ float nextY = 0;
+
+ if (STACKWINDOWS > 0 || centerMasterWindow)
+ WIDTH *= PMASTERNODE->percMaster;
+
+ if (orientation == ORIENTATION_RIGHT)
+ nextX = WORKAREA.w - WIDTH;
+ else if (centerMasterWindow)
+ nextX += (TOTAL_WIDTH - WIDTH) / 2;
+
+ for (auto& nd : m_masterNodesData) {
+ if (!nd->isMaster)
+ continue;
+
+ float HEIGHT = mastersLeft > 1 ? heightLeft / mastersLeft * nd->percSize : heightLeft;
+ if (HEIGHT > heightLeft * 0.9f && mastersLeft > 1)
+ HEIGHT = heightLeft * 0.9f;
+
+ if (*PSMARTRESIZING) {
+ nd->percSize *= WORKAREA.h / masterAccumulatedSize;
+ HEIGHT = masterAverageSize * nd->percSize;
+ }
+
+ nd->size = Vector2D(WIDTH, HEIGHT);
+ nd->position = (*PIGNORERESERVED && centerMasterWindow ? WORKAREA.pos() - Vector2D(PMONITOR->m_reservedArea.left(), 0.0) : WORKAREA.pos()) + Vector2D(nextX, nextY);
+ nd->pTarget->setPositionGlobal({nd->position, nd->size});
+
+ mastersLeft--;
+ heightLeft -= HEIGHT;
+ nextY += HEIGHT;
+ }
+ }
+
+ if (STACKWINDOWS == 0)
+ return;
+
+ // compute placement of slave window(s)
+ int slavesLeft = STACKWINDOWS;
+ if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) {
+ const float HEIGHT = WORKAREA.h - PMASTERNODE->size.y;
+ float widthLeft = WORKAREA.w;
+ float nextX = 0;
+ float nextY = 0;
+
+ if (orientation == ORIENTATION_TOP)
+ nextY = PMASTERNODE->size.y;
+
+ for (auto& nd : m_masterNodesData) {
+ if (nd->isMaster)
+ continue;
+
+ float WIDTH = slavesLeft > 1 ? widthLeft / slavesLeft * nd->percSize : widthLeft;
+ if (WIDTH > widthLeft * 0.9f && slavesLeft > 1)
+ WIDTH = widthLeft * 0.9f;
+
+ if (*PSMARTRESIZING) {
+ nd->percSize *= WORKAREA.w / slaveAccumulatedSize;
+ WIDTH = slaveAverageSize * nd->percSize;
+ }
+
+ nd->size = Vector2D(WIDTH, HEIGHT);
+ nd->position = WORKAREA.pos() + Vector2D(nextX, nextY);
+ nd->pTarget->setPositionGlobal({nd->position, nd->size});
+
+ slavesLeft--;
+ widthLeft -= WIDTH;
+ nextX += WIDTH;
+ }
+ } else if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) {
+ const float WIDTH = WORKAREA.w - PMASTERNODE->size.x;
+ float heightLeft = WORKAREA.h;
+ float nextY = 0;
+ float nextX = 0;
+
+ if (orientation == ORIENTATION_LEFT)
+ nextX = PMASTERNODE->size.x;
+
+ for (auto& nd : m_masterNodesData) {
+ if (nd->isMaster)
+ continue;
+
+ float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd->percSize : heightLeft;
+ if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1)
+ HEIGHT = heightLeft * 0.9f;
+
+ if (*PSMARTRESIZING) {
+ nd->percSize *= WORKAREA.h / slaveAccumulatedSize;
+ HEIGHT = slaveAverageSize * nd->percSize;
+ }
+
+ nd->size = Vector2D(WIDTH, HEIGHT);
+ nd->position = WORKAREA.pos() + Vector2D(nextX, nextY);
+ nd->pTarget->setPositionGlobal({nd->position, nd->size});
+
+ slavesLeft--;
+ heightLeft -= HEIGHT;
+ nextY += HEIGHT;
+ }
+ } else { // slaves for centered master window(s)
+ const float WIDTH = ((*PIGNORERESERVED ? UNRESERVED_WIDTH : WORKAREA.w) - PMASTERNODE->size.x) / 2.0;
+ float heightLeft = 0;
+ float heightLeftL = WORKAREA.h;
+ float heightLeftR = WORKAREA.h;
+ float nextX = 0;
+ float nextY = 0;
+ float nextYL = 0;
+ float nextYR = 0;
+ bool onRight = *CMFALLBACK == "right";
+ int slavesLeftL = 1 + (slavesLeft - 1) / 2;
+ int slavesLeftR = slavesLeft - slavesLeftL;
+
+ if (onRight) {
+ slavesLeftR = 1 + (slavesLeft - 1) / 2;
+ slavesLeftL = slavesLeft - slavesLeftR;
+ }
+
+ const float slaveAverageHeightL = WORKAREA.h / slavesLeftL;
+ const float slaveAverageHeightR = WORKAREA.h / slavesLeftR;
+ float slaveAccumulatedHeightL = 0;
+ float slaveAccumulatedHeightR = 0;
+
+ if (*PSMARTRESIZING) {
+ for (auto const& nd : m_masterNodesData) {
+ if (nd->isMaster)
+ continue;
+
+ if (onRight)
+ slaveAccumulatedHeightR += slaveAverageHeightR * nd->percSize;
+ else
+ slaveAccumulatedHeightL += slaveAverageHeightL * nd->percSize;
+
+ onRight = !onRight;
+ }
+
+ onRight = *CMFALLBACK == "right";
+ }
+
+ for (auto& nd : m_masterNodesData) {
+ if (nd->isMaster)
+ continue;
+
+ if (onRight) {
+ nextX = WIDTH + PMASTERNODE->size.x - (*PIGNORERESERVED ? PMONITOR->m_reservedArea.left() : 0);
+ nextY = nextYR;
+ heightLeft = heightLeftR;
+ slavesLeft = slavesLeftR;
+ } else {
+ nextX = 0;
+ nextY = nextYL;
+ heightLeft = heightLeftL;
+ slavesLeft = slavesLeftL;
+ }
+
+ float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd->percSize : heightLeft;
+ if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1)
+ HEIGHT = heightLeft * 0.9f;
+
+ if (*PSMARTRESIZING) {
+ if (onRight) {
+ nd->percSize *= WORKAREA.h / slaveAccumulatedHeightR;
+ HEIGHT = slaveAverageHeightR * nd->percSize;
+ } else {
+ nd->percSize *= WORKAREA.h / slaveAccumulatedHeightL;
+ HEIGHT = slaveAverageHeightL * nd->percSize;
+ }
+ }
+
+ nd->size = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? PMONITOR->m_reservedArea.right() : PMONITOR->m_reservedArea.left())) : WIDTH, HEIGHT);
+ nd->position = WORKAREA.pos() + Vector2D(nextX, nextY);
+ nd->pTarget->setPositionGlobal({nd->position, nd->size});
+
+ if (onRight) {
+ heightLeftR -= HEIGHT;
+ nextYR += HEIGHT;
+ slavesLeftR--;
+ } else {
+ heightLeftL -= HEIGHT;
+ nextYL += HEIGHT;
+ slavesLeftL--;
+ }
+
+ onRight = !onRight;
+ }
+ }
+}
+
+SP<ITarget> CMasterAlgorithm::getNextCandidate(SP<ITarget> old) {
+ const auto MIDDLE = old->position().middle();
+
+ if (const auto NODE = getClosestNode(MIDDLE); NODE)
+ return NODE->pTarget.lock();
+
+ if (const auto NODE = getMasterNode(); NODE)
+ return NODE->pTarget.lock();
+
+ return nullptr;
+}
+
+SP<ITarget> CMasterAlgorithm::getNextTarget(SP<ITarget> t, bool next, bool loop) {
+ if (t->floating())
+ return nullptr;
+
+ const auto PNODE = getNodeFromTarget(t);
+
+ auto nodes = m_masterNodesData;
+ if (!next)
+ std::ranges::reverse(nodes);
+
+ const auto NODEIT = std::ranges::find(nodes, PNODE);
+
+ const bool ISMASTER = PNODE->isMaster;
+
+ auto CANDIDATE = std::find_if(NODEIT, nodes.end(), [&](const auto& other) { return other != PNODE && ISMASTER == other->isMaster; });
+ if (CANDIDATE == nodes.end())
+ CANDIDATE = std::ranges::find_if(nodes, [&](const auto& other) { return other != PNODE && ISMASTER != other->isMaster; });
+
+ if (CANDIDATE != nodes.end() && !loop) {
+ if ((*CANDIDATE)->isMaster && next)
+ return nullptr;
+ if (!(*CANDIDATE)->isMaster && ISMASTER && !next)
+ return nullptr;
+ }
+
+ return CANDIDATE == nodes.end() ? nullptr : (*CANDIDATE)->pTarget.lock();
+}
+
+int CMasterAlgorithm::getMastersNo() {
+ return std::ranges::count_if(m_masterNodesData, [](const auto& n) { return n->isMaster; });
+}
+
+bool CMasterAlgorithm::isWindowTiled(PHLWINDOW x) {
+ return x && !x->layoutTarget()->floating();
+}
+
+SP<SMasterNodeData> CMasterAlgorithm::getClosestNode(const Vector2D& point) {
+ SP<SMasterNodeData> res = nullptr;
+ double distClosest = -1;
+ for (auto& n : m_masterNodesData) {
+ if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) {
+ auto distAnother = vecToRectDistanceSquared(point, n->position, n->position + n->size);
+ if (!res || distAnother < distClosest) {
+ res = n;
+ distClosest = distAnother;
+ }
+ }
+ }
+ return res;
+}
diff --git a/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp b/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp
new file mode 100644
index 00000000..4524587f
--- /dev/null
+++ b/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp
@@ -0,0 +1,75 @@
+#include "../../TiledAlgorithm.hpp"
+
+#include <hyprutils/string/VarList2.hpp>
+
+namespace Layout {
+ class CAlgorithm;
+}
+
+namespace Layout::Tiled {
+ struct SMasterNodeData;
+
+ //orientation determines which side of the screen the master area resides
+ enum eOrientation : uint8_t {
+ ORIENTATION_LEFT = 0,
+ ORIENTATION_TOP,
+ ORIENTATION_RIGHT,
+ ORIENTATION_BOTTOM,
+ ORIENTATION_CENTER
+ };
+
+ struct SMasterWorkspaceData {
+ WORKSPACEID workspaceID = WORKSPACE_INVALID;
+ eOrientation orientation = ORIENTATION_LEFT;
+ // Previously focused non-master window when `focusmaster previous` command was issued
+ WP<ITarget> focusMasterPrev;
+
+ //
+ bool operator==(const SMasterWorkspaceData& rhs) const {
+ return workspaceID == rhs.workspaceID;
+ }
+ };
+
+ class CMasterAlgorithm : public ITiledAlgorithm {
+ public:
+ CMasterAlgorithm() = default;
+ virtual ~CMasterAlgorithm() = default;
+
+ virtual void newTarget(SP<ITarget> target);
+ virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
+ virtual void removeTarget(SP<ITarget> target);
+
+ virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
+ virtual void recalculate();
+
+ virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
+
+ virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);
+ virtual std::optional<Vector2D> predictSizeForNewTarget();
+
+ virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
+ virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
+
+ private:
+ std::vector<SP<SMasterNodeData>> m_masterNodesData;
+ SMasterWorkspaceData m_workspaceData;
+
+ void addTarget(SP<ITarget> target, bool firstMap);
+
+ bool m_forceWarps = false;
+
+ void buildOrientationCycleVectorFromVars(std::vector<eOrientation>& cycle, Hyprutils::String::CVarList2* vars);
+ void buildOrientationCycleVectorFromEOperation(std::vector<eOrientation>& cycle);
+ void runOrientationCycle(Hyprutils::String::CVarList2* vars, int next);
+ eOrientation getDynamicOrientation();
+ int getNodesNo();
+ SP<SMasterNodeData> getNodeFromWindow(PHLWINDOW);
+ SP<SMasterNodeData> getNodeFromTarget(SP<ITarget>);
+ SP<SMasterNodeData> getMasterNode();
+ SP<SMasterNodeData> getClosestNode(const Vector2D&);
+ void calculateWorkspace();
+ SP<ITarget> getNextTarget(SP<ITarget>, bool, bool);
+ int getMastersNo();
+ bool isWindowTiled(PHLWINDOW);
+ };
+}; \ No newline at end of file
diff --git a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp
new file mode 100644
index 00000000..65533e71
--- /dev/null
+++ b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp
@@ -0,0 +1,274 @@
+#include "MonocleAlgorithm.hpp"
+
+#include "../../Algorithm.hpp"
+#include "../../../space/Space.hpp"
+#include "../../../target/WindowTarget.hpp"
+#include "../../../LayoutManager.hpp"
+
+#include "../../../../config/ConfigValue.hpp"
+#include "../../../../desktop/state/FocusState.hpp"
+#include "../../../../desktop/history/WindowHistoryTracker.hpp"
+#include "../../../../helpers/Monitor.hpp"
+#include "../../../../Compositor.hpp"
+
+#include <hyprutils/string/VarList2.hpp>
+#include <hyprutils/string/ConstVarList.hpp>
+#include <hyprutils/utils/ScopeGuard.hpp>
+
+using namespace Hyprutils::String;
+using namespace Hyprutils::Utils;
+using namespace Layout;
+using namespace Layout::Tiled;
+
+CMonocleAlgorithm::CMonocleAlgorithm() {
+ // hook into focus changes to bring focused window to front
+ m_focusCallback = g_pHookSystem->hookDynamic("activeWindow", [this](void* hk, SCallbackInfo& info, std::any param) {
+ const auto PWINDOW = std::any_cast<Desktop::View::SWindowActiveEvent>(param).window;
+
+ if (!PWINDOW)
+ return;
+
+ if (!PWINDOW->m_workspace->isVisible())
+ return;
+
+ const auto TARGET = PWINDOW->layoutTarget();
+ if (!TARGET)
+ return;
+
+ focusTargetUpdate(TARGET);
+ });
+}
+
+CMonocleAlgorithm::~CMonocleAlgorithm() {
+ // unhide all windows before destruction
+ for (const auto& data : m_targetDatas) {
+ const auto TARGET = data->target.lock();
+ if (!TARGET)
+ continue;
+
+ const auto WINDOW = TARGET->window();
+ if (WINDOW)
+ WINDOW->setHidden(false);
+ }
+
+ m_focusCallback.reset();
+}
+
+SP<SMonocleTargetData> CMonocleAlgorithm::dataFor(SP<ITarget> t) {
+ for (auto& data : m_targetDatas) {
+ if (data->target.lock() == t)
+ return data;
+ }
+ return nullptr;
+}
+
+void CMonocleAlgorithm::newTarget(SP<ITarget> target) {
+ const auto DATA = m_targetDatas.emplace_back(makeShared<SMonocleTargetData>(target));
+
+ m_currentVisibleIndex = m_targetDatas.size() - 1;
+
+ recalculate();
+}
+
+void CMonocleAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {
+ newTarget(target);
+}
+
+void CMonocleAlgorithm::removeTarget(SP<ITarget> target) {
+ auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; });
+
+ if (it == m_targetDatas.end())
+ return;
+
+ // unhide window when removing from monocle layout
+ const auto WINDOW = target->window();
+ if (WINDOW)
+ WINDOW->setHidden(false);
+
+ const auto INDEX = std::distance(m_targetDatas.begin(), it);
+ m_targetDatas.erase(it);
+
+ if (m_targetDatas.empty()) {
+ m_currentVisibleIndex = 0;
+ return;
+ }
+
+ // try to use the last window in history if we can
+ for (const auto& historyWindow : Desktop::History::windowTracker()->historyForWorkspace(m_parent->space()->workspace()) | std::views::reverse) {
+ auto it = std::ranges::find_if(m_targetDatas, [&historyWindow](const auto& d) { return d->target == historyWindow->layoutTarget(); });
+
+ if (it == m_targetDatas.end())
+ continue;
+
+ // we found a historical target, use that first
+ m_currentVisibleIndex = std::distance(m_targetDatas.begin(), it);
+
+ recalculate();
+
+ return;
+ }
+
+ // if we didn't find history, fall back to last
+
+ if (m_currentVisibleIndex >= (int)m_targetDatas.size())
+ m_currentVisibleIndex = m_targetDatas.size() - 1;
+ else if (INDEX <= m_currentVisibleIndex && m_currentVisibleIndex > 0)
+ m_currentVisibleIndex--;
+
+ recalculate();
+}
+
+void CMonocleAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
+ // monocle layout doesn't support manual resizing, all windows are fullscreen
+}
+
+void CMonocleAlgorithm::recalculate() {
+ if (m_targetDatas.empty())
+ return;
+
+ const auto WORK_AREA = m_parent->space()->workArea();
+
+ for (size_t i = 0; i < m_targetDatas.size(); ++i) {
+ const auto& DATA = m_targetDatas[i];
+ const auto TARGET = DATA->target.lock();
+
+ if (!TARGET)
+ continue;
+
+ const auto WINDOW = TARGET->window();
+ if (!WINDOW)
+ continue;
+
+ DATA->layoutBox = WORK_AREA;
+ TARGET->setPositionGlobal(WORK_AREA);
+
+ const bool SHOULD_BE_VISIBLE = ((int)i == m_currentVisibleIndex);
+ WINDOW->setHidden(!SHOULD_BE_VISIBLE);
+ }
+}
+
+SP<ITarget> CMonocleAlgorithm::getNextCandidate(SP<ITarget> old) {
+ if (m_targetDatas.empty())
+ return nullptr;
+
+ auto it = std::ranges::find_if(m_targetDatas, [old](const auto& data) { return data->target.lock() == old; });
+
+ if (it == m_targetDatas.end()) {
+ if (m_currentVisibleIndex >= 0 && m_currentVisibleIndex < (int)m_targetDatas.size())
+ return m_targetDatas[m_currentVisibleIndex]->target.lock();
+ return nullptr;
+ }
+
+ auto next = std::next(it);
+ if (next == m_targetDatas.end())
+ next = m_targetDatas.begin();
+
+ return next->get()->target.lock();
+}
+
+std::expected<void, std::string> CMonocleAlgorithm::layoutMsg(const std::string_view& sv) {
+ CVarList2 vars(std::string{sv}, 0, 's');
+
+ if (vars.size() < 1)
+ return std::unexpected("layoutmsg requires at least 1 argument");
+
+ const auto COMMAND = vars[0];
+
+ if (COMMAND == "cyclenext") {
+ cycleNext();
+ return {};
+ } else if (COMMAND == "cycleprev") {
+ cyclePrev();
+ return {};
+ }
+
+ return std::unexpected(std::format("Unknown monocle layoutmsg: {}", COMMAND));
+}
+
+std::optional<Vector2D> CMonocleAlgorithm::predictSizeForNewTarget() {
+ const auto WORK_AREA = m_parent->space()->workArea();
+ return WORK_AREA.size();
+}
+
+void CMonocleAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
+ auto nodeA = dataFor(a);
+ auto nodeB = dataFor(b);
+
+ if (nodeA)
+ nodeA->target = b;
+ if (nodeB)
+ nodeB->target = a;
+
+ recalculate();
+}
+
+void CMonocleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
+ // try to find a monitor in the specified direction, thats the logical thing
+ if (!t || !t->space() || !t->space()->workspace())
+ return;
+
+ const auto PMONINDIR = g_pCompositor->getMonitorInDirection(t->space()->workspace()->m_monitor.lock(), dir);
+
+ // if we found a monitor, move the window there
+ if (PMONINDIR && PMONINDIR != t->space()->workspace()->m_monitor.lock()) {
+ const auto TARGETWS = PMONINDIR->m_activeWorkspace;
+
+ if (t->window())
+ t->window()->setAnimationsToMove();
+
+ t->assignToSpace(TARGETWS->m_space);
+ }
+}
+
+void CMonocleAlgorithm::cycleNext() {
+ if (m_targetDatas.empty())
+ return;
+
+ m_currentVisibleIndex = (m_currentVisibleIndex + 1) % m_targetDatas.size();
+ updateVisible();
+}
+
+void CMonocleAlgorithm::cyclePrev() {
+ if (m_targetDatas.empty())
+ return;
+
+ m_currentVisibleIndex--;
+ if (m_currentVisibleIndex < 0)
+ m_currentVisibleIndex = m_targetDatas.size() - 1;
+ updateVisible();
+}
+
+void CMonocleAlgorithm::focusTargetUpdate(SP<ITarget> target) {
+ auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; });
+
+ if (it == m_targetDatas.end())
+ return;
+
+ const auto NEW_INDEX = std::distance(m_targetDatas.begin(), it);
+
+ if (m_currentVisibleIndex != NEW_INDEX) {
+ m_currentVisibleIndex = NEW_INDEX;
+ updateVisible();
+ }
+}
+
+void CMonocleAlgorithm::updateVisible() {
+ recalculate();
+
+ const auto VISIBLE_TARGET = getVisibleTarget();
+ if (!VISIBLE_TARGET)
+ return;
+
+ const auto WINDOW = VISIBLE_TARGET->window();
+ if (!WINDOW)
+ return;
+
+ Desktop::focusState()->fullWindowFocus(WINDOW, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
+}
+
+SP<ITarget> CMonocleAlgorithm::getVisibleTarget() {
+ if (m_currentVisibleIndex < 0 || m_currentVisibleIndex >= (int)m_targetDatas.size())
+ return nullptr;
+
+ return m_targetDatas[m_currentVisibleIndex]->target.lock();
+}
diff --git a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp
new file mode 100644
index 00000000..e409b885
--- /dev/null
+++ b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "../../TiledAlgorithm.hpp"
+#include "../../../../managers/HookSystemManager.hpp"
+
+#include <vector>
+
+namespace Layout::Tiled {
+
+ struct SMonocleTargetData {
+ SMonocleTargetData(SP<ITarget> t) : target(t) {
+ ;
+ }
+
+ WP<ITarget> target;
+ CBox layoutBox;
+ };
+
+ class CMonocleAlgorithm : public ITiledAlgorithm {
+ public:
+ CMonocleAlgorithm();
+ virtual ~CMonocleAlgorithm();
+
+ virtual void newTarget(SP<ITarget> target);
+ virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
+ virtual void removeTarget(SP<ITarget> target);
+
+ virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
+ virtual void recalculate();
+
+ virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
+
+ virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);
+ virtual std::optional<Vector2D> predictSizeForNewTarget();
+
+ virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
+ virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
+
+ private:
+ std::vector<SP<SMonocleTargetData>> m_targetDatas;
+ SP<HOOK_CALLBACK_FN> m_focusCallback;
+
+ int m_currentVisibleIndex = 0;
+
+ SP<SMonocleTargetData> dataFor(SP<ITarget> t);
+ void cycleNext();
+ void cyclePrev();
+ void focusTargetUpdate(SP<ITarget> target);
+ void updateVisible();
+ SP<ITarget> getVisibleTarget();
+ };
+};
diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp
new file mode 100644
index 00000000..63b98717
--- /dev/null
+++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp
@@ -0,0 +1,293 @@
+#include "ScrollTapeController.hpp"
+#include "ScrollingAlgorithm.hpp"
+#include <algorithm>
+#include <cmath>
+
+using namespace Layout::Tiled;
+
+CScrollTapeController::CScrollTapeController(eScrollDirection direction) : m_direction(direction) {
+ ;
+}
+
+void CScrollTapeController::setDirection(eScrollDirection dir) {
+ m_direction = dir;
+}
+
+eScrollDirection CScrollTapeController::getDirection() const {
+ return m_direction;
+}
+
+bool CScrollTapeController::isPrimaryHorizontal() const {
+ return m_direction == SCROLL_DIR_RIGHT || m_direction == SCROLL_DIR_LEFT;
+}
+
+bool CScrollTapeController::isReversed() const {
+ return m_direction == SCROLL_DIR_LEFT || m_direction == SCROLL_DIR_UP;
+}
+
+size_t CScrollTapeController::stripCount() const {
+ return m_strips.size();
+}
+
+SStripData& CScrollTapeController::getStrip(size_t index) {
+ return m_strips[index];
+}
+
+const SStripData& CScrollTapeController::getStrip(size_t index) const {
+ return m_strips[index];
+}
+
+void CScrollTapeController::setOffset(double offset) {
+ m_offset = offset;
+}
+
+double CScrollTapeController::getOffset() const {
+ return m_offset;
+}
+
+void CScrollTapeController::adjustOffset(double delta) {
+ m_offset += delta;
+}
+
+size_t CScrollTapeController::addStrip(float size) {
+ m_strips.emplace_back();
+ m_strips.back().size = size;
+ return m_strips.size() - 1;
+}
+
+void CScrollTapeController::insertStrip(size_t afterIndex, float size) {
+ if (afterIndex >= m_strips.size()) {
+ addStrip(size);
+ return;
+ }
+
+ SStripData newStrip;
+ newStrip.size = size;
+ m_strips.insert(m_strips.begin() + afterIndex + 1, newStrip);
+}
+
+void CScrollTapeController::removeStrip(size_t index) {
+ if (index < m_strips.size())
+ m_strips.erase(m_strips.begin() + index);
+}
+
+double CScrollTapeController::getPrimary(const Vector2D& v) const {
+ return isPrimaryHorizontal() ? v.x : v.y;
+}
+
+double CScrollTapeController::getSecondary(const Vector2D& v) const {
+ return isPrimaryHorizontal() ? v.y : v.x;
+}
+
+void CScrollTapeController::setPrimary(Vector2D& v, double val) const {
+ if (isPrimaryHorizontal())
+ v.x = val;
+ else
+ v.y = val;
+}
+
+void CScrollTapeController::setSecondary(Vector2D& v, double val) const {
+ if (isPrimaryHorizontal())
+ v.y = val;
+ else
+ v.x = val;
+}
+
+Vector2D CScrollTapeController::makeVector(double primary, double secondary) const {
+ if (isPrimaryHorizontal())
+ return {primary, secondary};
+ else
+ return {secondary, primary};
+}
+
+double CScrollTapeController::calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne) const {
+ if (m_strips.empty())
+ return 0.0;
+
+ if (fullscreenOnOne && m_strips.size() == 1)
+ return getPrimary(usableArea.size());
+
+ double total = 0.0;
+ const double usablePrimary = getPrimary(usableArea.size());
+
+ for (const auto& strip : m_strips) {
+ total += usablePrimary * strip.size;
+ }
+
+ return total;
+}
+
+double CScrollTapeController::calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
+ if (stripIndex >= m_strips.size())
+ return 0.0;
+
+ const double usablePrimary = getPrimary(usableArea.size());
+ double current = 0.0;
+
+ for (size_t i = 0; i < stripIndex; ++i) {
+ const double stripSize = (fullscreenOnOne && m_strips.size() == 1) ? usablePrimary : usablePrimary * m_strips[i].size;
+ current += stripSize;
+ }
+
+ return current;
+}
+
+double CScrollTapeController::calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
+ if (stripIndex >= m_strips.size())
+ return 0.0;
+
+ const double usablePrimary = getPrimary(usableArea.size());
+
+ if (fullscreenOnOne && m_strips.size() == 1)
+ return usablePrimary;
+
+ return usablePrimary * m_strips[stripIndex].size;
+}
+
+CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) {
+ if (stripIndex >= m_strips.size())
+ return {};
+
+ const auto& strip = m_strips[stripIndex];
+ if (targetIndex >= strip.targetSizes.size())
+ return {};
+
+ const double usableSecondary = getSecondary(usableArea.size());
+ const double usablePrimary = getPrimary(usableArea.size());
+ const double cameraOffset = calculateCameraOffset(usableArea, fullscreenOnOne);
+
+ // calculate position along primary axis (strip position)
+ double primaryPos = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
+ double primarySize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
+
+ // calculate position along secondary axis (within strip)
+ double secondaryPos = 0.0;
+ for (size_t i = 0; i < targetIndex; ++i) {
+ secondaryPos += strip.targetSizes[i] * usableSecondary;
+ }
+ double secondarySize = strip.targetSizes[targetIndex] * usableSecondary;
+
+ // apply camera offset based on direction
+ // for RIGHT/DOWN: scroll offset moves content left/up (subtract)
+ // for LEFT/UP: scroll offset moves content right/down (different coordinate system)
+ if (m_direction == SCROLL_DIR_LEFT) {
+ // LEFT: flip the entire primary axis, then apply offset
+ primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset;
+ } else if (m_direction == SCROLL_DIR_UP) {
+ // UP: flip the entire primary axis, then apply offset
+ primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset;
+ } else {
+ // RIGHT/DOWN: normal offset
+ primaryPos -= cameraOffset;
+ }
+
+ // create the box in primary/secondary coordinates
+ Vector2D pos = makeVector(primaryPos, secondaryPos);
+ Vector2D size = makeVector(primarySize, secondarySize);
+
+ // translate to workspace position
+ pos = pos + workspaceOffset;
+
+ return CBox{pos, size};
+}
+
+double CScrollTapeController::calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne) {
+ const double maxExtent = calculateMaxExtent(usableArea, fullscreenOnOne);
+ const double usablePrimary = getPrimary(usableArea.size());
+
+ // don't adjust the offset if we are dragging
+ if (isBeingDragged())
+ return m_offset;
+
+ // if the content fits in viewport, center it
+ if (maxExtent < usablePrimary)
+ m_offset = std::round((maxExtent - usablePrimary) / 2.0);
+
+ // if the offset is negative but we already extended, reset offset to 0
+ if (maxExtent > usablePrimary && m_offset < 0.0)
+ m_offset = 0.0;
+
+ return m_offset;
+}
+
+Vector2D CScrollTapeController::getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne) {
+ const double offset = calculateCameraOffset(usableArea, fullscreenOnOne);
+
+ if (isReversed())
+ return makeVector(offset, 0.0);
+ else
+ return makeVector(-offset, 0.0);
+}
+
+void CScrollTapeController::centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) {
+ if (stripIndex >= m_strips.size())
+ return;
+
+ const double usablePrimary = getPrimary(usableArea.size());
+ const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
+ const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
+
+ m_offset = stripStart - (usablePrimary - stripSize) / 2.0;
+}
+
+void CScrollTapeController::fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) {
+ if (stripIndex >= m_strips.size())
+ return;
+
+ const double usablePrimary = getPrimary(usableArea.size());
+ const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
+ const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
+
+ m_offset = std::clamp(m_offset, stripStart - usablePrimary + stripSize, stripStart);
+}
+
+bool CScrollTapeController::isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
+ if (stripIndex >= m_strips.size())
+ return false;
+
+ const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
+ const double stripEnd = stripStart + calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
+ const double viewStart = m_offset;
+ const double viewEnd = m_offset + getPrimary(usableArea.size());
+
+ return stripStart < viewEnd && viewStart < stripEnd;
+}
+
+size_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne) const {
+ if (m_strips.empty())
+ return 0;
+
+ const double usablePrimary = getPrimary(usableArea.size());
+ double currentPos = m_offset;
+
+ 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)
+ return i;
+ }
+
+ return m_strips.empty() ? 0 : m_strips.size() - 1;
+}
+
+void CScrollTapeController::swapStrips(size_t a, size_t b) {
+ if (a >= m_strips.size() || b >= m_strips.size())
+ return;
+
+ std::swap(m_strips.at(a), m_strips.at(b));
+}
+
+bool CScrollTapeController::isBeingDragged() const {
+ for (const auto& s : m_strips) {
+ if (!s.userData)
+ continue;
+
+ for (const auto& d : s.userData->targetDatas) {
+ if (d->target == g_layoutManager->dragController()->target())
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp
new file mode 100644
index 00000000..d03a9b94
--- /dev/null
+++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp
@@ -0,0 +1,83 @@
+#pragma once
+
+#include "../../../../helpers/math/Math.hpp"
+#include "../../../../helpers/memory/Memory.hpp"
+#include <vector>
+
+namespace Layout::Tiled {
+
+ struct SColumnData;
+
+ enum eScrollDirection : uint8_t {
+ SCROLL_DIR_RIGHT = 0,
+ SCROLL_DIR_LEFT,
+ SCROLL_DIR_DOWN,
+ SCROLL_DIR_UP,
+ };
+
+ struct SStripData {
+ float size = 1.F; // size along primary axis
+ std::vector<float> targetSizes; // sizes along secondary axis for each target in this strip
+ WP<SColumnData> userData;
+
+ SStripData() = default;
+ };
+
+ struct STapeLayoutResult {
+ CBox box;
+ size_t stripIndex = 0;
+ size_t targetIndex = 0;
+ };
+
+ class CScrollTapeController {
+ public:
+ CScrollTapeController(eScrollDirection direction = SCROLL_DIR_RIGHT);
+ ~CScrollTapeController() = default;
+
+ void setDirection(eScrollDirection dir);
+ eScrollDirection getDirection() const;
+ bool isPrimaryHorizontal() const;
+ bool isReversed() const;
+
+ size_t addStrip(float size = 1.0F);
+ void insertStrip(size_t afterIndex, float size = 1.0F);
+ void removeStrip(size_t index);
+ size_t stripCount() const;
+ SStripData& getStrip(size_t index);
+ const SStripData& getStrip(size_t index) const;
+ void swapStrips(size_t a, size_t b);
+
+ void setOffset(double offset);
+ double getOffset() const;
+ void adjustOffset(double delta);
+
+ double calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne = false) const;
+ double calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;
+ double calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;
+
+ CBox calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false);
+
+ double calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne = false);
+ Vector2D getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne = false);
+
+ void centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false);
+ void fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false);
+
+ bool isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;
+
+ size_t getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne = false) const;
+
+ private:
+ eScrollDirection m_direction = SCROLL_DIR_RIGHT;
+ std::vector<SStripData> m_strips;
+ double m_offset = 0.0;
+
+ double getPrimary(const Vector2D& v) const;
+ double getSecondary(const Vector2D& v) const;
+ void setPrimary(Vector2D& v, double val) const;
+ void setSecondary(Vector2D& v, double val) const;
+ bool isBeingDragged() const;
+
+ Vector2D makeVector(double primary, double secondary) const;
+ };
+};
diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp
new file mode 100644
index 00000000..74de48e4
--- /dev/null
+++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp
@@ -0,0 +1,1412 @@
+#include "ScrollingAlgorithm.hpp"
+#include "ScrollTapeController.hpp"
+
+#include "../../Algorithm.hpp"
+#include "../../../space/Space.hpp"
+#include "../../../LayoutManager.hpp"
+
+#include "../../../../Compositor.hpp"
+#include "../../../../desktop/state/FocusState.hpp"
+#include "../../../../config/ConfigValue.hpp"
+#include "../../../../config/ConfigManager.hpp"
+#include "../../../../render/Renderer.hpp"
+#include "../../../../managers/input/InputManager.hpp"
+
+#include <hyprutils/string/VarList2.hpp>
+#include <hyprutils/string/ConstVarList.hpp>
+#include <hyprutils/utils/ScopeGuard.hpp>
+
+using namespace Hyprutils::String;
+using namespace Hyprutils::Utils;
+using namespace Layout;
+using namespace Layout::Tiled;
+
+constexpr float MIN_COLUMN_WIDTH = 0.05F;
+constexpr float MAX_COLUMN_WIDTH = 1.F;
+constexpr float MIN_ROW_HEIGHT = 0.1F;
+constexpr float MAX_ROW_HEIGHT = 1.F;
+
+//
+float SColumnData::getColumnWidth() const {
+ if (!scrollingData || !scrollingData->controller)
+ return 1.F;
+
+ auto sd = scrollingData.lock();
+ if (!sd)
+ return 1.F;
+
+ int64_t idx = sd->idx(self.lock());
+ if (idx < 0 || (size_t)idx >= sd->controller->stripCount())
+ return 1.F;
+
+ return sd->controller->getStrip(idx).size;
+}
+
+void SColumnData::setColumnWidth(float width) {
+ if (!scrollingData || !scrollingData->controller)
+ return;
+
+ auto sd = scrollingData.lock();
+ if (!sd)
+ return;
+
+ int64_t idx = sd->idx(self.lock());
+ if (idx < 0 || (size_t)idx >= sd->controller->stripCount())
+ return;
+
+ sd->controller->getStrip(idx).size = width;
+}
+
+float SColumnData::getTargetSize(size_t idx) const {
+ if (!scrollingData || !scrollingData->controller)
+ return 1.F;
+
+ auto sd = scrollingData.lock();
+ if (!sd)
+ return 1.F;
+
+ int64_t colIdx = sd->idx(self.lock());
+ if (colIdx < 0 || (size_t)colIdx >= sd->controller->stripCount())
+ return 1.F;
+
+ const auto& strip = sd->controller->getStrip(colIdx);
+ if (idx >= strip.targetSizes.size())
+ return 1.F;
+
+ return strip.targetSizes[idx];
+}
+
+void SColumnData::setTargetSize(size_t idx, float size) {
+ if (!scrollingData || !scrollingData->controller)
+ return;
+
+ auto sd = scrollingData.lock();
+ if (!sd)
+ return;
+
+ int64_t colIdx = sd->idx(self.lock());
+ if (colIdx < 0 || (size_t)colIdx >= sd->controller->stripCount())
+ return;
+
+ auto& strip = sd->controller->getStrip(colIdx);
+ if (idx >= strip.targetSizes.size())
+ strip.targetSizes.resize(idx + 1, 1.F);
+
+ strip.targetSizes[idx] = size;
+}
+
+float SColumnData::getTargetSize(SP<SScrollingTargetData> target) const {
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ if (targetDatas[i] == target)
+ return getTargetSize(i);
+ }
+ return 1.F;
+}
+
+void SColumnData::setTargetSize(SP<SScrollingTargetData> target, float size) {
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ if (targetDatas[i] == target) {
+ setTargetSize(i, size);
+ return;
+ }
+ }
+}
+
+void SColumnData::add(SP<ITarget> t) {
+ const float newSize = 1.F / (float)(targetDatas.size() + 1);
+
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1));
+ }
+
+ targetDatas.emplace_back(makeShared<SScrollingTargetData>(t, self.lock()));
+ setTargetSize(targetDatas.size() - 1, newSize);
+}
+
+void SColumnData::add(SP<ITarget> t, int after) {
+ const float newSize = 1.F / (float)(targetDatas.size() + 1);
+
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1));
+ }
+
+ targetDatas.insert(targetDatas.begin() + after + 1, makeShared<SScrollingTargetData>(t, self.lock()));
+
+ // Sync sizes - need to insert at the right position
+ if (scrollingData) {
+ auto sd = scrollingData.lock();
+ if (sd && sd->controller) {
+ int64_t colIdx = sd->idx(self.lock());
+ if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) {
+ auto& strip = sd->controller->getStrip(colIdx);
+ strip.targetSizes.insert(strip.targetSizes.begin() + after + 1, newSize);
+ }
+ }
+ }
+}
+
+void SColumnData::add(SP<SScrollingTargetData> w) {
+ const float newSize = 1.F / (float)(targetDatas.size() + 1);
+
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1));
+ }
+
+ targetDatas.emplace_back(w);
+ w->column = self;
+ setTargetSize(targetDatas.size() - 1, newSize);
+}
+
+void SColumnData::add(SP<SScrollingTargetData> w, int after) {
+ const float newSize = 1.F / (float)(targetDatas.size() + 1);
+
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1));
+ }
+
+ targetDatas.insert(targetDatas.begin() + after + 1, w);
+ w->column = self;
+
+ // Sync sizes
+ if (scrollingData) {
+ auto sd = scrollingData.lock();
+ if (sd && sd->controller) {
+ int64_t colIdx = sd->idx(self.lock());
+ if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) {
+ auto& strip = sd->controller->getStrip(colIdx);
+ strip.targetSizes.insert(strip.targetSizes.begin() + after + 1, newSize);
+ }
+ }
+ }
+}
+
+size_t SColumnData::idx(SP<ITarget> t) {
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ if (targetDatas[i]->target == t)
+ return i;
+ }
+ return 0;
+}
+
+size_t SColumnData::idxForHeight(float y) {
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ if (targetDatas[i]->target->position().y < y)
+ continue;
+ return i - 1;
+ }
+ return targetDatas.size() - 1;
+}
+
+void SColumnData::remove(SP<ITarget> t) {
+ const auto SIZE_BEFORE = targetDatas.size();
+ size_t removedIdx = 0;
+ bool found = false;
+
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ if (targetDatas[i]->target == t) {
+ removedIdx = i;
+ found = true;
+ break;
+ }
+ }
+
+ std::erase_if(targetDatas, [&t](const auto& e) { return e->target == t; });
+
+ if (SIZE_BEFORE == targetDatas.size() && SIZE_BEFORE > 0)
+ return;
+
+ if (found && scrollingData) {
+ auto sd = scrollingData.lock();
+ if (sd && sd->controller) {
+ int64_t colIdx = sd->idx(self.lock());
+ if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) {
+ auto& strip = sd->controller->getStrip(colIdx);
+ if (removedIdx < strip.targetSizes.size()) {
+ strip.targetSizes.erase(strip.targetSizes.begin() + removedIdx);
+ }
+ }
+ }
+ }
+
+ // Renormalize sizes
+ float newMaxSize = 0.F;
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ newMaxSize += getTargetSize(i);
+ }
+
+ if (newMaxSize > 0.F) {
+ for (size_t i = 0; i < targetDatas.size(); ++i) {
+ setTargetSize(i, getTargetSize(i) / newMaxSize);
+ }
+ }
+
+ if (targetDatas.empty() && scrollingData)
+ scrollingData->remove(self.lock());
+}
+
+void SColumnData::up(SP<SScrollingTargetData> w) {
+ for (size_t i = 1; i < targetDatas.size(); ++i) {
+ if (targetDatas[i] != w)
+ continue;
+
+ std::swap(targetDatas[i], targetDatas[i - 1]);
+ break;
+ }
+}
+
+void SColumnData::down(SP<SScrollingTargetData> w) {
+ for (size_t i = 0; i < targetDatas.size() - 1; ++i) {
+ if (targetDatas[i] != w)
+ continue;
+
+ std::swap(targetDatas[i], targetDatas[i + 1]);
+ break;
+ }
+}
+
+SP<SScrollingTargetData> SColumnData::next(SP<SScrollingTargetData> w) {
+ for (size_t i = 0; i < targetDatas.size() - 1; ++i) {
+ if (targetDatas[i] != w)
+ continue;
+
+ return targetDatas[i + 1];
+ }
+
+ return nullptr;
+}
+
+SP<SScrollingTargetData> SColumnData::prev(SP<SScrollingTargetData> w) {
+ for (size_t i = 1; i < targetDatas.size(); ++i) {
+ if (targetDatas[i] != w)
+ continue;
+
+ return targetDatas[i - 1];
+ }
+
+ return nullptr;
+}
+
+bool SColumnData::has(SP<ITarget> t) {
+ return std::ranges::find_if(targetDatas, [t](const auto& e) { return e->target == t; }) != targetDatas.end();
+}
+
+SScrollingData::SScrollingData(CScrollingAlgorithm* algo) : algorithm(algo) {
+ controller = makeUnique<CScrollTapeController>(SCROLL_DIR_RIGHT);
+}
+
+SP<SColumnData> SScrollingData::add() {
+ static const auto PCOLWIDTH = CConfigValue<Hyprlang::FLOAT>("scrolling:column_width");
+ auto col = columns.emplace_back(makeShared<SColumnData>(self.lock()));
+ col->self = col;
+
+ size_t stripIdx = controller->addStrip(*PCOLWIDTH);
+ controller->getStrip(stripIdx).userData = col;
+
+ return col;
+}
+
+SP<SColumnData> SScrollingData::add(int after) {
+ static const auto PCOLWIDTH = CConfigValue<Hyprlang::FLOAT>("scrolling:column_width");
+ auto col = makeShared<SColumnData>(self.lock());
+ col->self = col;
+ columns.insert(columns.begin() + after + 1, col);
+
+ controller->insertStrip(after, *PCOLWIDTH);
+ controller->getStrip(after + 1).userData = col;
+
+ return col;
+}
+
+int64_t SScrollingData::idx(SP<SColumnData> c) {
+ for (size_t i = 0; i < columns.size(); ++i) {
+ if (columns[i] == c)
+ return i;
+ }
+
+ return -1;
+}
+
+void SScrollingData::remove(SP<SColumnData> c) {
+ // find index before removing
+ int64_t index = idx(c);
+
+ std::erase(columns, c);
+
+ // sync with controller
+ if (index >= 0)
+ controller->removeStrip(index);
+}
+
+SP<SColumnData> SScrollingData::next(SP<SColumnData> c) {
+ for (size_t i = 0; i < columns.size(); ++i) {
+ if (columns[i] != c)
+ continue;
+
+ if (i == columns.size() - 1)
+ return nullptr;
+
+ return columns[i + 1];
+ }
+
+ return nullptr;
+}
+
+SP<SColumnData> SScrollingData::prev(SP<SColumnData> c) {
+ for (size_t i = 0; i < columns.size(); ++i) {
+ if (columns[i] != c)
+ continue;
+
+ if (i == 0)
+ return nullptr;
+
+ return columns[i - 1];
+ }
+
+ return nullptr;
+}
+
+void SScrollingData::centerCol(SP<SColumnData> c) {
+ if (!c)
+ return;
+
+ static const auto PFSONONE = CConfigValue<Hyprlang::INT>("scrolling:fullscreen_on_one_column");
+ const auto USABLE = algorithm->usableArea();
+ int64_t colIdx = idx(c);
+
+ if (colIdx >= 0)
+ controller->centerStrip(colIdx, USABLE, *PFSONONE);
+}
+
+void SScrollingData::fitCol(SP<SColumnData> c) {
+ if (!c)
+ return;
+
+ static const auto PFSONONE = CConfigValue<Hyprlang::INT>("scrolling:fullscreen_on_one_column");
+ const auto USABLE = algorithm->usableArea();
+ int64_t colIdx = idx(c);
+
+ if (colIdx >= 0)
+ controller->fitStrip(colIdx, USABLE, *PFSONONE);
+}
+
+void SScrollingData::centerOrFitCol(SP<SColumnData> c) {
+ if (!c)
+ return;
+
+ static const auto PFITMETHOD = CConfigValue<Hyprlang::INT>("scrolling:focus_fit_method");
+
+ if (*PFITMETHOD == 1)
+ fitCol(c);
+ else
+ centerCol(c);
+}
+
+SP<SColumnData> SScrollingData::atCenter() {
+ static const auto PFSONONE = CConfigValue<Hyprlang::INT>("scrolling:fullscreen_on_one_column");
+ const auto USABLE = algorithm->usableArea();
+
+ size_t centerIdx = controller->getStripAtCenter(USABLE, *PFSONONE);
+
+ if (centerIdx < columns.size())
+ return columns[centerIdx];
+
+ return nullptr;
+}
+
+void SScrollingData::recalculate(bool forceInstant) {
+ if (algorithm->m_parent->space()->workspace()->m_hasFullscreenWindow)
+ return;
+
+ static const auto PFSONONE = CConfigValue<Hyprlang::INT>("scrolling:fullscreen_on_one_column");
+
+ const CBox USABLE = algorithm->usableArea();
+ const auto WORKAREA = algorithm->m_parent->space()->workArea();
+
+ controller->setDirection(algorithm->getDynamicDirection());
+
+ for (size_t i = 0; i < columns.size(); ++i) {
+ const auto& COL = columns[i];
+
+ for (size_t j = 0; j < COL->targetDatas.size(); ++j) {
+ const auto& TARGET = COL->targetDatas[j];
+
+ TARGET->layoutBox = controller->calculateTargetBox(i, j, USABLE, WORKAREA.pos(), *PFSONONE);
+
+ if (TARGET->target)
+ TARGET->target->setPositionGlobal(TARGET->layoutBox);
+ if (forceInstant && TARGET->target)
+ TARGET->target->warpPositionSize();
+ }
+ }
+}
+
+double SScrollingData::maxWidth() {
+ static const auto PFSONONE = CConfigValue<Hyprlang::INT>("scrolling:fullscreen_on_one_column");
+ const auto USABLE = algorithm->usableArea();
+
+ return controller->calculateMaxExtent(USABLE, *PFSONONE);
+}
+
+bool SScrollingData::visible(SP<SColumnData> c) {
+ static const auto PFSONONE = CConfigValue<Hyprlang::INT>("scrolling:fullscreen_on_one_column");
+ const auto USABLE = algorithm->usableArea();
+ int64_t colIdx = idx(c);
+
+ if (colIdx >= 0)
+ return controller->isStripVisible(colIdx, USABLE, *PFSONONE);
+
+ return false;
+}
+
+CScrollingAlgorithm::CScrollingAlgorithm() {
+ static const auto PCONFWIDTHS = CConfigValue<Hyprlang::STRING>("scrolling:explicit_column_widths");
+ static const auto PCONFDIRECTION = CConfigValue<Hyprlang::STRING>("scrolling:direction");
+
+ m_scrollingData = makeShared<SScrollingData>(this);
+ m_scrollingData->self = m_scrollingData;
+
+ // Helper to parse direction string
+ auto parseDirection = [](const std::string& dir) -> eScrollDirection {
+ if (dir == "left")
+ return SCROLL_DIR_LEFT;
+ else if (dir == "down")
+ return SCROLL_DIR_DOWN;
+ else if (dir == "up")
+ return SCROLL_DIR_UP;
+ else
+ return SCROLL_DIR_RIGHT; // default
+ };
+
+ m_configCallback = g_pHookSystem->hookDynamic("configReloaded", [this, parseDirection](void* hk, SCallbackInfo& info, std::any param) {
+ static const auto PCONFDIRECTION = CConfigValue<Hyprlang::STRING>("scrolling:direction");
+
+ m_config.configuredWidths.clear();
+
+ CConstVarList widths(*PCONFWIDTHS, 0, ',');
+ for (auto& w : widths) {
+ try {
+ m_config.configuredWidths.emplace_back(std::stof(std::string{w}));
+ } catch (...) { Log::logger->log(Log::ERR, "scrolling: Failed to parse width {} as float", w); }
+ }
+ if (m_config.configuredWidths.empty())
+ m_config.configuredWidths = {0.333, 0.5, 0.667, 1.0};
+
+ // Update scroll direction
+ m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION));
+ });
+
+ m_mouseButtonCallback = g_pHookSystem->hookDynamic("mouseButton", [this](void* self, SCallbackInfo& info, std::any e) {
+ auto E = std::any_cast<IPointer::SButtonEvent>(e);
+ if (E.state == WL_POINTER_BUTTON_STATE_RELEASED && Desktop::focusState()->window())
+ focusOnInput(Desktop::focusState()->window()->layoutTarget(), true);
+ });
+
+ m_focusCallback = g_pHookSystem->hookDynamic("activeWindow", [this](void* hk, SCallbackInfo& info, std::any param) {
+ const auto E = std::any_cast<Desktop::View::SWindowActiveEvent>(param);
+ const auto PWINDOW = E.window;
+
+ if (!PWINDOW)
+ return;
+
+ static const auto PFOLLOW_FOCUS = CConfigValue<Hyprlang::INT>("scrolling:follow_focus");
+
+ if (!*PFOLLOW_FOCUS && !Desktop::isHardInputFocusReason(E.reason))
+ return;
+
+ if (PWINDOW->m_workspace != m_parent->space()->workspace())
+ return;
+
+ const auto TARGET = PWINDOW->layoutTarget();
+ if (!TARGET || TARGET->floating())
+ return;
+
+ focusOnInput(TARGET, Desktop::isHardInputFocusReason(E.reason));
+ });
+
+ // Initialize default widths and direction
+ m_config.configuredWidths = {0.333, 0.5, 0.667, 1.0};
+ m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION));
+}
+
+CScrollingAlgorithm::~CScrollingAlgorithm() {
+ m_configCallback.reset();
+ m_focusCallback.reset();
+}
+
+void CScrollingAlgorithm::focusOnInput(SP<ITarget> target, bool hardInput) {
+ static const auto PFOLLOW_FOCUS_MIN_PERC = CConfigValue<Hyprlang::FLOAT>("scrolling:follow_min_visible");
+
+ if (!target || target->space() != m_parent->space())
+ return;
+
+ const auto TARGETDATA = dataFor(target);
+ if (!TARGETDATA)
+ return;
+
+ if (*PFOLLOW_FOCUS_MIN_PERC > 0.F && !hardInput) {
+ // check how much of the window is visible, unless hard input focus
+
+ const auto IS_HORIZ = m_scrollingData->controller->isPrimaryHorizontal();
+
+ const auto MON_BOX = m_parent->space()->workspace()->m_monitor->logicalBox();
+ const auto TARGET_POS = target->position();
+ const double VISIBLE_LEN = IS_HORIZ ? //
+ std::abs(std::min(MON_BOX.x + MON_BOX.w, TARGET_POS.x + TARGET_POS.w) - (std::max(MON_BOX.x, TARGET_POS.x))) //
+ :
+ std::abs(std::min(MON_BOX.y + MON_BOX.h, TARGET_POS.y + TARGET_POS.h) - (std::max(MON_BOX.y, TARGET_POS.y)));
+
+ // if the amount of visible X is below minimum, reject
+ if (VISIBLE_LEN < (IS_HORIZ ? MON_BOX.w : MON_BOX.h) * std::clamp(*PFOLLOW_FOCUS_MIN_PERC, 0.F, 1.F))
+ return;
+ }
+
+ static const auto PFITMETHOD = CConfigValue<Hyprlang::INT>("scrolling:focus_fit_method");
+ if (*PFITMETHOD == 1)
+ m_scrollingData->fitCol(TARGETDATA->column.lock());
+ else
+ m_scrollingData->centerCol(TARGETDATA->column.lock());
+ m_scrollingData->recalculate();
+}
+
+void CScrollingAlgorithm::newTarget(SP<ITarget> target) {
+ auto droppingOn = Desktop::focusState()->window();
+
+ if (droppingOn && droppingOn->layoutTarget() == target)
+ droppingOn = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS);
+
+ SP<SScrollingTargetData> droppingData = droppingOn ? dataFor(droppingOn->layoutTarget()) : nullptr;
+ SP<SColumnData> droppingColumn = droppingData ? droppingData->column.lock() : nullptr;
+
+ if (!droppingColumn) {
+ auto col = m_scrollingData->add();
+ col->add(target);
+ m_scrollingData->fitCol(col);
+ } else {
+ if (g_layoutManager->dragController()->wasDraggingWindow() && g_layoutManager->dragController()->draggingTiled()) {
+ if (droppingOn) {
+ const auto IDX = droppingColumn->idx(droppingOn->layoutTarget());
+ const auto TOP = droppingOn->getWindowIdealBoundingBoxIgnoreReserved().middle().y > g_pInputManager->getMouseCoordsInternal().y;
+ droppingColumn->add(target, TOP ? (IDX == 0 ? -1 : IDX - 1) : (IDX));
+ } else
+ droppingColumn->add(target);
+ m_scrollingData->fitCol(droppingColumn);
+ } else {
+ auto idx = m_scrollingData->idx(droppingColumn);
+ auto col = idx == -1 ? m_scrollingData->add() : m_scrollingData->add(idx);
+ col->add(target);
+ m_scrollingData->fitCol(col);
+ }
+ }
+
+ m_scrollingData->recalculate();
+}
+
+void CScrollingAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {
+ newTarget(target);
+}
+
+void CScrollingAlgorithm::removeTarget(SP<ITarget> target) {
+ const auto DATA = dataFor(target);
+
+ if (!DATA)
+ return;
+
+ if (!m_scrollingData->next(DATA->column.lock()) && DATA->column->targetDatas.size() <= 1) {
+ // move the view if this is the last column
+ const auto USABLE = usableArea();
+ m_scrollingData->controller->adjustOffset(-(USABLE.w * DATA->column->getColumnWidth()));
+ }
+
+ DATA->column->remove(target);
+
+ if (!DATA->column) {
+ // column got removed, let's ensure we don't leave any cringe extra space
+ const auto USABLE = usableArea();
+ double newOffset = std::clamp(m_scrollingData->controller->getOffset(), 0.0, std::max(m_scrollingData->maxWidth() - USABLE.w, 1.0));
+ m_scrollingData->controller->setOffset(newOffset);
+ }
+
+ m_scrollingData->recalculate();
+}
+
+void CScrollingAlgorithm::resizeTarget(const Vector2D& delta, SP<ITarget> target, eRectCorner corner) {
+ if (!validMapped(target->window()))
+ return;
+
+ const auto DATA = dataFor(target);
+
+ if (!DATA) {
+ const auto PWINDOW = target->window();
+ *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + delta)
+ .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}),
+ PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}));
+ PWINDOW->updateWindowDecos();
+ return;
+ }
+
+ if (!DATA->column || !DATA->column->scrollingData)
+ return;
+
+ static const auto PFSONONE = CConfigValue<Hyprlang::INT>("scrolling:fullscreen_on_one_column");
+
+ const auto ADJUSTED_DELTA = m_scrollingData->controller->isPrimaryHorizontal() ? delta : Vector2D{delta.y, delta.x};
+ const auto USABLE = usableArea();
+ const auto DELTA_AS_PERC = ADJUSTED_DELTA / USABLE.size();
+ Vector2D modDelta = ADJUSTED_DELTA;
+
+ const auto CURR_COLUMN = DATA->column.lock();
+ const int64_t COL_IDX = m_scrollingData->idx(CURR_COLUMN);
+
+ if (COL_IDX < 0)
+ return;
+
+ const double currentStart = m_scrollingData->controller->calculateStripStart(COL_IDX, USABLE, *PFSONONE);
+ const double currentSize = m_scrollingData->controller->calculateStripSize(COL_IDX, USABLE, *PFSONONE);
+ const double currentEnd = currentStart + currentSize;
+
+ const double cameraOffset = m_scrollingData->controller->getOffset();
+ const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal();
+ const double usablePrimary = isPrimaryHoriz ? USABLE.w : USABLE.h;
+
+ const double onScreenStart = currentStart - cameraOffset;
+ const double onScreenEnd = currentEnd - cameraOffset;
+
+ // set the offset because we'll prevent centering during a drag
+ m_scrollingData->controller->setOffset(cameraOffset);
+
+ const bool RESIZING_LEFT = isPrimaryHoriz ? corner == CORNER_BOTTOMLEFT || corner == CORNER_TOPLEFT : corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT;
+
+ if (RESIZING_LEFT) {
+ // resize from left edge (inner edge) - grow/shrink column width and adjust offset to keep RIGHT edge stationary
+ const float oldWidth = CURR_COLUMN->getColumnWidth();
+ const float requestedDelta = -(float)DELTA_AS_PERC.x; // negative delta means grow when dragging left
+ float actualDelta = requestedDelta;
+
+ // clamp delta so we don't shrink below MIN or grow above MAX
+ const float newWidthUnclamped = oldWidth + actualDelta;
+ const float newWidthClamped = std::clamp(newWidthUnclamped, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH);
+ actualDelta = newWidthClamped - oldWidth;
+
+ if (actualDelta * usablePrimary > onScreenStart)
+ actualDelta = onScreenStart / usablePrimary;
+
+ if (actualDelta != 0.F) {
+ CURR_COLUMN->setColumnWidth(oldWidth + actualDelta);
+ // adjust camera offset so the RIGHT edge stays stationary on screen
+ // when column grows (actualDelta > 0), we need to increase offset by the same amount
+ m_scrollingData->controller->adjustOffset(actualDelta * usablePrimary);
+ }
+
+ } else {
+ // resize from right edge (outer edge) - adjust column width only, keep left edge fixed
+ const float oldWidth = CURR_COLUMN->getColumnWidth();
+ const float requestedDelta = (float)DELTA_AS_PERC.x;
+ float actualDelta = requestedDelta;
+
+ // clamp delta so we don't shrink below MIN or grow above MAX
+ const float newWidthUnclamped = oldWidth + actualDelta;
+ const float newWidthClamped = std::clamp(newWidthUnclamped, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH);
+ actualDelta = newWidthClamped - oldWidth;
+
+ // also clamp so right edge doesn't go past right viewport boundary
+ if (onScreenEnd + (actualDelta * usablePrimary) > usablePrimary)
+ actualDelta = (usablePrimary - onScreenEnd) / usablePrimary;
+
+ if (actualDelta != 0.F)
+ CURR_COLUMN->setColumnWidth(oldWidth + actualDelta);
+ }
+
+ if (DATA->column->targetDatas.size() > 1) {
+ const auto& CURR_TD = DATA;
+ const auto NEXT_TD = DATA->column->next(DATA);
+ const auto PREV_TD = DATA->column->prev(DATA);
+ if (corner == CORNER_NONE) {
+ if (!PREV_TD)
+ corner = CORNER_BOTTOMRIGHT;
+ else {
+ corner = CORNER_TOPRIGHT;
+ modDelta.y *= -1.0f;
+ }
+ }
+
+ switch (corner) {
+ case CORNER_BOTTOMLEFT:
+ case CORNER_BOTTOMRIGHT: {
+ if (!NEXT_TD)
+ break;
+
+ float nextSize = CURR_COLUMN->getTargetSize(NEXT_TD);
+ float currSize = CURR_COLUMN->getTargetSize(CURR_TD);
+
+ if (nextSize <= MIN_ROW_HEIGHT && delta.y >= 0)
+ break;
+
+ float adjust = std::clamp((float)(delta.y / USABLE.h), (-currSize + MIN_ROW_HEIGHT), (nextSize - MIN_ROW_HEIGHT));
+
+ CURR_COLUMN->setTargetSize(NEXT_TD, std::clamp(nextSize - adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT));
+ CURR_COLUMN->setTargetSize(CURR_TD, std::clamp(currSize + adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT));
+ break;
+ }
+ case CORNER_TOPLEFT:
+ case CORNER_TOPRIGHT: {
+ if (!PREV_TD)
+ break;
+
+ float prevSize = CURR_COLUMN->getTargetSize(PREV_TD);
+ float currSize = CURR_COLUMN->getTargetSize(CURR_TD);
+
+ if ((prevSize <= MIN_ROW_HEIGHT && modDelta.y <= 0) || (currSize <= MIN_ROW_HEIGHT && delta.y >= 0))
+ break;
+
+ float adjust = std::clamp((float)(modDelta.y / USABLE.h), -(prevSize - MIN_ROW_HEIGHT), (currSize - MIN_ROW_HEIGHT));
+
+ CURR_COLUMN->setTargetSize(PREV_TD, std::clamp(prevSize + adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT));
+ CURR_COLUMN->setTargetSize(CURR_TD, std::clamp(currSize - adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT));
+ break;
+ }
+
+ default: break;
+ }
+ }
+
+ m_scrollingData->recalculate(true);
+}
+
+void CScrollingAlgorithm::recalculate() {
+ if (Desktop::focusState()->window())
+ focusOnInput(Desktop::focusState()->window()->layoutTarget(), true);
+
+ m_scrollingData->recalculate();
+}
+
+SP<SScrollingTargetData> CScrollingAlgorithm::closestNode(const Vector2D& posGlobglobgabgalab) {
+ SP<SScrollingTargetData> res = nullptr;
+ double distClosest = -1;
+ for (auto& c : m_scrollingData->columns) {
+ for (auto& n : c->targetDatas) {
+ if (n->target && Desktop::View::validMapped(n->target->window())) {
+ auto distAnother = vecToRectDistanceSquared(posGlobglobgabgalab, n->layoutBox.pos(), n->layoutBox.pos() + n->layoutBox.size());
+ if (!res || distAnother < distClosest) {
+ res = n;
+ distClosest = distAnother;
+ }
+ }
+ }
+ }
+ return res;
+}
+
+SP<ITarget> CScrollingAlgorithm::getNextCandidate(SP<ITarget> old) {
+ const auto CENTER = old->position().middle();
+
+ const auto NODE = closestNode(CENTER);
+
+ if (!NODE)
+ return nullptr;
+
+ return NODE->target.lock();
+}
+
+void CScrollingAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
+ auto nodeA = dataFor(a);
+ auto nodeB = dataFor(b);
+
+ if (nodeA)
+ nodeA->target = b;
+ if (nodeB)
+ nodeB->target = a;
+
+ m_scrollingData->recalculate();
+}
+
+void CScrollingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
+ moveTargetTo(t, dir, silent);
+}
+
+void CScrollingAlgorithm::moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent) {
+ const auto DATA = dataFor(t);
+
+ if (!DATA)
+ return;
+
+ const auto TAPE_DIR = getDynamicDirection();
+ const auto CURRENT_COL = DATA->column.lock();
+ const auto current_idx = m_scrollingData->idx(CURRENT_COL);
+
+ if (dir == Math::DIRECTION_LEFT) {
+ const auto COL = m_scrollingData->prev(DATA->column.lock());
+
+ // ignore moves to the "origin" when on first column and moving opposite to tape direction
+ if (!COL && current_idx == 0 && (TAPE_DIR == SCROLL_DIR_RIGHT || TAPE_DIR == SCROLL_DIR_DOWN))
+ return;
+
+ DATA->column->remove(t);
+
+ if (!COL) {
+ const auto NEWCOL = m_scrollingData->add(-1);
+ NEWCOL->add(DATA);
+ m_scrollingData->centerOrFitCol(NEWCOL);
+ } else {
+ if (COL->targetDatas.size() > 0)
+ COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y));
+ else
+ COL->add(DATA);
+ m_scrollingData->centerOrFitCol(COL);
+ }
+ } else if (dir == Math::DIRECTION_RIGHT) {
+ const auto COL = m_scrollingData->next(DATA->column.lock());
+
+ // ignore moves to the "origin" when on last column and moving opposite to tape direction
+ if (!COL && current_idx == (int64_t)m_scrollingData->columns.size() - 1 && (TAPE_DIR == SCROLL_DIR_LEFT || TAPE_DIR == SCROLL_DIR_UP))
+ return;
+
+ DATA->column->remove(t);
+
+ if (!COL) {
+ // make a new one
+ const auto NEWCOL = m_scrollingData->add();
+ NEWCOL->add(DATA);
+ m_scrollingData->centerOrFitCol(NEWCOL);
+ } else {
+ if (COL->targetDatas.size() > 0)
+ COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y));
+ else
+ COL->add(DATA);
+ m_scrollingData->centerOrFitCol(COL);
+ }
+
+ } else if (dir == Math::DIRECTION_UP)
+ DATA->column->up(DATA);
+ else if (dir == Math::DIRECTION_DOWN)
+ DATA->column->down(DATA);
+
+ m_scrollingData->recalculate();
+ focusTargetUpdate(t);
+ if (t->window())
+ g_pCompositor->warpCursorTo(t->window()->middle());
+}
+
+std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::string_view& sv) {
+ auto centerOrFit = [this](const SP<SColumnData> COL) -> void {
+ static const auto PFITMETHOD = CConfigValue<Hyprlang::INT>("scrolling:focus_fit_method");
+ if (*PFITMETHOD == 1)
+ m_scrollingData->fitCol(COL);
+ else
+ m_scrollingData->centerCol(COL);
+ };
+
+ const auto ARGS = CVarList(std::string{sv}, 0, ' ');
+ if (ARGS[0] == "move") {
+ if (ARGS[1] == "+col" || ARGS[1] == "col") {
+ const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);
+ if (!TDATA)
+ return std::unexpected("no window");
+
+ const auto COL = m_scrollingData->next(TDATA->column.lock());
+ if (!COL) {
+ // move to max
+ double maxOffset = m_scrollingData->maxWidth();
+ m_scrollingData->controller->setOffset(maxOffset);
+ m_scrollingData->recalculate();
+ focusTargetUpdate(nullptr);
+ return {};
+ }
+
+ centerOrFit(COL);
+ m_scrollingData->recalculate();
+
+ focusTargetUpdate(COL->targetDatas.front()->target.lock());
+ if (COL->targetDatas.front()->target->window())
+ g_pCompositor->warpCursorTo(COL->targetDatas.front()->target->window()->middle());
+
+ return {};
+ } else if (ARGS[1] == "-col") {
+ const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);
+ if (!TDATA) {
+ if (m_scrollingData->columns.size() > 0) {
+ m_scrollingData->centerCol(m_scrollingData->columns.back());
+ m_scrollingData->recalculate();
+ focusTargetUpdate((m_scrollingData->columns.back()->targetDatas.back())->target.lock());
+ if (m_scrollingData->columns.back()->targetDatas.back()->target->window())
+ g_pCompositor->warpCursorTo((m_scrollingData->columns.back()->targetDatas.back())->target->window()->middle());
+ }
+
+ return {};
+ }
+
+ const auto COL = m_scrollingData->prev(TDATA->column.lock());
+ if (!COL)
+ return {};
+
+ centerOrFit(COL);
+ m_scrollingData->recalculate();
+
+ focusTargetUpdate(COL->targetDatas.back()->target.lock());
+ if (COL->targetDatas.front()->target->window())
+ g_pCompositor->warpCursorTo(COL->targetDatas.front()->target->window()->middle());
+
+ return {};
+ }
+
+ const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0);
+
+ if (!PLUSMINUS.has_value())
+ return std::unexpected("failed to parse offset");
+
+ m_scrollingData->controller->adjustOffset(-(*PLUSMINUS));
+ m_scrollingData->recalculate();
+
+ const auto ATCENTER = m_scrollingData->atCenter();
+
+ focusTargetUpdate(ATCENTER ? (*ATCENTER->targetDatas.begin())->target.lock() : nullptr);
+ } else if (ARGS[0] == "colresize") {
+ const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);
+
+ if (!TDATA)
+ return {};
+
+ if (ARGS[1] == "all") {
+ float abs = 0;
+ try {
+ abs = std::stof(ARGS[2]);
+ } catch (...) { return {}; }
+
+ for (const auto& c : m_scrollingData->columns) {
+ c->setColumnWidth(abs);
+ }
+
+ m_scrollingData->recalculate();
+ return {};
+ }
+
+ CScopeGuard x([this, TDATA] {
+ auto col = TDATA->column.lock();
+ if (col) {
+ col->setColumnWidth(std::clamp(col->getColumnWidth(), MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH));
+ m_scrollingData->centerOrFitCol(col);
+ }
+ m_scrollingData->recalculate();
+ });
+
+ if (ARGS[1][0] == '+' || ARGS[1][0] == '-') {
+ if (ARGS[1] == "+conf") {
+ auto col = TDATA->column.lock();
+ if (col) {
+ for (size_t i = 0; i < m_config.configuredWidths.size(); ++i) {
+ if (m_config.configuredWidths[i] > col->getColumnWidth()) {
+ col->setColumnWidth(m_config.configuredWidths[i]);
+ break;
+ }
+
+ if (i == m_config.configuredWidths.size() - 1)
+ col->setColumnWidth(m_config.configuredWidths[0]);
+ }
+ }
+
+ return {};
+ } else if (ARGS[1] == "-conf") {
+ auto col = TDATA->column.lock();
+ if (col) {
+ for (size_t i = m_config.configuredWidths.size() - 1;; --i) {
+ if (m_config.configuredWidths[i] < col->getColumnWidth()) {
+ col->setColumnWidth(m_config.configuredWidths[i]);
+ break;
+ }
+
+ if (i == 0) {
+ col->setColumnWidth(m_config.configuredWidths.back());
+ break;
+ }
+ }
+ }
+
+ return {};
+ }
+
+ const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0);
+
+ if (!PLUSMINUS.has_value())
+ return {};
+
+ auto col = TDATA->column.lock();
+ if (col)
+ col->setColumnWidth(col->getColumnWidth() + *PLUSMINUS);
+ } else {
+ float abs = 0;
+ try {
+ abs = std::stof(ARGS[1]);
+ } catch (...) { return {}; }
+
+ auto col = TDATA->column.lock();
+ if (col)
+ col->setColumnWidth(abs);
+ }
+ } else if (ARGS[0] == "fit") {
+ const auto PWINDOW = Desktop::focusState()->window();
+
+ if (!PWINDOW)
+ return std::unexpected("no focused window");
+
+ const auto WDATA = dataFor(PWINDOW->layoutTarget());
+
+ if (!WDATA || m_scrollingData->columns.size() == 0)
+ return std::unexpected("can't fit: no window or columns");
+
+ if (ARGS[1] == "active") {
+ // fit the current column to 1.F
+ const auto USABLE = usableArea();
+
+ WDATA->column->setColumnWidth(1.F);
+
+ double off = 0.F;
+ for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) {
+ if (m_scrollingData->columns[i]->has(PWINDOW->layoutTarget()))
+ break;
+
+ off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth();
+ }
+
+ m_scrollingData->controller->setOffset(off);
+ m_scrollingData->recalculate();
+ } else if (ARGS[1] == "all") {
+ // fit all columns on screen
+ const size_t LEN = m_scrollingData->columns.size();
+ for (const auto& c : m_scrollingData->columns) {
+ c->setColumnWidth(1.F / (float)LEN);
+ }
+
+ m_scrollingData->controller->setOffset(0);
+ m_scrollingData->recalculate();
+ } else if (ARGS[1] == "toend") {
+ // fit all columns on screen that start from the current and end on the last
+ bool begun = false;
+ size_t foundAt = 0;
+ for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) {
+ if (!begun && !m_scrollingData->columns[i]->has(PWINDOW->layoutTarget()))
+ continue;
+
+ if (!begun) {
+ begun = true;
+ foundAt = i;
+ }
+
+ m_scrollingData->columns[i]->setColumnWidth(1.F / (float)(m_scrollingData->columns.size() - foundAt));
+ }
+
+ if (!begun)
+ return std::unexpected("couldn't find beginning");
+
+ const auto USABLE = usableArea();
+
+ double off = 0;
+ for (size_t i = 0; i < foundAt; ++i) {
+ off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth();
+ }
+
+ m_scrollingData->controller->setOffset(off);
+ m_scrollingData->recalculate();
+ } else if (ARGS[1] == "tobeg") {
+ // fit all columns on screen that start from the current and end on the last
+ bool begun = false;
+ size_t foundAt = 0;
+ for (int64_t i = (int64_t)m_scrollingData->columns.size() - 1; i >= 0; --i) {
+ if (!begun && !m_scrollingData->columns[i]->has(PWINDOW->layoutTarget()))
+ continue;
+
+ if (!begun) {
+ begun = true;
+ foundAt = i;
+ }
+
+ m_scrollingData->columns[i]->setColumnWidth(1.F / (float)(foundAt + 1));
+ }
+
+ if (!begun)
+ return {};
+
+ m_scrollingData->controller->setOffset(0);
+ m_scrollingData->recalculate();
+ } else if (ARGS[1] == "visible") {
+ // fit all columns on screen that start from the current and end on the last
+
+ bool begun = false;
+ size_t foundAt = 0;
+ std::vector<SP<SColumnData>> visible;
+ for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) {
+ if (!begun && !m_scrollingData->visible(m_scrollingData->columns[i]))
+ continue;
+
+ if (!begun) {
+ begun = true;
+ foundAt = i;
+ }
+
+ if (!m_scrollingData->visible(m_scrollingData->columns[i]))
+ break;
+
+ visible.emplace_back(m_scrollingData->columns[i]);
+ }
+
+ if (!begun)
+ return {};
+
+ double off = 0;
+
+ if (foundAt != 0) {
+ const auto USABLE = usableArea();
+
+ for (size_t i = 0; i < foundAt; ++i) {
+ off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth();
+ }
+ }
+
+ for (const auto& v : visible) {
+ v->setColumnWidth(1.F / (float)visible.size());
+ }
+
+ m_scrollingData->controller->setOffset(off);
+ m_scrollingData->recalculate();
+ }
+ } else if (ARGS[0] == "focus") {
+ const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);
+ static const auto PNOFALLBACK = CConfigValue<Hyprlang::INT>("general:no_focus_fallback");
+
+ if (!TDATA || ARGS[1].empty())
+ return std::unexpected("no window to focus");
+
+ // Determine if we're in vertical scroll mode (strips are horizontal)
+ const bool isVerticalScroll = (getDynamicDirection() == SCROLL_DIR_DOWN || getDynamicDirection() == SCROLL_DIR_UP);
+
+ // Map direction keys based on scroll mode:
+ // Horizontal scroll (RIGHT/LEFT): u/d move within strip, l/r move between strips
+ // Vertical scroll (DOWN/UP): l/r move within strip, u/d move between strips
+ char dirChar = ARGS[1][0];
+
+ // Convert to semantic directions
+ bool isPrevInStrip = (!isVerticalScroll && (dirChar == 'u' || dirChar == 't')) || (isVerticalScroll && dirChar == 'l');
+ bool isNextInStrip = (!isVerticalScroll && (dirChar == 'b' || dirChar == 'd')) || (isVerticalScroll && dirChar == 'r');
+ bool isPrevStrip = (!isVerticalScroll && dirChar == 'l') || (isVerticalScroll && (dirChar == 'u' || dirChar == 't'));
+ bool isNextStrip = (!isVerticalScroll && dirChar == 'r') || (isVerticalScroll && (dirChar == 'b' || dirChar == 'd'));
+
+ if (isPrevInStrip) {
+ // Move to previous target within current strip
+ auto PREV = TDATA->column->prev(TDATA);
+ if (!PREV) {
+ if (!*PNOFALLBACK)
+ PREV = TDATA->column->targetDatas.back();
+ else
+ return std::unexpected("fallback disabled (no target)");
+ }
+
+ focusTargetUpdate(PREV->target.lock());
+ if (PREV->target->window())
+ g_pCompositor->warpCursorTo(PREV->target->window()->middle());
+ } else if (isNextInStrip) {
+ // Move to next target within current strip
+ auto NEXT = TDATA->column->next(TDATA);
+ if (!NEXT) {
+ if (!*PNOFALLBACK)
+ NEXT = TDATA->column->targetDatas.front();
+ else
+ return std::unexpected("fallback disabled (no target)");
+ }
+
+ focusTargetUpdate(NEXT->target.lock());
+ if (NEXT->target->window())
+ g_pCompositor->warpCursorTo(NEXT->target->window()->middle());
+ } else if (isPrevStrip) {
+ // Move to previous strip
+ auto PREV = m_scrollingData->prev(TDATA->column.lock());
+ if (!PREV) {
+ if (*PNOFALLBACK) {
+ centerOrFit(TDATA->column.lock());
+ m_scrollingData->recalculate();
+ if (TDATA->target->window())
+ g_pCompositor->warpCursorTo(TDATA->target->window()->middle());
+ return {};
+ } else
+ PREV = m_scrollingData->columns.back();
+ }
+
+ auto pTargetData = findBestNeighbor(TDATA, PREV);
+ if (pTargetData) {
+ focusTargetUpdate(pTargetData->target.lock());
+ centerOrFit(PREV);
+ m_scrollingData->recalculate();
+ if (pTargetData->target->window())
+ g_pCompositor->warpCursorTo(pTargetData->target->window()->middle());
+ }
+ } else if (isNextStrip) {
+ // Move to next strip
+ auto NEXT = m_scrollingData->next(TDATA->column.lock());
+ if (!NEXT) {
+ if (*PNOFALLBACK) {
+ centerOrFit(TDATA->column.lock());
+ m_scrollingData->recalculate();
+ if (TDATA->target->window())
+ g_pCompositor->warpCursorTo(TDATA->target->window()->middle());
+ return {};
+ } else
+ NEXT = m_scrollingData->columns.front();
+ }
+
+ auto pTargetData = findBestNeighbor(TDATA, NEXT);
+ if (pTargetData) {
+ focusTargetUpdate(pTargetData->target.lock());
+ centerOrFit(NEXT);
+ m_scrollingData->recalculate();
+ if (pTargetData->target->window())
+ g_pCompositor->warpCursorTo(pTargetData->target->window()->middle());
+ }
+ }
+ } else if (ARGS[0] == "promote") {
+ const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);
+
+ if (!TDATA)
+ return std::unexpected("no window focused");
+
+ auto idx = m_scrollingData->idx(TDATA->column.lock());
+ auto col = idx == -1 ? m_scrollingData->add() : m_scrollingData->add(idx);
+
+ TDATA->column->remove(TDATA->target.lock());
+
+ col->add(TDATA);
+
+ m_scrollingData->recalculate();
+ } else if (ARGS[0] == "swapcol") {
+ if (ARGS.size() < 2)
+ return std::unexpected("not enough args");
+
+ const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);
+ if (!TDATA)
+ return std::unexpected("no window");
+
+ const auto CURRENT_COL = TDATA->column.lock();
+ if (!CURRENT_COL)
+ return std::unexpected("no current col");
+
+ if (m_scrollingData->columns.size() < 2)
+ return std::unexpected("not enough columns to swap");
+
+ const int64_t currentIdx = m_scrollingData->idx(CURRENT_COL);
+ const size_t colCount = m_scrollingData->columns.size();
+
+ if (currentIdx == -1)
+ return std::unexpected("no current column");
+
+ const std::string& direction = ARGS[1];
+ int64_t targetIdx = -1;
+
+ // wrap around swaps
+ if (direction == "l")
+ targetIdx = (currentIdx == 0) ? (colCount - 1) : (currentIdx - 1);
+ else if (direction == "r")
+ targetIdx = (currentIdx == (int64_t)colCount - 1) ? 0 : (currentIdx + 1);
+ else
+ return std::unexpected("no target (invalid direction?)");
+ ;
+
+ std::swap(m_scrollingData->columns.at(currentIdx), m_scrollingData->columns.at(targetIdx));
+
+ m_scrollingData->controller->swapStrips(currentIdx, targetIdx);
+
+ m_scrollingData->centerOrFitCol(CURRENT_COL);
+ m_scrollingData->recalculate();
+ } else
+ return std::unexpected("no such layoutmsg for scrolling");
+
+ return {};
+}
+
+std::optional<Vector2D> CScrollingAlgorithm::predictSizeForNewTarget() {
+ return std::nullopt;
+}
+
+void CScrollingAlgorithm::focusTargetUpdate(SP<ITarget> target) {
+ if (!target || !validMapped(target->window())) {
+ Desktop::focusState()->fullWindowFocus(nullptr, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
+ return;
+ }
+ Desktop::focusState()->fullWindowFocus(target->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
+ const auto TARGETDATA = dataFor(target);
+ if (TARGETDATA) {
+ if (auto col = TARGETDATA->column.lock())
+ col->lastFocusedTarget = TARGETDATA;
+ }
+}
+
+SP<SScrollingTargetData> CScrollingAlgorithm::findBestNeighbor(SP<SScrollingTargetData> pCurrent, SP<SColumnData> pTargetCol) {
+ if (!pCurrent || !pTargetCol || pTargetCol->targetDatas.empty())
+ return nullptr;
+
+ const double currentTop = pCurrent->layoutBox.y;
+ const double currentBottom = pCurrent->layoutBox.y + pCurrent->layoutBox.h;
+ std::vector<SP<SScrollingTargetData>> overlappingTargets;
+ for (const auto& candidate : pTargetCol->targetDatas) {
+ const double candidateTop = candidate->layoutBox.y;
+ const double candidateBottom = candidate->layoutBox.y + candidate->layoutBox.h;
+ const bool overlaps = (candidateTop < currentBottom) && (candidateBottom > currentTop);
+
+ if (overlaps)
+ overlappingTargets.emplace_back(candidate);
+ }
+ if (!overlappingTargets.empty()) {
+ auto lastFocused = pTargetCol->lastFocusedTarget.lock();
+
+ if (lastFocused) {
+ auto it = std::ranges::find(overlappingTargets, lastFocused);
+ if (it != overlappingTargets.end())
+ return lastFocused;
+ }
+
+ auto topmost = std::ranges::min_element(overlappingTargets, std::less<>{}, [](const SP<SScrollingTargetData>& t) { return t->layoutBox.y; });
+ return *topmost;
+ }
+ if (!pTargetCol->targetDatas.empty())
+ return pTargetCol->targetDatas.front();
+ return nullptr;
+}
+
+SP<SScrollingTargetData> CScrollingAlgorithm::dataFor(SP<ITarget> t) {
+ if (!t)
+ return nullptr;
+
+ for (const auto& c : m_scrollingData->columns) {
+ for (const auto& d : c->targetDatas) {
+ if (d->target == t)
+ return d;
+ }
+ }
+
+ return nullptr;
+}
+
+eScrollDirection CScrollingAlgorithm::getDynamicDirection() {
+ const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent->space()->workspace());
+ std::string directionString;
+ if (WORKSPACERULE.layoutopts.contains("direction"))
+ directionString = WORKSPACERULE.layoutopts.at("direction");
+
+ static const auto PCONFDIRECTION = CConfigValue<Hyprlang::STRING>("scrolling:direction");
+ std::string configDirection = *PCONFDIRECTION;
+
+ // Workspace rule overrides global config
+ if (!directionString.empty())
+ configDirection = directionString;
+
+ // Parse direction string
+ if (configDirection == "left")
+ return SCROLL_DIR_LEFT;
+ else if (configDirection == "down")
+ return SCROLL_DIR_DOWN;
+ else if (configDirection == "up")
+ return SCROLL_DIR_UP;
+ else
+ return SCROLL_DIR_RIGHT; // default
+}
+
+CBox CScrollingAlgorithm::usableArea() {
+ CBox box = m_parent->space()->workArea();
+ box.translate(-m_parent->space()->workspace()->m_monitor->m_position);
+ return box;
+}
diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp
new file mode 100644
index 00000000..a2a9316e
--- /dev/null
+++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp
@@ -0,0 +1,137 @@
+#pragma once
+
+#include "../../TiledAlgorithm.hpp"
+#include "../../../../managers/HookSystemManager.hpp"
+#include "../../../../helpers/math/Direction.hpp"
+#include "ScrollTapeController.hpp"
+
+#include <vector>
+
+namespace Layout::Tiled {
+ class CScrollingAlgorithm;
+ struct SColumnData;
+ struct SScrollingData;
+
+ struct SScrollingTargetData {
+ SScrollingTargetData(SP<ITarget> t, SP<SColumnData> col) : target(t), column(col) {
+ ;
+ }
+
+ WP<ITarget> target;
+ WP<SColumnData> column;
+ bool ignoreFullscreenChecks = false;
+
+ CBox layoutBox;
+ };
+
+ struct SColumnData {
+ SColumnData(SP<SScrollingData> data) : scrollingData(data) {
+ ;
+ }
+
+ void add(SP<ITarget> t);
+ void add(SP<ITarget> t, int after);
+ void add(SP<SScrollingTargetData> w);
+ void add(SP<SScrollingTargetData> w, int after);
+ void remove(SP<ITarget> t);
+ bool has(SP<ITarget> t);
+ size_t idx(SP<ITarget> t);
+
+ // index of lowest target that is above y.
+ size_t idxForHeight(float y);
+
+ void up(SP<SScrollingTargetData> w);
+ void down(SP<SScrollingTargetData> w);
+
+ SP<SScrollingTargetData> next(SP<SScrollingTargetData> w);
+ SP<SScrollingTargetData> prev(SP<SScrollingTargetData> w);
+
+ std::vector<SP<SScrollingTargetData>> targetDatas;
+ WP<SScrollingData> scrollingData;
+ WP<SScrollingTargetData> lastFocusedTarget;
+
+ WP<SColumnData> self;
+
+ // Helper methods to access controller-managed data
+ float getColumnWidth() const;
+ void setColumnWidth(float width);
+ float getTargetSize(size_t idx) const;
+ void setTargetSize(size_t idx, float size);
+ float getTargetSize(SP<SScrollingTargetData> target) const;
+ void setTargetSize(SP<SScrollingTargetData> target, float size);
+ };
+
+ struct SScrollingData {
+ SScrollingData(CScrollingAlgorithm* algo);
+
+ std::vector<SP<SColumnData>> columns;
+
+ UP<CScrollTapeController> controller;
+
+ SP<SColumnData> add();
+ SP<SColumnData> add(int after);
+ int64_t idx(SP<SColumnData> c);
+ void remove(SP<SColumnData> c);
+ double maxWidth();
+ SP<SColumnData> next(SP<SColumnData> c);
+ SP<SColumnData> prev(SP<SColumnData> c);
+ SP<SColumnData> atCenter();
+
+ bool visible(SP<SColumnData> c);
+ void centerCol(SP<SColumnData> c);
+ void fitCol(SP<SColumnData> c);
+ void centerOrFitCol(SP<SColumnData> c);
+
+ void recalculate(bool forceInstant = false);
+
+ CScrollingAlgorithm* algorithm = nullptr;
+ WP<SScrollingData> self;
+ std::optional<double> lockedCameraOffset;
+ };
+
+ class CScrollingAlgorithm : public ITiledAlgorithm {
+ public:
+ CScrollingAlgorithm();
+ virtual ~CScrollingAlgorithm();
+
+ virtual void newTarget(SP<ITarget> target);
+ virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
+ virtual void removeTarget(SP<ITarget> target);
+
+ virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
+ virtual void recalculate();
+
+ virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
+
+ virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);
+ virtual std::optional<Vector2D> predictSizeForNewTarget();
+
+ virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
+ virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
+
+ CBox usableArea();
+
+ private:
+ SP<SScrollingData> m_scrollingData;
+
+ SP<HOOK_CALLBACK_FN> m_configCallback;
+ SP<HOOK_CALLBACK_FN> m_focusCallback;
+ SP<HOOK_CALLBACK_FN> m_mouseButtonCallback;
+
+ struct {
+ std::vector<float> configuredWidths;
+ } m_config;
+
+ eScrollDirection getDynamicDirection();
+
+ SP<SScrollingTargetData> findBestNeighbor(SP<SScrollingTargetData> pCurrent, SP<SColumnData> pTargetCol);
+ SP<SScrollingTargetData> dataFor(SP<ITarget> t);
+ SP<SScrollingTargetData> closestNode(const Vector2D& posGlobglobgabgalab);
+
+ void focusTargetUpdate(SP<ITarget> target);
+ void moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent);
+ void focusOnInput(SP<ITarget> target, bool hardInput);
+
+ friend struct SScrollingData;
+ };
+};
diff --git a/src/layout/space/Space.cpp b/src/layout/space/Space.cpp
new file mode 100644
index 00000000..742c398a
--- /dev/null
+++ b/src/layout/space/Space.cpp
@@ -0,0 +1,185 @@
+#include "Space.hpp"
+
+#include "../target/Target.hpp"
+#include "../algorithm/Algorithm.hpp"
+
+#include "../../debug/log/Logger.hpp"
+#include "../../desktop/Workspace.hpp"
+#include "../../config/ConfigManager.hpp"
+
+using namespace Layout;
+
+SP<CSpace> CSpace::create(PHLWORKSPACE w) {
+ auto space = SP<CSpace>(new CSpace(w));
+ space->m_self = space;
+ return space;
+}
+
+CSpace::CSpace(PHLWORKSPACE parent) : m_parent(parent) {
+ recheckWorkArea();
+}
+
+void CSpace::add(SP<ITarget> t) {
+ m_targets.emplace_back(t);
+
+ recheckWorkArea();
+
+ if (m_algorithm)
+ m_algorithm->addTarget(t);
+
+ m_parent->updateWindows();
+}
+
+void CSpace::move(SP<ITarget> t, std::optional<Vector2D> focalPoint) {
+ m_targets.emplace_back(t);
+
+ recheckWorkArea();
+
+ if (m_algorithm)
+ m_algorithm->moveTarget(t, focalPoint);
+
+ m_parent->updateWindows();
+}
+
+void CSpace::remove(SP<ITarget> t) {
+ std::erase_if(m_targets, [&t](const auto& e) { return !e || e == t; });
+
+ recheckWorkArea();
+
+ if (m_algorithm)
+ m_algorithm->removeTarget(t);
+
+ if (m_parent) // can be null if the workspace is gone
+ m_parent->updateWindows();
+}
+
+void CSpace::setAlgorithmProvider(SP<CAlgorithm> algo) {
+ m_algorithm = algo;
+}
+
+void CSpace::recheckWorkArea() {
+ if (!m_parent || !m_parent->m_monitor) {
+ Log::logger->log(Log::ERR, "CSpace: recheckWorkArea on no parent / mon?!");
+ return;
+ }
+
+ const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent.lock());
+
+ auto workArea = m_parent->m_monitor->logicalBoxMinusReserved();
+
+ static auto PGAPSOUTDATA = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_out");
+ static auto PFLOATGAPSDATA = CConfigValue<Hyprlang::CUSTOMTYPE>("general:float_gaps");
+ auto* const PGAPSOUT = sc<CCssGapData*>((PGAPSOUTDATA.ptr())->getData());
+ auto* PFLOATGAPS = sc<CCssGapData*>(PFLOATGAPSDATA.ptr()->getData());
+ if (PFLOATGAPS->m_bottom < 0 || PFLOATGAPS->m_left < 0 || PFLOATGAPS->m_right < 0 || PFLOATGAPS->m_top < 0)
+ PFLOATGAPS = PGAPSOUT;
+
+ auto gapsOut = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT);
+ auto gapsFloat = WORKSPACERULE.gapsOut.value_or(*PFLOATGAPS);
+
+ Desktop::CReservedArea reservedGaps{gapsOut.m_top, gapsOut.m_right, gapsOut.m_bottom, gapsOut.m_left};
+ Desktop::CReservedArea reservedFloatGaps{gapsFloat.m_top, gapsFloat.m_right, gapsFloat.m_bottom, gapsFloat.m_left};
+
+ auto floatWorkArea = workArea;
+
+ reservedFloatGaps.applyip(floatWorkArea);
+ reservedGaps.applyip(workArea);
+
+ m_workArea = workArea;
+ m_floatingWorkArea = floatWorkArea;
+}
+
+const CBox& CSpace::workArea(bool floating) const {
+ return floating ? m_floatingWorkArea : m_workArea;
+}
+
+PHLWORKSPACE CSpace::workspace() const {
+ return m_parent.lock();
+}
+
+void CSpace::toggleTargetFloating(SP<ITarget> t) {
+ t->setWasTiling(true);
+ m_algorithm->setFloating(t, !t->floating());
+ t->setWasTiling(false);
+
+ m_parent->updateWindows();
+
+ recalculate();
+}
+
+CBox CSpace::targetPositionLocal(SP<ITarget> t) const {
+ return t->position().translate(-m_workArea.pos());
+}
+
+void CSpace::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
+ if (!m_algorithm)
+ return;
+
+ m_algorithm->resizeTarget(Δ, target, corner);
+}
+
+void CSpace::moveTarget(const Vector2D& Δ, SP<ITarget> target) {
+ if (!m_algorithm)
+ return;
+
+ m_algorithm->moveTarget(Δ, target);
+}
+
+SP<CAlgorithm> CSpace::algorithm() const {
+ return m_algorithm;
+}
+
+void CSpace::recalculate() {
+ recheckWorkArea();
+
+ if (m_algorithm)
+ m_algorithm->recalculate();
+}
+
+void CSpace::setFullscreen(SP<ITarget> t, eFullscreenMode mode) {
+ t->setFullscreenMode(mode);
+
+ if (mode == FSMODE_NONE && m_algorithm && t->floating())
+ m_algorithm->recenter(t);
+
+ recalculate();
+}
+
+std::expected<void, std::string> CSpace::layoutMsg(const std::string_view& sv) {
+ if (m_algorithm)
+ return m_algorithm->layoutMsg(sv);
+
+ return {};
+}
+
+std::optional<Vector2D> CSpace::predictSizeForNewTiledTarget() {
+ if (m_algorithm)
+ return m_algorithm->predictSizeForNewTiledTarget();
+
+ return std::nullopt;
+}
+
+void CSpace::swap(SP<ITarget> a, SP<ITarget> b) {
+ for (auto& t : m_targets) {
+ if (t == a)
+ t = b;
+ else if (t == b)
+ t = a;
+ }
+
+ if (m_algorithm)
+ m_algorithm->swapTargets(a, b);
+}
+
+void CSpace::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
+ if (m_algorithm)
+ m_algorithm->moveTargetInDirection(t, dir, silent);
+}
+
+SP<ITarget> CSpace::getNextCandidate(SP<ITarget> old) {
+ return !m_algorithm ? nullptr : m_algorithm->getNextCandidate(old);
+}
+
+const std::vector<WP<ITarget>>& CSpace::targets() const {
+ return m_targets;
+}
diff --git a/src/layout/space/Space.hpp b/src/layout/space/Space.hpp
new file mode 100644
index 00000000..4229e99d
--- /dev/null
+++ b/src/layout/space/Space.hpp
@@ -0,0 +1,67 @@
+#pragma once
+
+#include "../../helpers/math/Math.hpp"
+#include "../../helpers/math/Direction.hpp"
+#include "../../helpers/memory/Memory.hpp"
+
+#include "../../desktop/DesktopTypes.hpp"
+
+#include "../LayoutManager.hpp"
+
+#include <optional>
+#include <expected>
+
+namespace Layout {
+ class ITarget;
+ class CAlgorithm;
+
+ class CSpace {
+ public:
+ static SP<CSpace> create(PHLWORKSPACE w);
+ ~CSpace() = default;
+
+ void add(SP<ITarget> t);
+ void remove(SP<ITarget> t);
+ void move(SP<ITarget> t, std::optional<Vector2D> focalPoint = std::nullopt);
+
+ void swap(SP<ITarget> a, SP<ITarget> b);
+
+ SP<ITarget> getNextCandidate(SP<ITarget> old);
+
+ void setAlgorithmProvider(SP<CAlgorithm> algo);
+ void recheckWorkArea();
+ void setFullscreen(SP<ITarget> t, eFullscreenMode mode);
+
+ void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
+
+ void recalculate();
+
+ void toggleTargetFloating(SP<ITarget> t);
+
+ std::expected<void, std::string> layoutMsg(const std::string_view& sv);
+ std::optional<Vector2D> predictSizeForNewTiledTarget();
+
+ const CBox& workArea(bool floating = false) const;
+ PHLWORKSPACE workspace() const;
+ CBox targetPositionLocal(SP<ITarget> t) const;
+
+ void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
+ void moveTarget(const Vector2D& Δ, SP<ITarget> target);
+
+ SP<CAlgorithm> algorithm() const;
+
+ const std::vector<WP<ITarget>>& targets() const;
+
+ private:
+ CSpace(PHLWORKSPACE parent);
+
+ WP<CSpace> m_self;
+
+ std::vector<WP<ITarget>> m_targets;
+ SP<CAlgorithm> m_algorithm;
+ PHLWORKSPACEREF m_parent;
+
+ // work area is in global coords
+ CBox m_workArea, m_floatingWorkArea;
+ };
+}; \ No newline at end of file
diff --git a/src/layout/supplementary/DragController.cpp b/src/layout/supplementary/DragController.cpp
new file mode 100644
index 00000000..a28aef07
--- /dev/null
+++ b/src/layout/supplementary/DragController.cpp
@@ -0,0 +1,396 @@
+#include "DragController.hpp"
+
+#include "../space/Space.hpp"
+
+#include "../../Compositor.hpp"
+#include "../../managers/cursor/CursorShapeOverrideController.hpp"
+#include "../../desktop/state/FocusState.hpp"
+#include "../../desktop/view/Group.hpp"
+#include "../../render/Renderer.hpp"
+
+using namespace Layout;
+using namespace Layout::Supplementary;
+
+SP<ITarget> CDragStateController::target() const {
+ return m_target.lock();
+}
+
+eMouseBindMode CDragStateController::mode() const {
+ return m_dragMode;
+}
+
+bool CDragStateController::wasDraggingWindow() const {
+ return m_wasDraggingWindow;
+}
+
+bool CDragStateController::dragThresholdReached() const {
+ return m_dragThresholdReached;
+}
+
+void CDragStateController::resetDragThresholdReached() {
+ m_dragThresholdReached = false;
+}
+
+bool CDragStateController::draggingTiled() const {
+ return m_draggingTiled;
+}
+
+bool CDragStateController::updateDragWindow() {
+ const auto DRAGGINGTARGET = m_target.lock();
+ const bool WAS_FULLSCREEN = DRAGGINGTARGET->fullscreenMode() != FSMODE_NONE;
+
+ if (m_dragThresholdReached) {
+ if (WAS_FULLSCREEN) {
+ Log::logger->log(Log::DEBUG, "Dragging a fullscreen window");
+ g_pCompositor->setWindowFullscreenInternal(DRAGGINGTARGET->window(), FSMODE_NONE);
+ }
+
+ const auto PWORKSPACE = DRAGGINGTARGET->workspace();
+ const auto DRAGGINGWINDOW = DRAGGINGTARGET->window();
+
+ if (PWORKSPACE->m_hasFullscreenWindow && (!DRAGGINGTARGET->floating() || (!DRAGGINGWINDOW->m_createdOverFullscreen && !DRAGGINGWINDOW->m_pinned))) {
+ Log::logger->log(Log::DEBUG, "Rejecting drag on a fullscreen workspace. (window under fullscreen)");
+ CKeybindManager::changeMouseBindMode(MBIND_INVALID);
+ return true;
+ }
+ }
+
+ m_draggingTiled = false;
+ m_draggingWindowOriginalFloatSize = DRAGGINGTARGET->lastFloatingSize();
+
+ if (WAS_FULLSCREEN && DRAGGINGTARGET->floating()) {
+ const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();
+ DRAGGINGTARGET->setPositionGlobal(CBox{MOUSECOORDS - DRAGGINGTARGET->position().size() / 2.F, DRAGGINGTARGET->position().size()});
+ } else if (!DRAGGINGTARGET->floating() && m_dragMode == MBIND_MOVE) {
+ Vector2D MINSIZE = DRAGGINGTARGET->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE});
+ DRAGGINGTARGET->rememberFloatingSize((DRAGGINGTARGET->position().size() * 0.8489).clamp(MINSIZE, Vector2D{}).floor());
+ DRAGGINGTARGET->setPositionGlobal(CBox{g_pInputManager->getMouseCoordsInternal() - DRAGGINGTARGET->position().size() / 2.F, DRAGGINGTARGET->position().size()});
+
+ if (m_dragThresholdReached) {
+ g_layoutManager->changeFloatingMode(DRAGGINGTARGET);
+ m_draggingTiled = true;
+ }
+ }
+
+ const auto DRAG_ORIGINAL_BOX = DRAGGINGTARGET->position();
+
+ m_beginDragXY = g_pInputManager->getMouseCoordsInternal();
+ m_beginDragPositionXY = DRAG_ORIGINAL_BOX.pos();
+ m_beginDragSizeXY = DRAG_ORIGINAL_BOX.size();
+ m_lastDragXY = m_beginDragXY;
+
+ return false;
+}
+
+void CDragStateController::dragBegin(SP<ITarget> target, eMouseBindMode mode) {
+ m_target = target;
+ m_dragMode = mode;
+
+ const auto DRAGGINGTARGET = m_target.lock();
+ static auto PDRAGTHRESHOLD = CConfigValue<Hyprlang::INT>("binds:drag_threshold");
+
+ m_mouseMoveEventCount = 1;
+ m_beginDragSizeXY = Vector2D();
+
+ // Window will be floating. Let's check if it's valid. It should be, but I don't like crashing.
+ if (!validMapped(DRAGGINGTARGET->window())) {
+ Log::logger->log(Log::ERR, "Dragging attempted on an invalid window (not mapped)");
+ CKeybindManager::changeMouseBindMode(MBIND_INVALID);
+ return;
+ }
+
+ if (!DRAGGINGTARGET->workspace()) {
+ Log::logger->log(Log::ERR, "Dragging attempted on an invalid window (no workspace)");
+ CKeybindManager::changeMouseBindMode(MBIND_INVALID);
+ return;
+ }
+
+ // Try to pick up dragged window now if drag_threshold is disabled
+ // or at least update dragging related variables for the cursors
+ m_dragThresholdReached = *PDRAGTHRESHOLD <= 0;
+ if (updateDragWindow())
+ return;
+
+ // get the grab corner
+ static auto RESIZECORNER = CConfigValue<Hyprlang::INT>("general:resize_corner");
+ if (*RESIZECORNER != 0 && *RESIZECORNER <= 4 && DRAGGINGTARGET->floating()) {
+ switch (*RESIZECORNER) {
+ case 1:
+ m_grabbedCorner = CORNER_TOPLEFT;
+ Cursor::overrideController->setOverride("nw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+ break;
+ case 2:
+ m_grabbedCorner = CORNER_TOPRIGHT;
+ Cursor::overrideController->setOverride("ne-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+ break;
+ case 3:
+ m_grabbedCorner = CORNER_BOTTOMRIGHT;
+ Cursor::overrideController->setOverride("se-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+ break;
+ case 4:
+ m_grabbedCorner = CORNER_BOTTOMLEFT;
+ Cursor::overrideController->setOverride("sw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+ break;
+ }
+ } else if (m_beginDragXY.x < m_beginDragPositionXY.x + m_beginDragSizeXY.x / 2.F) {
+ if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.F) {
+ m_grabbedCorner = CORNER_TOPLEFT;
+ Cursor::overrideController->setOverride("nw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+ } else {
+ m_grabbedCorner = CORNER_BOTTOMLEFT;
+ Cursor::overrideController->setOverride("sw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+ }
+ } else {
+ if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.F) {
+ m_grabbedCorner = CORNER_TOPRIGHT;
+ Cursor::overrideController->setOverride("ne-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+ } else {
+ m_grabbedCorner = CORNER_BOTTOMRIGHT;
+ Cursor::overrideController->setOverride("se-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+ }
+ }
+
+ if (m_dragMode != MBIND_RESIZE && m_dragMode != MBIND_RESIZE_FORCE_RATIO && m_dragMode != MBIND_RESIZE_BLOCK_RATIO)
+ Cursor::overrideController->setOverride("grabbing", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+
+ DRAGGINGTARGET->damageEntire();
+
+ g_pKeybindManager->shadowKeybinds();
+
+ if (DRAGGINGTARGET->window()) {
+ Desktop::focusState()->rawWindowFocus(DRAGGINGTARGET->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
+ g_pCompositor->changeWindowZOrder(DRAGGINGTARGET->window(), true);
+ }
+}
+void CDragStateController::dragEnd() {
+ auto draggingTarget = m_target.lock();
+
+ m_mouseMoveEventCount = 1;
+
+ if (!validMapped(draggingTarget->window())) {
+ if (draggingTarget->window()) {
+ Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+ m_target.reset();
+ }
+ return;
+ }
+
+ Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
+ m_target.reset();
+ m_wasDraggingWindow = true;
+
+ if (m_dragMode == MBIND_MOVE && draggingTarget->window()) {
+ draggingTarget->damageEntire();
+
+ const auto DRAGGING_WINDOW = draggingTarget->window();
+
+ const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();
+ PHLWINDOW pWindow =
+ g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGING_WINDOW);
+
+ if (pWindow) {
+ if (pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_END, MOUSECOORDS, DRAGGING_WINDOW))
+ return;
+
+ const bool FLOATEDINTOTILED = !pWindow->m_isFloating && !m_draggingTiled;
+ static auto PDRAGINTOGROUP = CConfigValue<Hyprlang::INT>("group:drag_into_group");
+
+ if (pWindow->m_group && DRAGGING_WINDOW->canBeGroupedInto(pWindow->m_group) && *PDRAGINTOGROUP == 1 && !FLOATEDINTOTILED) {
+ pWindow->m_group->add(DRAGGING_WINDOW);
+ // fix the draggingTarget, now it's DRAGGING_WINDOW
+ draggingTarget = DRAGGING_WINDOW->m_target;
+ }
+ }
+ }
+
+ if (m_draggingTiled) {
+ // static auto PPRECISEMOUSE = CConfigValue<Hyprlang::INT>("dwindle:precise_mouse_move");
+
+ // FIXME: remove or rethink
+ // if (*PPRECISEMOUSE) {
+ // eDirection direction = DIRECTION_DEFAULT;
+
+ // const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();
+ // const PHLWINDOW pReferenceWindow =
+ // g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGINGWINDOW);
+
+ // if (pReferenceWindow && pReferenceWindow != DRAGGINGWINDOW) {
+ // const Vector2D draggedCenter = DRAGGINGWINDOW->m_realPosition->goal() + DRAGGINGWINDOW->m_realSize->goal() / 2.f;
+ // const Vector2D referenceCenter = pReferenceWindow->m_realPosition->goal() + pReferenceWindow->m_realSize->goal() / 2.f;
+ // const float xDiff = draggedCenter.x - referenceCenter.x;
+ // const float yDiff = draggedCenter.y - referenceCenter.y;
+
+ // if (fabs(xDiff) > fabs(yDiff))
+ // direction = xDiff < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
+ // else
+ // direction = yDiff < 0 ? DIRECTION_UP : DIRECTION_DOWN;
+ // }
+
+ // onWindowRemovedTiling(DRAGGINGWINDOW);
+ // onWindowCreatedTiling(DRAGGINGWINDOW, direction);
+ // } else
+
+ // make sure to check if we are floating because drag into group could make us tiled already
+ if (draggingTarget->floating())
+ g_layoutManager->changeFloatingMode(draggingTarget);
+
+ draggingTarget->rememberFloatingSize(m_draggingWindowOriginalFloatSize);
+ }
+
+ draggingTarget->damageEntire();
+
+ Desktop::focusState()->fullWindowFocus(draggingTarget->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
+
+ m_wasDraggingWindow = false;
+ m_dragMode = MBIND_INVALID;
+}
+
+void CDragStateController::mouseMove(const Vector2D& mousePos) {
+ if (m_target.expired())
+ return;
+
+ const auto DRAGGINGTARGET = m_target.lock();
+ static auto PDRAGTHRESHOLD = CConfigValue<Hyprlang::INT>("binds:drag_threshold");
+
+ // Window invalid or drag begin size 0,0 meaning we rejected it.
+ if ((!validMapped(DRAGGINGTARGET->window()) || m_beginDragSizeXY == Vector2D())) {
+ CKeybindManager::changeMouseBindMode(MBIND_INVALID);
+ return;
+ }
+
+ // Yoink dragged window here instead if using drag_threshold and it has been reached
+ if (*PDRAGTHRESHOLD > 0 && !m_dragThresholdReached) {
+ if ((m_beginDragXY.distanceSq(mousePos) <= std::pow(*PDRAGTHRESHOLD, 2) && m_beginDragXY == m_lastDragXY))
+ return;
+ m_dragThresholdReached = true;
+ if (updateDragWindow())
+ return;
+ }
+
+ static auto TIMER = std::chrono::high_resolution_clock::now(), MSTIMER = TIMER;
+
+ const auto DELTA = Vector2D(mousePos.x - m_beginDragXY.x, mousePos.y - m_beginDragXY.y);
+ const auto TICKDELTA = Vector2D(mousePos.x - m_lastDragXY.x, mousePos.y - m_lastDragXY.y);
+
+ static auto SNAPENABLED = CConfigValue<Hyprlang::INT>("general:snap:enabled");
+
+ const auto TIMERDELTA = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - TIMER).count();
+ const auto MSDELTA = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - MSTIMER).count();
+ const auto MSMONITOR = 1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate;
+ static int totalMs = 0;
+ bool canSkipUpdate = true;
+
+ MSTIMER = std::chrono::high_resolution_clock::now();
+
+ if (m_mouseMoveEventCount == 1)
+ totalMs = 0;
+
+ if (MSMONITOR > 16.0) {
+ totalMs += MSDELTA < MSMONITOR ? MSDELTA : std::round(totalMs * 1.0 / m_mouseMoveEventCount);
+ m_mouseMoveEventCount += 1;
+
+ // check if time-window is enough to skip update on 60hz monitor
+ canSkipUpdate = std::clamp(MSMONITOR - TIMERDELTA, 0.0, MSMONITOR) > totalMs * 1.0 / m_mouseMoveEventCount;
+ }
+
+ if ((abs(TICKDELTA.x) < 1.f && abs(TICKDELTA.y) < 1.f) || (TIMERDELTA < MSMONITOR && canSkipUpdate && (m_dragMode != MBIND_MOVE)))
+ return;
+
+ TIMER = std::chrono::high_resolution_clock::now();
+
+ m_lastDragXY = mousePos;
+
+ DRAGGINGTARGET->damageEntire();
+
+ if (m_dragMode == MBIND_MOVE) {
+
+ Vector2D newPos = m_beginDragPositionXY + DELTA;
+ Vector2D newSize = DRAGGINGTARGET->position().size();
+
+ if (*SNAPENABLED && !m_draggingTiled)
+ g_layoutManager->performSnap(newPos, newSize, DRAGGINGTARGET, MBIND_MOVE, -1, m_beginDragSizeXY);
+
+ newPos = newPos.round();
+
+ DRAGGINGTARGET->setPositionGlobal({newPos, newSize});
+ DRAGGINGTARGET->warpPositionSize();
+ } else if (m_dragMode == MBIND_RESIZE || m_dragMode == MBIND_RESIZE_FORCE_RATIO || m_dragMode == MBIND_RESIZE_BLOCK_RATIO) {
+ if (DRAGGINGTARGET->floating()) {
+
+ Vector2D MINSIZE = DRAGGINGTARGET->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE});
+ Vector2D MAXSIZE = DRAGGINGTARGET->maxSize().value_or(Math::VECTOR2D_MAX);
+
+ Vector2D newSize = m_beginDragSizeXY;
+ Vector2D newPos = m_beginDragPositionXY;
+
+ if (m_grabbedCorner == CORNER_BOTTOMRIGHT)
+ newSize = newSize + DELTA;
+ else if (m_grabbedCorner == CORNER_TOPLEFT)
+ newSize = newSize - DELTA;
+ else if (m_grabbedCorner == CORNER_TOPRIGHT)
+ newSize = newSize + Vector2D(DELTA.x, -DELTA.y);
+ else if (m_grabbedCorner == CORNER_BOTTOMLEFT)
+ newSize = newSize + Vector2D(-DELTA.x, DELTA.y);
+
+ eMouseBindMode mode = m_dragMode;
+ if (DRAGGINGTARGET->window() && DRAGGINGTARGET->window()->m_ruleApplicator->keepAspectRatio().valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO)
+ mode = MBIND_RESIZE_FORCE_RATIO;
+
+ if (m_beginDragSizeXY.x >= 1 && m_beginDragSizeXY.y >= 1 && mode == MBIND_RESIZE_FORCE_RATIO) {
+
+ const float RATIO = m_beginDragSizeXY.y / m_beginDragSizeXY.x;
+
+ if (MINSIZE.x * RATIO > MINSIZE.y)
+ MINSIZE = Vector2D(MINSIZE.x, MINSIZE.x * RATIO);
+ else
+ MINSIZE = Vector2D(MINSIZE.y / RATIO, MINSIZE.y);
+
+ if (MAXSIZE.x * RATIO < MAXSIZE.y)
+ MAXSIZE = Vector2D(MAXSIZE.x, MAXSIZE.x * RATIO);
+ else
+ MAXSIZE = Vector2D(MAXSIZE.y / RATIO, MAXSIZE.y);
+
+ if (newSize.x * RATIO > newSize.y)
+ newSize = Vector2D(newSize.x, newSize.x * RATIO);
+ else
+ newSize = Vector2D(newSize.y / RATIO, newSize.y);
+ }
+
+ newSize = newSize.clamp(MINSIZE, MAXSIZE);
+
+ if (m_grabbedCorner == CORNER_TOPLEFT)
+ newPos = newPos - newSize + m_beginDragSizeXY;
+ else if (m_grabbedCorner == CORNER_TOPRIGHT)
+ newPos = newPos + Vector2D(0.0, (m_beginDragSizeXY - newSize).y);
+ else if (m_grabbedCorner == CORNER_BOTTOMLEFT)
+ newPos = newPos + Vector2D((m_beginDragSizeXY - newSize).x, 0.0);
+
+ if (*SNAPENABLED) {
+ g_layoutManager->performSnap(newPos, newSize, DRAGGINGTARGET, mode, m_grabbedCorner, m_beginDragSizeXY);
+ newSize = newSize.clamp(MINSIZE, MAXSIZE);
+ }
+
+ CBox wb = {newPos, newSize};
+ wb.round();
+
+ DRAGGINGTARGET->setPositionGlobal(wb);
+ DRAGGINGTARGET->warpPositionSize();
+ } else {
+ g_layoutManager->resizeTarget(TICKDELTA, DRAGGINGTARGET, m_grabbedCorner);
+ DRAGGINGTARGET->warpPositionSize();
+ }
+ }
+
+ // get middle point
+ Vector2D middle = DRAGGINGTARGET->position().middle();
+
+ // and check its monitor
+ const auto PMONITOR = g_pCompositor->getMonitorFromVector(middle);
+
+ if (PMONITOR && PMONITOR->m_activeWorkspace && DRAGGINGTARGET->floating() /* If we're resaizing a tiled target, don't do this */) {
+ const auto WS = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace;
+ DRAGGINGTARGET->assignToSpace(WS->m_space);
+ }
+
+ DRAGGINGTARGET->damageEntire();
+}
diff --git a/src/layout/supplementary/DragController.hpp b/src/layout/supplementary/DragController.hpp
new file mode 100644
index 00000000..3a0d8071
--- /dev/null
+++ b/src/layout/supplementary/DragController.hpp
@@ -0,0 +1,54 @@
+#pragma once
+
+#include "../target/Target.hpp"
+#include "../../managers/input/InputManager.hpp"
+
+namespace Layout {
+ enum eRectCorner : uint8_t;
+}
+
+namespace Layout::Supplementary {
+
+ // DragStateController contains logic to begin and end a drag, which shouldn't be part of the layout's job. It's stuff like
+ // toggling float when dragging tiled, remembering sizes, checking deltas, etc.
+ class CDragStateController {
+ public:
+ CDragStateController() = default;
+ ~CDragStateController() = default;
+
+ void dragBegin(SP<ITarget> target, eMouseBindMode mode);
+ void dragEnd();
+
+ void mouseMove(const Vector2D& mousePos);
+ eMouseBindMode mode() const;
+ bool wasDraggingWindow() const;
+ bool dragThresholdReached() const;
+ void resetDragThresholdReached();
+ bool draggingTiled() const;
+
+ /*
+ Called to try to pick up window for dragging.
+ Updates drag related variables and floats window if threshold reached.
+ Return true to reject
+ */
+ bool updateDragWindow();
+
+ SP<ITarget> target() const;
+
+ private:
+ WP<ITarget> m_target;
+
+ eMouseBindMode m_dragMode = MBIND_INVALID;
+ bool m_wasDraggingWindow = false;
+ bool m_dragThresholdReached = false;
+ bool m_draggingTiled = false;
+
+ int m_mouseMoveEventCount = 0;
+ Vector2D m_beginDragXY;
+ Vector2D m_lastDragXY;
+ Vector2D m_beginDragPositionXY;
+ Vector2D m_beginDragSizeXY;
+ Vector2D m_draggingWindowOriginalFloatSize;
+ Layout::eRectCorner m_grabbedCorner = sc<Layout::eRectCorner>(0) /* CORNER_NONE */;
+ };
+};
diff --git a/src/layout/supplementary/WorkspaceAlgoMatcher.cpp b/src/layout/supplementary/WorkspaceAlgoMatcher.cpp
new file mode 100644
index 00000000..b476c3a0
--- /dev/null
+++ b/src/layout/supplementary/WorkspaceAlgoMatcher.cpp
@@ -0,0 +1,139 @@
+#include "WorkspaceAlgoMatcher.hpp"
+
+#include "../../config/ConfigValue.hpp"
+#include "../../config/ConfigManager.hpp"
+
+#include "../algorithm/Algorithm.hpp"
+#include "../space/Space.hpp"
+
+#include "../algorithm/floating/default/DefaultFloatingAlgorithm.hpp"
+#include "../algorithm/tiled/dwindle/DwindleAlgorithm.hpp"
+#include "../algorithm/tiled/master/MasterAlgorithm.hpp"
+#include "../algorithm/tiled/scrolling/ScrollingAlgorithm.hpp"
+#include "../algorithm/tiled/monocle/MonocleAlgorithm.hpp"
+
+#include "../../Compositor.hpp"
+
+using namespace Layout;
+using namespace Layout::Supplementary;
+
+constexpr const char* DEFAULT_FLOATING_ALGO = "default";
+constexpr const char* DEFAULT_TILED_ALGO = "dwindle";
+
+const UP<CWorkspaceAlgoMatcher>& Supplementary::algoMatcher() {
+ static UP<CWorkspaceAlgoMatcher> m = makeUnique<CWorkspaceAlgoMatcher>();
+ return m;
+}
+
+CWorkspaceAlgoMatcher::CWorkspaceAlgoMatcher() {
+ m_tiledAlgos = {
+ {"dwindle", [] { return makeUnique<Tiled::CDwindleAlgorithm>(); }},
+ {"master", [] { return makeUnique<Tiled::CMasterAlgorithm>(); }},
+ {"scrolling", [] { return makeUnique<Tiled::CScrollingAlgorithm>(); }},
+ {"monocle", [] { return makeUnique<Tiled::CMonocleAlgorithm>(); }},
+ };
+
+ m_floatingAlgos = {
+ {"default", [] { return makeUnique<Floating::CDefaultFloatingAlgorithm>(); }},
+ };
+
+ m_algoNames = {
+ {&typeid(Tiled::CDwindleAlgorithm), "dwindle"},
+ {&typeid(Tiled::CMasterAlgorithm), "master"},
+ {&typeid(Tiled::CScrollingAlgorithm), "scrolling"},
+ {&typeid(Tiled::CMonocleAlgorithm), "monocle"},
+ {&typeid(Floating::CDefaultFloatingAlgorithm), "default"},
+ };
+}
+
+bool CWorkspaceAlgoMatcher::registerTiledAlgo(const std::string& name, const std::type_info* typeInfo, std::function<UP<ITiledAlgorithm>()>&& factory) {
+ if (m_tiledAlgos.contains(name) || m_floatingAlgos.contains(name))
+ return false;
+
+ m_tiledAlgos.emplace(name, std::move(factory));
+ m_algoNames.emplace(typeInfo, name);
+
+ updateWorkspaceLayouts();
+
+ return true;
+}
+
+bool CWorkspaceAlgoMatcher::registerFloatingAlgo(const std::string& name, const std::type_info* typeInfo, std::function<UP<IFloatingAlgorithm>()>&& factory) {
+ if (m_tiledAlgos.contains(name) || m_floatingAlgos.contains(name))
+ return false;
+
+ m_floatingAlgos.emplace(name, std::move(factory));
+ m_algoNames.emplace(typeInfo, name);
+
+ updateWorkspaceLayouts();
+
+ return true;
+}
+
+bool CWorkspaceAlgoMatcher::unregisterAlgo(const std::string& name) {
+ if (!m_tiledAlgos.contains(name) && !m_floatingAlgos.contains(name))
+ return false;
+
+ std::erase_if(m_algoNames, [&name](const auto& e) { return e.second == name; });
+
+ if (m_floatingAlgos.contains(name))
+ m_floatingAlgos.erase(name);
+ else
+ m_tiledAlgos.erase(name);
+
+ // this is needed here to avoid situations where a plugin unloads and we still have a UP
+ // to a plugin layout
+ updateWorkspaceLayouts();
+
+ return true;
+}
+
+UP<ITiledAlgorithm> CWorkspaceAlgoMatcher::algoForNameTiled(const std::string& s) {
+ if (m_tiledAlgos.contains(s))
+ return m_tiledAlgos.at(s)();
+ return m_tiledAlgos.at(DEFAULT_TILED_ALGO)();
+}
+
+UP<IFloatingAlgorithm> CWorkspaceAlgoMatcher::algoForNameFloat(const std::string& s) {
+ if (m_floatingAlgos.contains(s))
+ return m_floatingAlgos.at(s)();
+ return m_floatingAlgos.at(DEFAULT_FLOATING_ALGO)();
+}
+
+std::string CWorkspaceAlgoMatcher::tiledAlgoForWorkspace(const PHLWORKSPACE& w) {
+ static auto PLAYOUT = CConfigValue<Hyprlang::STRING>("general:layout");
+
+ auto rule = g_pConfigManager->getWorkspaceRuleFor(w);
+ return rule.layout.value_or(*PLAYOUT);
+}
+
+SP<CAlgorithm> CWorkspaceAlgoMatcher::createAlgorithmForWorkspace(PHLWORKSPACE w) {
+ return CAlgorithm::create(algoForNameTiled(tiledAlgoForWorkspace(w)), makeUnique<Floating::CDefaultFloatingAlgorithm>(), w->m_space);
+}
+
+void CWorkspaceAlgoMatcher::updateWorkspaceLayouts() {
+ // TODO: make this ID-based, string comparison is slow
+ for (const auto& ws : g_pCompositor->getWorkspaces()) {
+ if (!ws)
+ continue;
+
+ const auto& TILED_ALGO = ws->m_space->algorithm()->tiledAlgo();
+
+ if (!TILED_ALGO)
+ continue;
+
+ const auto LAYOUT_TO_USE = tiledAlgoForWorkspace(ws.lock());
+
+ if (m_algoNames.contains(&typeid(*TILED_ALGO.get())) && m_algoNames.at(&typeid(*TILED_ALGO.get())) == LAYOUT_TO_USE)
+ continue;
+
+ // needs a switchup
+ ws->m_space->algorithm()->updateTiledAlgo(algoForNameTiled(LAYOUT_TO_USE));
+ }
+}
+
+std::string CWorkspaceAlgoMatcher::getNameForTiledAlgo(const std::type_info* type) {
+ if (m_algoNames.contains(type))
+ return m_algoNames.at(type);
+ return "unknown";
+}
diff --git a/src/layout/supplementary/WorkspaceAlgoMatcher.hpp b/src/layout/supplementary/WorkspaceAlgoMatcher.hpp
new file mode 100644
index 00000000..d39e2998
--- /dev/null
+++ b/src/layout/supplementary/WorkspaceAlgoMatcher.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "../../desktop/DesktopTypes.hpp"
+
+#include <map>
+#include <type_traits>
+#include <functional>
+#include <string>
+
+namespace Layout {
+ class CAlgorithm;
+ class ITiledAlgorithm;
+ class IFloatingAlgorithm;
+}
+
+namespace Layout::Supplementary {
+ class CWorkspaceAlgoMatcher {
+ public:
+ CWorkspaceAlgoMatcher();
+ ~CWorkspaceAlgoMatcher() = default;
+
+ SP<CAlgorithm> createAlgorithmForWorkspace(PHLWORKSPACE w);
+ void updateWorkspaceLayouts();
+ std::string getNameForTiledAlgo(const std::type_info* type);
+
+ // these fns can fail due to name collisions
+ bool registerTiledAlgo(const std::string& name, const std::type_info* typeInfo, std::function<UP<ITiledAlgorithm>()>&& factory);
+ bool registerFloatingAlgo(const std::string& name, const std::type_info* typeInfo, std::function<UP<IFloatingAlgorithm>()>&& factory);
+
+ // this fn fails if the algo isn't registered
+ bool unregisterAlgo(const std::string& name);
+
+ private:
+ UP<ITiledAlgorithm> algoForNameTiled(const std::string& s);
+ UP<IFloatingAlgorithm> algoForNameFloat(const std::string& s);
+
+ std::string tiledAlgoForWorkspace(const PHLWORKSPACE&);
+
+ std::map<std::string, std::function<UP<ITiledAlgorithm>()>> m_tiledAlgos;
+ std::map<std::string, std::function<UP<IFloatingAlgorithm>()>> m_floatingAlgos;
+
+ std::map<const std::type_info*, std::string> m_algoNames;
+ };
+
+ const UP<CWorkspaceAlgoMatcher>& algoMatcher();
+} \ No newline at end of file
diff --git a/src/layout/target/Target.cpp b/src/layout/target/Target.cpp
new file mode 100644
index 00000000..e433c237
--- /dev/null
+++ b/src/layout/target/Target.cpp
@@ -0,0 +1,146 @@
+#include "Target.hpp"
+#include "../space/Space.hpp"
+#include "../../debug/log/Logger.hpp"
+
+#include <hyprutils/utils/ScopeGuard.hpp>
+
+using namespace Layout;
+using namespace Hyprutils::Utils;
+
+void ITarget::setPositionGlobal(const CBox& box) {
+ m_box = box;
+ m_box.round();
+}
+
+void ITarget::assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint) {
+ if (m_space == space && !m_ghostSpace)
+ return;
+
+ const bool HAD_SPACE = !!m_space;
+
+ if (m_space && !m_ghostSpace)
+ m_space->remove(m_self.lock());
+
+ m_space = space;
+
+ if (space && HAD_SPACE)
+ space->move(m_self.lock(), focalPoint);
+ else if (space)
+ space->add(m_self.lock());
+
+ if (!space)
+ Log::logger->log(Log::WARN, "ITarget: assignToSpace with a null space?");
+
+ m_ghostSpace = false;
+
+ onUpdateSpace();
+}
+
+void ITarget::setSpaceGhost(const SP<CSpace>& space) {
+ if (m_space)
+ assignToSpace(nullptr);
+
+ m_space = space;
+
+ m_ghostSpace = true;
+}
+
+SP<CSpace> ITarget::space() const {
+ return m_space;
+}
+
+PHLWORKSPACE ITarget::workspace() const {
+ if (!m_space)
+ return nullptr;
+
+ return m_space->workspace();
+}
+
+CBox ITarget::position() const {
+ return m_box;
+}
+
+void ITarget::rememberFloatingSize(const Vector2D& size) {
+ m_floatingSize = size;
+}
+
+Vector2D ITarget::lastFloatingSize() const {
+ return m_floatingSize;
+}
+
+void ITarget::recalc() {
+ setPositionGlobal(m_box);
+}
+
+void ITarget::setPseudo(bool x) {
+ if (m_pseudo == x)
+ return;
+
+ m_pseudo = x;
+
+ recalc();
+}
+
+bool ITarget::isPseudo() const {
+ return m_pseudo;
+}
+
+void ITarget::setPseudoSize(const Vector2D& size) {
+ m_pseudoSize = size;
+
+ recalc();
+}
+
+Vector2D ITarget::pseudoSize() {
+ return m_pseudoSize;
+}
+
+void ITarget::swap(SP<ITarget> b) {
+ const auto IS_FLOATING = floating();
+ const auto IS_FLOATING_B = b->floating();
+
+ // Keep workspaces alive during a swap: moving one window will unref the ws
+
+ // NOLINTNEXTLINE
+ const auto PWS1 = workspace();
+ // NOLINTNEXTLINE
+ const auto PWS2 = b->workspace();
+
+ CScopeGuard x([&] {
+ b->setFloating(IS_FLOATING);
+ setFloating(IS_FLOATING_B);
+
+ // update the spaces
+ b->onUpdateSpace();
+ onUpdateSpace();
+ });
+
+ if (b->space() == m_space) {
+ // simplest
+ m_space->swap(m_self.lock(), b);
+ m_space->recalculate();
+ return;
+ }
+
+ // spaces differ
+ if (m_space)
+ m_space->swap(m_self.lock(), b);
+ if (b->space())
+ b->space()->swap(b, m_self.lock());
+
+ std::swap(m_space, b->m_space);
+
+ // recalc both
+ if (m_space)
+ m_space->recalculate();
+ if (b->space())
+ b->space()->recalculate();
+}
+
+bool ITarget::wasTiling() const {
+ return m_wasTiling;
+}
+
+void ITarget::setWasTiling(bool x) {
+ m_wasTiling = x;
+}
diff --git a/src/layout/target/Target.hpp b/src/layout/target/Target.hpp
new file mode 100644
index 00000000..dcaefdb4
--- /dev/null
+++ b/src/layout/target/Target.hpp
@@ -0,0 +1,79 @@
+#pragma once
+
+#include "../../helpers/math/Math.hpp"
+#include "../../helpers/memory/Memory.hpp"
+#include "../../desktop/Workspace.hpp"
+
+#include <expected>
+#include <cstdint>
+
+namespace Layout {
+ enum eTargetType : uint8_t {
+ TARGET_TYPE_WINDOW = 0,
+ TARGET_TYPE_GROUP,
+ };
+
+ enum eGeometryFailure : uint8_t {
+ GEOMETRY_NO_DESIRED = 0,
+ GEOMETRY_INVALID_DESIRED = 1,
+ };
+
+ class CSpace;
+
+ struct SGeometryRequested {
+ Vector2D size;
+ std::optional<Vector2D> pos;
+ };
+
+ class ITarget {
+ public:
+ virtual ~ITarget() = default;
+
+ virtual eTargetType type() = 0;
+
+ // position is within its space
+ virtual void setPositionGlobal(const CBox& box);
+ virtual CBox position() const;
+ virtual void assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint = std::nullopt);
+ virtual void setSpaceGhost(const SP<CSpace>& space);
+ virtual SP<CSpace> space() const;
+ virtual PHLWORKSPACE workspace() const;
+ virtual PHLWINDOW window() const = 0;
+ virtual void recalc();
+ virtual bool wasTiling() const;
+ virtual void setWasTiling(bool x);
+
+ virtual void rememberFloatingSize(const Vector2D& size);
+ virtual Vector2D lastFloatingSize() const;
+
+ virtual void setPseudo(bool x);
+ virtual bool isPseudo() const;
+ virtual void setPseudoSize(const Vector2D& size);
+ virtual Vector2D pseudoSize();
+ virtual void swap(SP<ITarget> b);
+
+ //
+ virtual bool floating() = 0;
+ virtual void setFloating(bool x) = 0;
+ virtual std::expected<SGeometryRequested, eGeometryFailure> desiredGeometry() = 0;
+ virtual eFullscreenMode fullscreenMode() = 0;
+ virtual void setFullscreenMode(eFullscreenMode mode) = 0;
+ virtual std::optional<Vector2D> minSize() = 0;
+ virtual std::optional<Vector2D> maxSize() = 0;
+ virtual void damageEntire() = 0;
+ virtual void warpPositionSize() = 0;
+ virtual void onUpdateSpace() = 0;
+
+ protected:
+ ITarget() = default;
+
+ CBox m_box;
+ SP<CSpace> m_space;
+ WP<ITarget> m_self;
+ Vector2D m_floatingSize;
+ bool m_pseudo = false;
+ bool m_ghostSpace = false; // ghost space means a target belongs to a space, but isn't sent to the layout
+ Vector2D m_pseudoSize = {1280, 720};
+ bool m_wasTiling = false;
+ };
+}; \ No newline at end of file
diff --git a/src/layout/target/WindowGroupTarget.cpp b/src/layout/target/WindowGroupTarget.cpp
new file mode 100644
index 00000000..ae883751
--- /dev/null
+++ b/src/layout/target/WindowGroupTarget.cpp
@@ -0,0 +1,92 @@
+#include "WindowGroupTarget.hpp"
+
+#include "../space/Space.hpp"
+#include "../algorithm/Algorithm.hpp"
+#include "WindowTarget.hpp"
+#include "Target.hpp"
+
+#include "../../render/Renderer.hpp"
+
+using namespace Layout;
+
+SP<CWindowGroupTarget> CWindowGroupTarget::create(SP<Desktop::View::CGroup> g) {
+ auto target = SP<CWindowGroupTarget>(new CWindowGroupTarget(g));
+ target->m_self = target;
+ return target;
+}
+
+CWindowGroupTarget::CWindowGroupTarget(SP<Desktop::View::CGroup> g) : m_group(g) {
+ ;
+}
+
+eTargetType CWindowGroupTarget::type() {
+ return TARGET_TYPE_GROUP;
+}
+
+void CWindowGroupTarget::setPositionGlobal(const CBox& box) {
+ ITarget::setPositionGlobal(box);
+
+ updatePos();
+}
+
+void CWindowGroupTarget::updatePos() {
+ for (const auto& w : m_group->windows()) {
+ w->m_target->setPositionGlobal(m_box);
+ }
+}
+
+void CWindowGroupTarget::assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint) {
+ ITarget::assignToSpace(space, focalPoint);
+
+ m_group->updateWorkspace(space->workspace());
+}
+
+bool CWindowGroupTarget::floating() {
+ return m_group->current()->m_target->floating();
+}
+
+void CWindowGroupTarget::setFloating(bool x) {
+ for (const auto& w : m_group->windows()) {
+ w->m_target->setFloating(x);
+ }
+}
+
+std::expected<SGeometryRequested, eGeometryFailure> CWindowGroupTarget::desiredGeometry() {
+ return m_group->current()->m_target->desiredGeometry();
+}
+
+PHLWINDOW CWindowGroupTarget::window() const {
+ return m_group->current();
+}
+
+eFullscreenMode CWindowGroupTarget::fullscreenMode() {
+ return m_group->current()->m_fullscreenState.internal;
+}
+
+void CWindowGroupTarget::setFullscreenMode(eFullscreenMode mode) {
+ m_group->current()->m_fullscreenState.internal = mode;
+}
+
+std::optional<Vector2D> CWindowGroupTarget::minSize() {
+ return m_group->current()->minSize();
+}
+
+std::optional<Vector2D> CWindowGroupTarget::maxSize() {
+ return m_group->current()->maxSize();
+}
+
+void CWindowGroupTarget::damageEntire() {
+ g_pHyprRenderer->damageWindow(m_group->current());
+}
+
+void CWindowGroupTarget::warpPositionSize() {
+ for (const auto& w : m_group->windows()) {
+ w->m_target->warpPositionSize();
+ }
+}
+
+void CWindowGroupTarget::onUpdateSpace() {
+ for (const auto& w : m_group->windows()) {
+ w->m_target->onUpdateSpace();
+ }
+}
diff --git a/src/layout/target/WindowGroupTarget.hpp b/src/layout/target/WindowGroupTarget.hpp
new file mode 100644
index 00000000..3d4b85a0
--- /dev/null
+++ b/src/layout/target/WindowGroupTarget.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "Target.hpp"
+
+#include "../../desktop/view/Window.hpp"
+#include "../../desktop/view/Group.hpp"
+
+namespace Layout {
+
+ class CWindowGroupTarget : public ITarget {
+ public:
+ static SP<CWindowGroupTarget> create(SP<Desktop::View::CGroup> g);
+ virtual ~CWindowGroupTarget() = default;
+
+ virtual eTargetType type();
+
+ virtual void setPositionGlobal(const CBox& box);
+ virtual void assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint = std::nullopt);
+ virtual PHLWINDOW window() const;
+
+ virtual bool floating();
+ virtual void setFloating(bool x);
+ virtual std::expected<SGeometryRequested, eGeometryFailure> desiredGeometry();
+ virtual eFullscreenMode fullscreenMode();
+ virtual void setFullscreenMode(eFullscreenMode mode);
+ virtual std::optional<Vector2D> minSize();
+ virtual std::optional<Vector2D> maxSize();
+ virtual void damageEntire();
+ virtual void warpPositionSize();
+ virtual void onUpdateSpace();
+
+ private:
+ CWindowGroupTarget(SP<Desktop::View::CGroup> g);
+
+ void updatePos();
+
+ WP<Desktop::View::CGroup> m_group;
+ };
+}; \ No newline at end of file
diff --git a/src/layout/target/WindowTarget.cpp b/src/layout/target/WindowTarget.cpp
new file mode 100644
index 00000000..0bd905af
--- /dev/null
+++ b/src/layout/target/WindowTarget.cpp
@@ -0,0 +1,363 @@
+#include "WindowTarget.hpp"
+
+#include "../space/Space.hpp"
+#include "../algorithm/Algorithm.hpp"
+
+#include "../../protocols/core/Compositor.hpp"
+#include "../../config/ConfigManager.hpp"
+#include "../../helpers/Monitor.hpp"
+#include "../../xwayland/XSurface.hpp"
+#include "../../Compositor.hpp"
+#include "../../render/Renderer.hpp"
+
+using namespace Layout;
+
+SP<ITarget> CWindowTarget::create(PHLWINDOW w) {
+ auto target = SP<CWindowTarget>(new CWindowTarget(w));
+ target->m_self = target;
+ return target;
+}
+
+CWindowTarget::CWindowTarget(PHLWINDOW w) : m_window(w) {
+ ;
+}
+
+eTargetType CWindowTarget::type() {
+ return TARGET_TYPE_WINDOW;
+}
+
+void CWindowTarget::setPositionGlobal(const CBox& box) {
+ ITarget::setPositionGlobal(box);
+
+ updatePos();
+}
+
+void CWindowTarget::updatePos() {
+
+ if (!m_space)
+ return;
+
+ if (fullscreenMode() == FSMODE_FULLSCREEN)
+ return;
+
+ if (floating() && fullscreenMode() != FSMODE_MAXIMIZED) {
+ m_window->m_position = m_box.pos();
+ m_window->m_size = m_box.size();
+
+ *m_window->m_realPosition = m_box.pos();
+ *m_window->m_realSize = m_box.size();
+
+ m_window->sendWindowSize();
+ m_window->updateWindowDecos();
+
+ return;
+ }
+
+ // Tiled is more complicated.
+
+ const auto PMONITOR = m_space->workspace()->m_monitor;
+ const auto PWORKSPACE = m_space->workspace();
+
+ // for gaps outer
+ const auto MONITOR_WORKAREA = m_space->workArea();
+ const bool DISPLAYLEFT = STICKS(m_box.x, MONITOR_WORKAREA.x);
+ const bool DISPLAYRIGHT = STICKS(m_box.x + m_box.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);
+ const bool DISPLAYTOP = STICKS(m_box.y, MONITOR_WORKAREA.y);
+ const bool DISPLAYBOTTOM = STICKS(m_box.y + m_box.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h);
+
+ // this is used for scrolling, so that the gaps are correct when a window is the full width and has neighbors
+ const bool DISPLAYINVERSELEFT = STICKS(m_box.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);
+ const bool DISPLAYINVERSERIGHT = STICKS(m_box.x + m_box.w, MONITOR_WORKAREA.x);
+
+ // get specific gaps and rules for this workspace,
+ // if user specified them in config
+ const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE);
+
+ if (!validMapped(m_window)) {
+ if (m_window)
+ g_layoutManager->removeTarget(m_window->layoutTarget());
+ return;
+ }
+
+ if (fullscreenMode() == FSMODE_FULLSCREEN)
+ return;
+
+ g_pHyprRenderer->damageWindow(window());
+
+ static auto PGAPSINDATA = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_in");
+ auto* const PGAPSIN = sc<CCssGapData*>((PGAPSINDATA.ptr())->getData());
+
+ auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN);
+ CBox nodeBox = m_box;
+ nodeBox.round();
+
+ m_window->m_size = nodeBox.size();
+ m_window->m_position = nodeBox.pos();
+
+ m_window->updateWindowDecos();
+
+ auto calcPos = m_window->m_position;
+ auto calcSize = m_window->m_size;
+
+ const static auto REQUESTEDRATIO = CConfigValue<Hyprlang::VEC2>("layout:single_window_aspect_ratio");
+ const static auto REQUESTEDRATIOTOLERANCE = CConfigValue<Hyprlang::FLOAT>("layout:single_window_aspect_ratio_tolerance");
+
+ Vector2D ratioPadding;
+
+ if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1) {
+ const Vector2D originalSize = MONITOR_WORKAREA.size();
+
+ const double requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y;
+ const double originalRatio = originalSize.x / originalSize.y;
+
+ if (requestedRatio > originalRatio) {
+ double padding = originalSize.y - (originalSize.x / requestedRatio);
+
+ if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.y)
+ ratioPadding = Vector2D{0., padding};
+ } else if (requestedRatio < originalRatio) {
+ double padding = originalSize.x - (originalSize.y * requestedRatio);
+
+ if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.x)
+ ratioPadding = Vector2D{padding, 0.};
+ }
+ }
+
+ const auto GAPOFFSETTOPLEFT = Vector2D(sc<double>(DISPLAYLEFT ? 0 : (DISPLAYINVERSELEFT ? 2 * gapsIn.m_left : gapsIn.m_left)), sc<double>(DISPLAYTOP ? 0 : gapsIn.m_top));
+
+ const auto GAPOFFSETBOTTOMRIGHT =
+ Vector2D(sc<double>(DISPLAYRIGHT ? 0 : (DISPLAYINVERSERIGHT ? 2 * gapsIn.m_right : gapsIn.m_right)), sc<double>(DISPLAYBOTTOM ? 0 : gapsIn.m_bottom));
+
+ calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2;
+ calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding;
+
+ if (isPseudo()) {
+ // Calculate pseudo
+ float scale = 1;
+
+ // adjust if doesn't fit
+ if (m_pseudoSize.x > calcSize.x || m_pseudoSize.y > calcSize.y) {
+ if (m_pseudoSize.x > calcSize.x)
+ scale = calcSize.x / m_pseudoSize.x;
+
+ if (m_pseudoSize.y * scale > calcSize.y)
+ scale = calcSize.y / m_pseudoSize.y;
+
+ auto DELTA = calcSize - m_pseudoSize * scale;
+ calcSize = m_pseudoSize * scale;
+ calcPos = calcPos + DELTA / 2.f; // center
+ } else {
+ auto DELTA = calcSize - m_pseudoSize;
+ calcPos = calcPos + DELTA / 2.f; // center
+ calcSize = m_pseudoSize;
+ }
+ }
+
+ const auto RESERVED = m_window->getFullWindowReservedArea();
+ calcPos = calcPos + RESERVED.topLeft;
+ calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight);
+
+ Vector2D availableSpace = calcSize;
+
+ static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>("misc:size_limits_tiled");
+
+ if (*PCLAMP_TILED) {
+ const auto borderSize = m_window->getRealBorderSize();
+ Vector2D monitorAvailable = MONITOR_WORKAREA.size() - Vector2D{2.0 * borderSize, 2.0 * borderSize};
+
+ Vector2D minSize = m_window->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable);
+ Vector2D maxSize = m_window->isFullscreen() ? Vector2D{INFINITY, INFINITY} :
+ m_window->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable);
+ calcSize = calcSize.clamp(minSize, maxSize);
+
+ calcPos += (availableSpace - calcSize) / 2.0;
+
+ calcPos.x = std::clamp(calcPos.x, MONITOR_WORKAREA.x + borderSize, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w - calcSize.x - borderSize);
+ calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y + borderSize, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y - borderSize);
+ }
+
+ if (m_window->onSpecialWorkspace() && !m_window->isFullscreen()) {
+ // if special, we adjust the coords a bit
+ static auto PSCALEFACTOR = CConfigValue<Hyprlang::FLOAT>("dwindle:special_scale_factor");
+
+ CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR};
+ wb.round(); // avoid rounding mess
+
+ *m_window->m_realPosition = wb.pos();
+ *m_window->m_realSize = wb.size();
+ } else {
+ CBox wb = {calcPos, calcSize};
+ wb.round(); // avoid rounding mess
+
+ *m_window->m_realSize = wb.size();
+ *m_window->m_realPosition = wb.pos();
+ }
+
+ m_window->updateWindowDecos();
+}
+
+void CWindowTarget::assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint) {
+ if (!space) {
+ ITarget::assignToSpace(space, focalPoint);
+ return;
+ }
+
+ // keep the ref here so that moveToWorkspace doesn't unref the workspace
+ // and assignToSpace doesn't think this is a new target because space wp is dead
+ const auto WSREF = space->workspace();
+
+ m_window->m_monitor = space->workspace()->m_monitor;
+ m_window->moveToWorkspace(space->workspace());
+
+ // layout and various update fns want the target to already have m_workspace set
+ ITarget::assignToSpace(space, focalPoint);
+
+ m_window->updateToplevel();
+ m_window->updateWindowDecos();
+}
+
+bool CWindowTarget::floating() {
+ return m_window->m_isFloating;
+}
+
+void CWindowTarget::setFloating(bool x) {
+ if (x == m_window->m_isFloating)
+ return;
+
+ m_window->m_isFloating = x;
+ m_window->m_pinned = false;
+
+ m_window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FLOATING);
+}
+
+Vector2D CWindowTarget::clampSizeForDesired(const Vector2D& size) const {
+ Vector2D newSize = size;
+ if (const auto m = m_window->minSize(); m)
+ newSize = newSize.clamp(*m);
+ if (const auto m = m_window->maxSize(); m)
+ newSize = newSize.clamp(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}, *m);
+ return newSize;
+}
+
+std::expected<SGeometryRequested, eGeometryFailure> CWindowTarget::desiredGeometry() {
+
+ SGeometryRequested requested;
+
+ CBox DESIRED_GEOM = g_pXWaylandManager->getGeometryForWindow(m_window.lock());
+ const auto PMONITOR = m_window->m_monitor.lock();
+
+ requested.size = clampSizeForDesired(DESIRED_GEOM.size());
+
+ if (m_window->m_isX11) {
+ Vector2D xy = {DESIRED_GEOM.x, DESIRED_GEOM.y};
+ xy = g_pXWaylandManager->xwaylandToWaylandCoords(xy);
+ requested.pos = xy;
+ }
+
+ const auto STOREDSIZE = m_window->m_ruleApplicator->persistentSize().valueOrDefault() ? g_pConfigManager->getStoredFloatingSize(m_window.lock()) : std::nullopt;
+
+ if (STOREDSIZE)
+ requested.size = clampSizeForDesired(*STOREDSIZE);
+
+ if (!PMONITOR) {
+ Log::logger->log(Log::ERR, "{:m} has an invalid monitor in desiredGeometry!", m_window.lock());
+ return std::unexpected(GEOMETRY_NO_DESIRED);
+ }
+
+ if (DESIRED_GEOM.width <= 2 || DESIRED_GEOM.height <= 2) {
+ const auto SURFACE = m_window->wlSurface()->resource();
+
+ if (SURFACE->m_current.size.x > 5 && SURFACE->m_current.size.y > 5) {
+ // center on mon and call it a day
+ requested.pos.reset();
+ requested.size = clampSizeForDesired(SURFACE->m_current.size);
+ return requested;
+ }
+
+ if (m_window->m_isX11 && m_window->isX11OverrideRedirect()) {
+ // check OR windows, they like their shit
+ const auto SIZE = clampSizeForDesired(m_window->m_xwaylandSurface->m_geometry.w > 0 && m_window->m_xwaylandSurface->m_geometry.h > 0 ?
+ m_window->m_xwaylandSurface->m_geometry.size() :
+ Vector2D{600, 400});
+
+ if (m_window->m_xwaylandSurface->m_geometry.x != 0 && m_window->m_xwaylandSurface->m_geometry.y != 0) {
+ requested.size = SIZE;
+ requested.pos = g_pXWaylandManager->xwaylandToWaylandCoords(m_window->m_xwaylandSurface->m_geometry.pos());
+ return requested;
+ }
+ }
+
+ return std::unexpected(m_window->m_isX11 && m_window->isX11OverrideRedirect() ? GEOMETRY_INVALID_DESIRED : GEOMETRY_NO_DESIRED);
+ }
+
+ // TODO: detect a popup in a more consistent way.
+ if ((DESIRED_GEOM.x == 0 && DESIRED_GEOM.y == 0) || !m_window->m_isX11) {
+ // middle of parent if available
+ if (!m_window->m_isX11) {
+ if (const auto PARENT = m_window->parent(); PARENT) {
+ const auto POS = PARENT->m_realPosition->goal() + PARENT->m_realSize->goal() / 2.F - DESIRED_GEOM.size() / 2.F;
+ requested.pos = POS;
+ }
+ }
+ } else {
+ // if it is, we respect where it wants to put itself, but apply monitor offset if outside
+ // most of these are popups
+
+ Vector2D pos;
+
+ if (const auto POPENMON = g_pCompositor->getMonitorFromVector(DESIRED_GEOM.middle()); POPENMON->m_id != PMONITOR->m_id)
+ pos = Vector2D(DESIRED_GEOM.x, DESIRED_GEOM.y) - POPENMON->m_position + PMONITOR->m_position;
+ else
+ pos = Vector2D(DESIRED_GEOM.x, DESIRED_GEOM.y);
+
+ requested.pos = pos;
+ }
+
+ if (DESIRED_GEOM.w <= 2 || DESIRED_GEOM.h <= 2)
+ return std::unexpected(GEOMETRY_NO_DESIRED);
+
+ return requested;
+}
+
+PHLWINDOW CWindowTarget::window() const {
+ return m_window.lock();
+}
+
+eFullscreenMode CWindowTarget::fullscreenMode() {
+ return m_window->m_fullscreenState.internal;
+}
+
+void CWindowTarget::setFullscreenMode(eFullscreenMode mode) {
+ if (floating() && m_window->m_fullscreenState.internal == FSMODE_NONE)
+ rememberFloatingSize(m_box.size());
+
+ m_window->m_fullscreenState.internal = mode;
+}
+
+std::optional<Vector2D> CWindowTarget::minSize() {
+ return m_window->minSize();
+}
+
+std::optional<Vector2D> CWindowTarget::maxSize() {
+ return m_window->maxSize();
+}
+
+void CWindowTarget::damageEntire() {
+ g_pHyprRenderer->damageWindow(m_window.lock());
+}
+
+void CWindowTarget::warpPositionSize() {
+ m_window->m_realSize->warp();
+ m_window->m_realPosition->warp();
+ m_window->updateWindowDecos();
+}
+
+void CWindowTarget::onUpdateSpace() {
+ if (!space())
+ return;
+
+ m_window->m_monitor = space()->workspace()->m_monitor;
+ m_window->moveToWorkspace(space()->workspace());
+ m_window->updateToplevel();
+ m_window->updateWindowDecos();
+}
diff --git a/src/layout/target/WindowTarget.hpp b/src/layout/target/WindowTarget.hpp
new file mode 100644
index 00000000..2939fd74
--- /dev/null
+++ b/src/layout/target/WindowTarget.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "Target.hpp"
+
+#include "../../desktop/view/Window.hpp"
+
+namespace Layout {
+
+ class CWindowTarget : public ITarget {
+ public:
+ static SP<ITarget> create(PHLWINDOW w);
+ virtual ~CWindowTarget() = default;
+
+ virtual eTargetType type();
+
+ virtual void setPositionGlobal(const CBox& box);
+ virtual void assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint = std::nullopt);
+ virtual PHLWINDOW window() const;
+
+ virtual bool floating();
+ virtual void setFloating(bool x);
+ virtual std::expected<SGeometryRequested, eGeometryFailure> desiredGeometry();
+ virtual eFullscreenMode fullscreenMode();
+ virtual void setFullscreenMode(eFullscreenMode mode);
+ virtual std::optional<Vector2D> minSize();
+ virtual std::optional<Vector2D> maxSize();
+ virtual void damageEntire();
+ virtual void warpPositionSize();
+ virtual void onUpdateSpace();
+
+ private:
+ CWindowTarget(PHLWINDOW w);
+
+ Vector2D clampSizeForDesired(const Vector2D& size) const;
+
+ void updatePos();
+
+ PHLWINDOWREF m_window;
+ };
+}; \ No newline at end of file
diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp
index 74da3572..777f6bbe 100644
--- a/src/managers/KeybindManager.cpp
+++ b/src/managers/KeybindManager.cpp
@@ -19,13 +19,19 @@
#include "../managers/HookSystemManager.hpp"
#include "../managers/input/InputManager.hpp"
#include "../managers/animation/DesktopAnimationManager.hpp"
-#include "../managers/LayoutManager.hpp"
#include "../managers/EventManager.hpp"
#include "../render/Renderer.hpp"
#include "../hyprerror/HyprError.hpp"
#include "../config/ConfigManager.hpp"
#include "../desktop/rule/windowRule/WindowRule.hpp"
#include "../desktop/rule/Engine.hpp"
+#include "../desktop/view/Group.hpp"
+#include "../layout/LayoutManager.hpp"
+#include "../layout/target/WindowTarget.hpp"
+#include "../layout/space/Space.hpp"
+#include "../layout/algorithm/Algorithm.hpp"
+#include "../layout/algorithm/tiled/master/MasterAlgorithm.hpp"
+#include "../layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp"
#include <optional>
#include <iterator>
@@ -322,10 +328,10 @@ void CKeybindManager::updateXKBTranslationState() {
}
bool CKeybindManager::ensureMouseBindState() {
- if (!g_pInputManager->m_currentlyDraggedWindow)
+ if (!g_layoutManager->dragController()->target())
return false;
- if (!g_pInputManager->m_currentlyDraggedWindow.expired()) {
+ if (g_layoutManager->dragController()->target()) {
changeMouseBindMode(MBIND_INVALID);
return true;
}
@@ -368,7 +374,7 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) {
const auto PNEWWINDOW = PNEWWORKSPACE->getLastFocusedWindow();
if (PNEWWINDOW) {
updateRelativeCursorCoords();
- Desktop::focusState()->fullWindowFocus(PNEWWINDOW);
+ Desktop::focusState()->fullWindowFocus(PNEWWINDOW, Desktop::FOCUS_REASON_KEYBIND);
PNEWWINDOW->warpCursor();
if (*PNOWARPS == 0 || *PFOLLOWMOUSE < 2) {
@@ -377,7 +383,7 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) {
g_pInputManager->m_forcedFocus.reset();
}
} else {
- Desktop::focusState()->rawWindowFocus(nullptr);
+ Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND);
g_pCompositor->warpCursorTo(monitor->middle());
}
Desktop::focusState()->rawMonitorFocus(monitor);
@@ -398,10 +404,10 @@ void CKeybindManager::switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCy
g_pInputManager->unconstrainMouse();
if (PLASTWINDOW && PLASTWINDOW->m_workspace == PWINDOWTOCHANGETO->m_workspace && PLASTWINDOW->isFullscreen())
- Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, nullptr, forceFSCycle);
+ Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle);
else {
updateRelativeCursorCoords();
- Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, nullptr, forceFSCycle);
+ Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle);
PWINDOWTOCHANGETO->warpCursor();
// Move mouse focus to the new window if required by current follow_mouse and warp modes
@@ -751,9 +757,9 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
// Require mouse to stay inside drag_threshold for clicks, outside for drags
// Check if either a mouse bind has triggered or currently over the threshold (maybe there is no mouse bind on the same key)
const auto THRESHOLDREACHED = key.mousePosAtPress.distanceSq(g_pInputManager->getMouseCoordsInternal()) > std::pow(*PDRAGTHRESHOLD, 2);
- if (k->click && (g_pInputManager->m_dragThresholdReached || THRESHOLDREACHED))
+ if (k->click && (g_layoutManager->dragController()->dragThresholdReached() || THRESHOLDREACHED))
continue;
- else if (k->drag && !g_pInputManager->m_dragThresholdReached && !THRESHOLDREACHED)
+ else if (k->drag && !g_layoutManager->dragController()->dragThresholdReached() && !THRESHOLDREACHED)
continue;
}
@@ -810,7 +816,7 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
found = true;
}
- g_pInputManager->m_dragThresholdReached = false;
+ g_layoutManager->dragController()->resetDragThresholdReached();
// if keybind wasn't found (or dispatcher said to) then pass event
res.passEvent |= !found;
@@ -1112,32 +1118,16 @@ static SDispatchResult toggleActiveFloatingCore(std::string args, std::optional<
return {};
// remove drag status
- if (!g_pInputManager->m_currentlyDraggedWindow.expired())
+ if (g_layoutManager->dragController()->target())
CKeybindManager::changeMouseBindMode(MBIND_INVALID);
- if (PWINDOW->m_groupData.pNextWindow.lock() && PWINDOW->m_groupData.pNextWindow.lock() != PWINDOW) {
- const auto PCURRENT = PWINDOW->getGroupCurrent();
-
- PCURRENT->m_isFloating = !PCURRENT->m_isFloating;
- g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(PCURRENT);
-
- PHLWINDOW curr = PCURRENT->m_groupData.pNextWindow.lock();
- while (curr != PCURRENT) {
- curr->m_isFloating = PCURRENT->m_isFloating;
- curr = curr->m_groupData.pNextWindow.lock();
- }
- } else {
- PWINDOW->m_isFloating = !PWINDOW->m_isFloating;
-
- g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(PWINDOW);
- }
+ g_layoutManager->changeFloatingMode(PWINDOW->layoutTarget());
if (PWINDOW->m_workspace) {
PWINDOW->m_workspace->updateWindows();
PWINDOW->m_workspace->updateWindowData();
}
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID());
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
return {};
@@ -1163,8 +1153,7 @@ SDispatchResult CKeybindManager::centerWindow(std::string args) {
const auto PMONITOR = PWINDOW->m_monitor.lock();
- *PWINDOW->m_realPosition = PMONITOR->logicalBoxMinusReserved().middle() - PWINDOW->m_realSize->goal() / 2.f;
- PWINDOW->m_position = PWINDOW->m_realPosition->goal();
+ PWINDOW->layoutTarget()->setPositionGlobal(CBox{PMONITOR->logicalBoxMinusReserved().middle() - PWINDOW->m_realSize->goal() / 2.F, PWINDOW->layoutTarget()->position().size()});
return {};
}
@@ -1180,10 +1169,7 @@ SDispatchResult CKeybindManager::toggleActivePseudo(std::string args) {
if (!PWINDOW)
return {.success = false, .error = "Window not found"};
- PWINDOW->m_isPseudotiled = !PWINDOW->m_isPseudotiled;
-
- if (!PWINDOW->isFullscreen())
- g_pLayoutManager->getCurrentLayout()->recalculateWindow(PWINDOW);
+ PWINDOW->layoutTarget()->setPseudo(!PWINDOW->layoutTarget()->isPseudo());
return {};
}
@@ -1276,7 +1262,7 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) {
if (PMONITOR != PMONITORWORKSPACEOWNER) {
Vector2D middle = PMONITORWORKSPACEOWNER->middle();
if (const auto PLAST = pWorkspaceToChangeTo->getLastFocusedWindow(); PLAST) {
- Desktop::focusState()->fullWindowFocus(PLAST);
+ Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);
if (*PWORKSPACECENTERON == 1)
middle = PLAST->middle();
}
@@ -1421,7 +1407,7 @@ SDispatchResult CKeybindManager::moveActiveToWorkspace(std::string args) {
pMonitor->changeWorkspace(pWorkspace);
- Desktop::focusState()->fullWindowFocus(PWINDOW);
+ Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND);
PWINDOW->warpCursor();
return {};
@@ -1465,7 +1451,7 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) {
if (const auto PATCOORDS =
g_pCompositor->vectorToWindowUnified(OLDMIDDLE, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, PWINDOW);
PATCOORDS)
- Desktop::focusState()->fullWindowFocus(PATCOORDS);
+ Desktop::focusState()->fullWindowFocus(PATCOORDS, Desktop::FOCUS_REASON_KEYBIND);
else
g_pInputManager->refocus();
}
@@ -1474,38 +1460,35 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) {
}
SDispatchResult CKeybindManager::moveFocusTo(std::string args) {
- static auto PFULLCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_fullscreen");
- static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback");
- static auto PGROUPCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_groupfirst");
- char arg = args[0];
+ static auto PFULLCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_fullscreen");
+ static auto PGROUPCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_groupfirst");
+ Math::eDirection dir = Math::fromChar(args[0]);
- if (!isDirection(args)) {
- Log::logger->log(Log::ERR, "Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg);
- return {.success = false, .error = std::format("Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)};
+ if (dir == Math::DIRECTION_DEFAULT) {
+ Log::logger->log(Log::ERR, "Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]);
+ return {.success = false, .error = std::format("Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])};
}
const auto PLASTWINDOW = Desktop::focusState()->window();
if (!PLASTWINDOW || !PLASTWINDOW->aliveAndVisible()) {
- if (*PMONITORFALLBACK)
- tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg));
-
+ tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir));
return {};
}
const auto PWINDOWTOCHANGETO = *PFULLCYCLE && PLASTWINDOW->isFullscreen() ?
- g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, arg != 'd' && arg != 'b' && arg != 'r') :
- g_pCompositor->getWindowInDirection(PLASTWINDOW, arg);
+ g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, dir != Math::DIRECTION_DOWN && dir != Math::DIRECTION_RIGHT) :
+ g_pCompositor->getWindowInDirection(PLASTWINDOW, dir);
// Prioritize focus change within groups if the window is a part of it.
- if (*PGROUPCYCLE && PLASTWINDOW->m_groupData.pNextWindow) {
+ if (*PGROUPCYCLE && PLASTWINDOW->m_group) {
auto isTheOnlyGroupOnWs = !PWINDOWTOCHANGETO && g_pCompositor->m_monitors.size() == 1;
- if (arg == 'l' && (PLASTWINDOW != PLASTWINDOW->getGroupHead() || isTheOnlyGroupOnWs)) {
- PLASTWINDOW->setGroupCurrent(PLASTWINDOW->getGroupPrevious());
+ if (dir == Math::DIRECTION_LEFT && (PLASTWINDOW != PLASTWINDOW->m_group->head() || isTheOnlyGroupOnWs)) {
+ PLASTWINDOW->m_group->moveCurrent(false);
return {};
}
- else if (arg == 'r' && (PLASTWINDOW != PLASTWINDOW->getGroupTail() || isTheOnlyGroupOnWs)) {
- PLASTWINDOW->setGroupCurrent(PLASTWINDOW->m_groupData.pNextWindow.lock());
+ else if (dir == Math::DIRECTION_RIGHT && (PLASTWINDOW != PLASTWINDOW->m_group->tail() || isTheOnlyGroupOnWs)) {
+ PLASTWINDOW->m_group->moveCurrent(true);
return {};
}
}
@@ -1516,52 +1499,51 @@ SDispatchResult CKeybindManager::moveFocusTo(std::string args) {
return {};
}
- Log::logger->log(Log::DEBUG, "No window found in direction {}, looking for a monitor", arg);
+ Log::logger->log(Log::DEBUG, "No window found in direction {}, looking for a monitor", Math::toString(dir));
- if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg)))
+ if (tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir)))
return {};
static auto PNOFALLBACK = CConfigValue<Hyprlang::INT>("general:no_focus_fallback");
if (*PNOFALLBACK)
- return {.success = false, .error = std::format("Nothing to focus to in direction {}", arg)};
+ return {.success = false, .error = std::format("Nothing to focus to in direction {}", Math::toString(dir))};
- Log::logger->log(Log::DEBUG, "No monitor found in direction {}, getting the inverse edge", arg);
+ Log::logger->log(Log::DEBUG, "No monitor found in direction {}, getting the inverse edge", Math::toString(dir));
const auto PMONITOR = PLASTWINDOW->m_monitor.lock();
if (!PMONITOR)
return {.success = false, .error = "last window has no monitor?"};
- if (arg == 'l' || arg == 'r') {
+ if (dir == Math::DIRECTION_LEFT || dir == Math::DIRECTION_RIGHT) {
if (STICKS(PLASTWINDOW->m_position.x, PMONITOR->m_position.x) && STICKS(PLASTWINDOW->m_size.x, PMONITOR->m_size.x))
return {.success = false, .error = "move does not make sense, would return back"};
} else if (STICKS(PLASTWINDOW->m_position.y, PMONITOR->m_position.y) && STICKS(PLASTWINDOW->m_size.y, PMONITOR->m_size.y))
return {.success = false, .error = "move does not make sense, would return back"};
CBox box = PMONITOR->logicalBox();
- switch (arg) {
- case 'l':
+ switch (dir) {
+ case Math::DIRECTION_LEFT:
box.x += box.w;
box.w = 1;
break;
- case 'r':
+ case Math::DIRECTION_RIGHT:
box.x -= 1;
box.w = 1;
break;
- case 'u':
- case 't':
+ case Math::DIRECTION_UP:
box.y += box.h;
box.h = 1;
break;
- case 'd':
- case 'b':
+ case Math::DIRECTION_DOWN:
box.y -= 1;
box.h = 1;
break;
+ default: break;
}
const auto PWINDOWCANDIDATE = g_pCompositor->getWindowInDirection(box, PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace,
- arg, PLASTWINDOW, PLASTWINDOW->m_isFloating);
+ dir, PLASTWINDOW, PLASTWINDOW->m_isFloating);
if (PWINDOWCANDIDATE)
switchToWindow(PWINDOWCANDIDATE);
@@ -1598,7 +1580,6 @@ SDispatchResult CKeybindManager::focusCurrentOrLast(std::string args) {
}
SDispatchResult CKeybindManager::swapActive(std::string args) {
- char arg = args[0];
const auto PLASTWINDOW = Desktop::focusState()->window();
PHLWINDOW PWINDOWTOCHANGETO = nullptr;
@@ -1608,9 +1589,10 @@ SDispatchResult CKeybindManager::swapActive(std::string args) {
if (PLASTWINDOW->isFullscreen())
return {.success = false, .error = "Can't swap fullscreen window"};
- if (isDirection(args))
- PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, arg);
- else
+ if (isDirection(args)) {
+ Math::eDirection dir = Math::fromChar(args[0]);
+ PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, dir);
+ } else
PWINDOWTOCHANGETO = g_pCompositor->getWindowByRegex(args);
if (!PWINDOWTOCHANGETO || PWINDOWTOCHANGETO == PLASTWINDOW) {
@@ -1621,13 +1603,12 @@ SDispatchResult CKeybindManager::swapActive(std::string args) {
Log::logger->log(Log::DEBUG, "Swapping active window with {}", args);
updateRelativeCursorCoords();
- g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, PWINDOWTOCHANGETO);
+ g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), PWINDOWTOCHANGETO->layoutTarget(), true);
PLASTWINDOW->warpCursor();
return {};
}
SDispatchResult CKeybindManager::moveActiveTo(std::string args) {
- char arg = args[0];
bool silent = args.ends_with(" silent");
if (silent)
args = args.substr(0, args.length() - 7);
@@ -1645,9 +1626,10 @@ SDispatchResult CKeybindManager::moveActiveTo(std::string args) {
return {};
}
- if (!isDirection(args)) {
- Log::logger->log(Log::ERR, "Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg);
- return {.success = false, .error = std::format("Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)};
+ Math::eDirection dir = Math::fromChar(args[0]);
+ if (dir == Math::DIRECTION_DEFAULT) {
+ Log::logger->log(Log::ERR, "Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]);
+ return {.success = false, .error = std::format("Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])};
}
const auto PLASTWINDOW = Desktop::focusState()->window();
@@ -1658,59 +1640,11 @@ SDispatchResult CKeybindManager::moveActiveTo(std::string args) {
if (PLASTWINDOW->isFullscreen())
return {.success = false, .error = "Can't move fullscreen window"};
- if (PLASTWINDOW->m_isFloating) {
- std::optional<float> vPosx, vPosy;
- const auto PMONITOR = PLASTWINDOW->m_monitor.lock();
- const auto BORDERSIZE = PLASTWINDOW->getRealBorderSize();
- static auto PGAPSCUSTOMDATA = CConfigValue<Hyprlang::CUSTOMTYPE>("general:float_gaps");
- static auto PGAPSOUTDATA = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_out");
- auto* PGAPSOUT = sc<CCssGapData*>(PGAPSCUSTOMDATA.ptr()->getData());
- if (PGAPSOUT->m_left < 0 || PGAPSOUT->m_right < 0 || PGAPSOUT->m_top < 0 || PGAPSOUT->m_bottom < 0)
- PGAPSOUT = sc<CCssGapData*>(PGAPSOUTDATA.ptr()->getData());
-
- switch (arg) {
- case 'l': vPosx = PMONITOR->m_reservedArea.left() + BORDERSIZE + PMONITOR->m_position.x + PGAPSOUT->m_left; break;
- case 'r':
- vPosx = PMONITOR->m_size.x - PMONITOR->m_reservedArea.right() - PLASTWINDOW->m_realSize->goal().x - BORDERSIZE + PMONITOR->m_position.x - PGAPSOUT->m_right;
- break;
- case 't':
- case 'u': vPosy = PMONITOR->m_reservedArea.top() + BORDERSIZE + PMONITOR->m_position.y + PGAPSOUT->m_top; break;
- case 'b':
- case 'd':
- vPosy = PMONITOR->m_size.y - PMONITOR->m_reservedArea.bottom() - PLASTWINDOW->m_realSize->goal().y - BORDERSIZE + PMONITOR->m_position.y - PGAPSOUT->m_bottom;
- break;
- }
-
- *PLASTWINDOW->m_realPosition = Vector2D(vPosx.value_or(PLASTWINDOW->m_realPosition->goal().x), vPosy.value_or(PLASTWINDOW->m_realPosition->goal().y));
-
- return {};
- }
-
- // If the window to change to is on the same workspace, switch them
- const auto PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, arg);
- if (PWINDOWTOCHANGETO) {
- updateRelativeCursorCoords();
-
- g_pLayoutManager->getCurrentLayout()->moveWindowTo(PLASTWINDOW, args, silent);
- if (!silent)
- PLASTWINDOW->warpCursor();
- return {};
- }
-
- static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback");
- if (!*PMONITORFALLBACK)
- return {};
-
- // Otherwise, we always want to move to the next monitor in that direction
- const auto PMONITORTOCHANGETO = g_pCompositor->getMonitorInDirection(arg);
- if (!PMONITORTOCHANGETO)
- return {.success = false, .error = "Nowhere to move active window to"};
+ updateRelativeCursorCoords();
- const auto PWORKSPACE = PMONITORTOCHANGETO->m_activeWorkspace;
- if (silent)
- moveActiveToWorkspaceSilent(PWORKSPACE->getConfigName());
- else
- moveActiveToWorkspace(PWORKSPACE->getConfigName());
+ g_layoutManager->moveInDirection(PLASTWINDOW->layoutTarget(), args, silent);
+ if (!silent)
+ PLASTWINDOW->warpCursor();
return {};
}
@@ -1724,10 +1658,10 @@ SDispatchResult CKeybindManager::toggleGroup(std::string args) {
if (PWINDOW->isFullscreen())
g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);
- if (PWINDOW->m_groupData.pNextWindow.expired())
- PWINDOW->createGroup();
+ if (!PWINDOW->m_group)
+ PWINDOW->m_group = Desktop::View::CGroup::create({PWINDOW});
else
- PWINDOW->destroyGroup();
+ PWINDOW->m_group->destroy();
return {};
}
@@ -1738,89 +1672,40 @@ SDispatchResult CKeybindManager::changeGroupActive(std::string args) {
if (!PWINDOW)
return {.success = false, .error = "Window not found"};
- if (PWINDOW->m_groupData.pNextWindow.expired())
- return {.success = false, .error = "No next window in group"};
+ if (!PWINDOW->m_group)
+ return {.success = false, .error = "No group"};
- if (PWINDOW->m_groupData.pNextWindow.lock() == PWINDOW)
+ if (PWINDOW->m_group->size() == 1)
return {.success = false, .error = "Only one window in group"};
if (isNumber(args, false)) {
// index starts from '1'; '0' means last window
- const int INDEX = std::stoi(args);
- if (INDEX > PWINDOW->getGroupSize())
- return {.success = false, .error = "Index too big, there aren't that many windows in this group"};
- if (INDEX == 0)
- PWINDOW->setGroupCurrent(PWINDOW->getGroupTail());
- else
- PWINDOW->setGroupCurrent(PWINDOW->getGroupWindowByIndex(INDEX - 1));
+ try {
+ const int INDEX = std::stoi(args);
+ PWINDOW->m_group->setCurrent(INDEX);
+ } catch (...) { return {.success = false, .error = "invalid idx"}; }
+
return {};
}
if (args != "b" && args != "prev")
- PWINDOW->setGroupCurrent(PWINDOW->m_groupData.pNextWindow.lock());
+ PWINDOW->m_group->moveCurrent(true);
else
- PWINDOW->setGroupCurrent(PWINDOW->getGroupPrevious());
+ PWINDOW->m_group->moveCurrent(false);
return {};
}
SDispatchResult CKeybindManager::toggleSplit(std::string args) {
- SLayoutMessageHeader header;
- header.pWindow = Desktop::focusState()->window();
-
- if (!header.pWindow)
- return {.success = false, .error = "Window not found"};
-
- const auto PWORKSPACE = header.pWindow->m_workspace;
-
- if (PWORKSPACE->m_hasFullscreenWindow)
- return {.success = false, .error = "Can't split windows that already split"};
-
- g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "togglesplit");
-
- return {};
+ return {.success = false, .error = "removed - use layoutmsg"};
}
SDispatchResult CKeybindManager::swapSplit(std::string args) {
- SLayoutMessageHeader header;
- header.pWindow = Desktop::focusState()->window();
-
- if (!header.pWindow)
- return {.success = false, .error = "Window not found"};
-
- const auto PWORKSPACE = header.pWindow->m_workspace;
-
- if (PWORKSPACE->m_hasFullscreenWindow)
- return {.success = false, .error = "Can't split windows that already split"};
-
- g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "swapsplit");
-
- return {};
+ return {.success = false, .error = "removed - use layoutmsg"};
}
SDispatchResult CKeybindManager::alterSplitRatio(std::string args) {
- std::optional<float> splitResult;
- bool exact = false;
-
- if (args.starts_with("exact")) {
- exact = true;
- splitResult = getPlusMinusKeywordResult(args.substr(5), 0);
- } else
- splitResult = getPlusMinusKeywordResult(args, 0);
-
- if (!splitResult.has_value()) {
- Log::logger->log(Log::ERR, "Splitratio invalid in alterSplitRatio!");
- return {.success = false, .error = "Splitratio invalid in alterSplitRatio!"};
- }
-
- const auto PLASTWINDOW = Desktop::focusState()->window();
-
- if (!PLASTWINDOW)
- return {.success = false, .error = "Window not found"};
-
- g_pLayoutManager->getCurrentLayout()->alterSplitRatio(PLASTWINDOW, splitResult.value(), exact);
-
- return {};
+ return {.success = false, .error = "removed - use layoutmsg"};
}
SDispatchResult CKeybindManager::focusMonitor(std::string arg) {
@@ -1903,58 +1788,7 @@ SDispatchResult CKeybindManager::moveCursor(std::string args) {
}
SDispatchResult CKeybindManager::workspaceOpt(std::string args) {
-
- // current workspace
- const auto PWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace;
-
- if (!PWORKSPACE)
- return {.success = false, .error = "Workspace not found"}; // ????
-
- if (args == "allpseudo") {
- PWORKSPACE->m_defaultPseudo = !PWORKSPACE->m_defaultPseudo;
-
- // apply
- for (auto const& w : g_pCompositor->m_windows) {
- if (!w->m_isMapped || w->m_workspace != PWORKSPACE)
- continue;
-
- w->m_isPseudotiled = PWORKSPACE->m_defaultPseudo;
- }
- } else if (args == "allfloat") {
- PWORKSPACE->m_defaultFloating = !PWORKSPACE->m_defaultFloating;
- // apply
-
- // we make a copy because changeWindowFloatingMode might invalidate the iterator
- std::vector<PHLWINDOW> ptrs(g_pCompositor->m_windows.begin(), g_pCompositor->m_windows.end());
-
- for (auto const& w : ptrs) {
- if (!w->m_isMapped || w->m_workspace != PWORKSPACE || w->isHidden())
- continue;
-
- if (!w->m_requestsFloat && w->m_isFloating != PWORKSPACE->m_defaultFloating) {
- const auto SAVEDPOS = w->m_realPosition->goal();
- const auto SAVEDSIZE = w->m_realSize->goal();
-
- w->m_isFloating = PWORKSPACE->m_defaultFloating;
- g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(w);
-
- if (PWORKSPACE->m_defaultFloating) {
- w->m_realPosition->setValueAndWarp(SAVEDPOS);
- w->m_realSize->setValueAndWarp(SAVEDSIZE);
- *w->m_realSize = w->m_realSize->value() + Vector2D(4, 4);
- *w->m_realPosition = w->m_realPosition->value() - Vector2D(2, 2);
- }
- }
- }
- } else {
- Log::logger->log(Log::ERR, "Invalid arg in workspaceOpt, opt \"{}\" doesn't exist.", args);
- return {.success = false, .error = std::format("Invalid arg in workspaceOpt, opt \"{}\" doesn't exist.", args)};
- }
-
- // recalc mon
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(Desktop::focusState()->monitor()->m_id);
-
- return {};
+ return {.success = false, .error = "workspaceopt is deprecated"};
}
SDispatchResult CKeybindManager::renameWorkspace(std::string args) {
@@ -2183,7 +2017,7 @@ SDispatchResult CKeybindManager::resizeActive(std::string args) {
if (SIZ.x < 1 || SIZ.y < 1)
return {.success = false, .error = "Invalid size provided"};
- g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PLASTWINDOW->m_realSize->goal());
+ g_layoutManager->resizeTarget(SIZ - PLASTWINDOW->m_realSize->goal(), PLASTWINDOW->layoutTarget());
if (PLASTWINDOW->m_realSize->goal().x > 1 && PLASTWINDOW->m_realSize->goal().y > 1)
PLASTWINDOW->setHidden(false);
@@ -2202,7 +2036,7 @@ SDispatchResult CKeybindManager::moveActive(std::string args) {
const auto POS = g_pCompositor->parseWindowVectorArgsRelative(args, PLASTWINDOW->m_realPosition->goal());
- g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PLASTWINDOW->m_realPosition->goal());
+ g_layoutManager->moveTarget(POS - PLASTWINDOW->m_realPosition->goal(), PLASTWINDOW->layoutTarget());
return {};
}
@@ -2224,7 +2058,7 @@ SDispatchResult CKeybindManager::moveWindow(std::string args) {
const auto POS = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_realPosition->goal());
- g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PWINDOW->m_realPosition->goal(), PWINDOW);
+ g_layoutManager->moveTarget(POS - PWINDOW->m_realPosition->goal(), PWINDOW->layoutTarget());
return {};
}
@@ -2249,7 +2083,7 @@ SDispatchResult CKeybindManager::resizeWindow(std::string args) {
if (SIZ.x < 1 || SIZ.y < 1)
return {.success = false, .error = "Invalid size provided"};
- g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PWINDOW->m_realSize->goal(), CORNER_NONE, PWINDOW);
+ g_layoutManager->resizeTarget(SIZ - PWINDOW->m_realSize->goal(), PWINDOW->layoutTarget(), Layout::CORNER_NONE);
if (PWINDOW->m_realSize->goal().x > 1 && PWINDOW->m_realSize->goal().y > 1)
PWINDOW->setHidden(false);
@@ -2271,14 +2105,32 @@ SDispatchResult CKeybindManager::circleNext(std::string arg) {
CVarList args{arg, 0, 's', true};
+ const auto PREV = args.contains("prev") || args.contains("p") || args.contains("last") || args.contains("l");
+
std::optional<bool> floatStatus = {};
- if (args.contains("tile") || args.contains("tiled"))
- floatStatus = false;
- else if (args.contains("float") || args.contains("floating"))
+ if (args.contains("tile") || args.contains("tiled")) {
+ // if we want just tiled, and we are on a tiled window, use layoutmsg for layouts that support it
+
+ if (!Desktop::focusState()->window()->m_isFloating) {
+ if (const auto SPACE = Desktop::focusState()->window()->layoutTarget()->space(); SPACE) {
+
+ constexpr const std::array<const std::type_info*, 2> LAYOUTS_WITH_CYCLE_NEXT = {
+ &typeid(Layout::Tiled::CMonocleAlgorithm),
+ &typeid(Layout::Tiled::CMasterAlgorithm),
+ };
+
+ if (std::ranges::contains(LAYOUTS_WITH_CYCLE_NEXT, &typeid(*SPACE->algorithm()->tiledAlgo().get()))) {
+ CKeybindManager::layoutmsg(PREV ? "cyclenext, b" : "cyclenext");
+ return {};
+ }
+ }
+ }
+ }
+
+ if (args.contains("float") || args.contains("floating"))
floatStatus = true;
const auto VISIBLE = args.contains("visible") || args.contains("v");
- const auto PREV = args.contains("prev") || args.contains("p") || args.contains("last") || args.contains("l");
const auto NEXT = args.contains("next") || args.contains("n"); // prev is default in classic alt+tab
const auto HIST = args.contains("hist") || args.contains("h");
const auto& w = HIST ? g_pCompositor->getWindowCycleHist(Desktop::focusState()->window(), true, floatStatus, VISIBLE, NEXT) :
@@ -2311,7 +2163,7 @@ SDispatchResult CKeybindManager::focusWindow(std::string regexp) {
changeworkspace(PWORKSPACE->getConfigName());
}
- Desktop::focusState()->fullWindowFocus(PWINDOW, nullptr, false);
+ Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND, nullptr, false);
PWINDOW->warpCursor();
@@ -2347,12 +2199,12 @@ SDispatchResult CKeybindManager::toggleSwallow(std::string args) {
// Unswallow
pWindow->m_swallowed->m_currentlySwallowed = false;
pWindow->m_swallowed->setHidden(false);
- g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow->m_swallowed.lock());
+ g_layoutManager->newTarget(pWindow->m_swallowed->layoutTarget(), pWindow->m_workspace->m_space);
} else {
// Reswallow
pWindow->m_swallowed->m_currentlySwallowed = true;
pWindow->m_swallowed->setHidden(true);
- g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow->m_swallowed.lock());
+ g_layoutManager->removeTarget(pWindow->m_swallowed->layoutTarget());
}
return {};
@@ -2613,9 +2465,9 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) {
}
SDispatchResult CKeybindManager::layoutmsg(std::string msg) {
- SLayoutMessageHeader hd = {Desktop::focusState()->window()};
- g_pLayoutManager->getCurrentLayout()->layoutMessage(hd, msg);
-
+ auto ret = g_layoutManager->layoutMsg(msg);
+ if (!ret)
+ return {.success = false, .error = ret.error()};
return {};
}
@@ -2669,11 +2521,11 @@ SDispatchResult CKeybindManager::swapnext(std::string arg) {
if (toSwap == PLASTWINDOW)
toSwap = g_pCompositor->getWindowCycle(PLASTWINDOW, true, std::nullopt, false, NEED_PREV);
- g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, toSwap);
+ g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), toSwap->layoutTarget(), false);
PLASTWINDOW->m_lastCycledWindow = toSwap;
- Desktop::focusState()->fullWindowFocus(PLASTWINDOW);
+ Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND);
return {};
}
@@ -2722,7 +2574,7 @@ SDispatchResult CKeybindManager::pinActive(std::string args) {
return {.success = false, .error = "pin: window not found"};
}
- PWINDOW->m_workspace = PMONITOR->m_activeWorkspace;
+ PWINDOW->moveToWorkspace(PMONITOR->m_activeWorkspace);
PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_PINNED);
@@ -2734,6 +2586,8 @@ SDispatchResult CKeybindManager::pinActive(std::string args) {
g_pEventManager->postEvent(SHyprIPCEvent{"pin", std::format("{:x},{}", rc<uintptr_t>(PWINDOW.get()), sc<int>(PWINDOW->m_pinned))});
EMIT_HOOK_EVENT("pin", PWINDOW);
+ g_pHyprRenderer->damageWindow(PWINDOW, true);
+
return {};
}
@@ -2760,7 +2614,7 @@ SDispatchResult CKeybindManager::mouse(std::string args) {
SDispatchResult CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE) {
if (MODE != MBIND_INVALID) {
- if (!g_pInputManager->m_currentlyDraggedWindow.expired() || g_pInputManager->m_dragMode != MBIND_INVALID)
+ if (g_layoutManager->dragController()->target())
return {};
const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();
@@ -2769,21 +2623,17 @@ SDispatchResult CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE)
if (!PWINDOW)
return SDispatchResult{.passEvent = true};
- if (!PWINDOW->isFullscreen() && MODE == MBIND_MOVE)
- PWINDOW->checkInputOnDecos(INPUT_TYPE_DRAG_START, MOUSECOORDS);
-
- if (g_pInputManager->m_currentlyDraggedWindow.expired())
- g_pInputManager->m_currentlyDraggedWindow = PWINDOW;
-
- g_pInputManager->m_dragMode = MODE;
+ if (!PWINDOW->isFullscreen() && MODE == MBIND_MOVE) {
+ if (PWINDOW->checkInputOnDecos(INPUT_TYPE_DRAG_START, MOUSECOORDS))
+ return SDispatchResult{.passEvent = false};
+ }
- g_pLayoutManager->getCurrentLayout()->onBeginDragWindow();
+ g_layoutManager->beginDragTarget(PWINDOW->layoutTarget(), MODE);
} else {
- if (g_pInputManager->m_currentlyDraggedWindow.expired() || g_pInputManager->m_dragMode == MBIND_INVALID)
+ if (!g_layoutManager->dragController()->target())
return {};
- g_pLayoutManager->getCurrentLayout()->onEndDragWindow();
- g_pInputManager->m_dragMode = MODE;
+ g_layoutManager->endDragTarget();
}
return {};
@@ -2845,17 +2695,15 @@ SDispatchResult CKeybindManager::lockActiveGroup(std::string args) {
if (!PWINDOW)
return {.success = false, .error = "No window found"};
- if (!PWINDOW->m_groupData.pNextWindow.lock())
+ if (!PWINDOW->m_group)
return {.success = false, .error = "Not a group"};
- const auto PHEAD = PWINDOW->getGroupHead();
-
if (args == "lock")
- PHEAD->m_groupData.locked = true;
+ PWINDOW->m_group->setLocked(true);
else if (args == "toggle")
- PHEAD->m_groupData.locked = !PHEAD->m_groupData.locked;
+ PWINDOW->m_group->setLocked(!PWINDOW->m_group->locked());
else
- PHEAD->m_groupData.locked = false;
+ PWINDOW->m_group->setLocked(false);
PWINDOW->updateDecorationValues();
@@ -2863,25 +2711,21 @@ SDispatchResult CKeybindManager::lockActiveGroup(std::string args) {
}
void CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection) {
- if (pWindow->m_groupData.deny)
+ if (!pWindowInDirection->m_group || pWindowInDirection->m_group->denied())
return;
updateRelativeCursorCoords();
- g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); // This removes grouped property!
-
if (pWindow->m_monitor != pWindowInDirection->m_monitor) {
pWindow->moveToWorkspace(pWindowInDirection->m_workspace);
pWindow->m_monitor = pWindowInDirection->m_monitor;
}
- static auto USECURRPOS = CConfigValue<Hyprlang::INT>("group:insert_after_current");
- (*USECURRPOS ? pWindowInDirection : pWindowInDirection->getGroupTail())->insertWindowToGroup(pWindow);
+ pWindowInDirection->m_group->add(pWindow);
- pWindowInDirection->setGroupCurrent(pWindow);
+ pWindowInDirection->m_group->setCurrent(pWindow);
pWindow->updateWindowDecos();
- g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow);
- Desktop::focusState()->fullWindowFocus(pWindow);
+ Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);
pWindow->warpCursor();
g_pEventManager->postEvent(SHyprIPCEvent{"moveintogroup", std::format("{:x}", rc<uintptr_t>(pWindow.get()))});
@@ -2889,70 +2733,51 @@ void CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowIn
void CKeybindManager::moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir) {
static auto BFOCUSREMOVEDWINDOW = CConfigValue<Hyprlang::INT>("group:focus_removed_window");
- const auto PWINDOWPREV = pWindow->getGroupPrevious();
- eDirection direction;
-
- switch (dir[0]) {
- case 't':
- case 'u': direction = DIRECTION_UP; break;
- case 'd':
- case 'b': direction = DIRECTION_DOWN; break;
- case 'l': direction = DIRECTION_LEFT; break;
- case 'r': direction = DIRECTION_RIGHT; break;
- default: direction = DIRECTION_DEFAULT;
- }
-
- updateRelativeCursorCoords();
- if (pWindow->m_groupData.pNextWindow.lock() == pWindow) {
- pWindow->destroyGroup();
- } else {
- g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow);
+ if (!pWindow->m_group)
+ return;
- const auto GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked;
- g_pKeybindManager->m_groupsLocked = true;
+ WP<Desktop::View::CGroup> group = pWindow->m_group;
- g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow, direction);
+ pWindow->m_group->remove(pWindow);
- g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV;
- }
-
- if (*BFOCUSREMOVEDWINDOW) {
- Desktop::focusState()->fullWindowFocus(pWindow);
+ if (*BFOCUSREMOVEDWINDOW || !group) {
+ Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);
pWindow->warpCursor();
} else {
- Desktop::focusState()->fullWindowFocus(PWINDOWPREV);
- PWINDOWPREV->warpCursor();
+ Desktop::focusState()->fullWindowFocus(group->current(), Desktop::FOCUS_REASON_KEYBIND);
+ group->current()->warpCursor();
}
g_pEventManager->postEvent(SHyprIPCEvent{"moveoutofgroup", std::format("{:x}", rc<uintptr_t>(pWindow.get()))});
}
SDispatchResult CKeybindManager::moveIntoGroup(std::string args) {
- char arg = args[0];
-
static auto PIGNOREGROUPLOCK = CConfigValue<Hyprlang::INT>("binds:ignore_group_lock");
if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_groupsLocked)
return {};
- if (!isDirection(args)) {
- Log::logger->log(Log::ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg);
- return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)};
+ Math::eDirection dir = Math::fromChar(args[0]);
+ if (dir == Math::DIRECTION_DEFAULT) {
+ Log::logger->log(Log::ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]);
+ return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])};
}
const auto PWINDOW = Desktop::focusState()->window();
- if (!PWINDOW || PWINDOW->m_groupData.deny)
+ if (!PWINDOW)
return {};
- auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg);
+ auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir);
- if (!PWINDOWINDIR || !PWINDOWINDIR->m_groupData.pNextWindow.lock())
+ if (!PWINDOWINDIR || !PWINDOWINDIR->m_group)
return {};
+ const auto GROUP = PWINDOWINDIR->m_group;
+
// Do not move window into locked group if binds:ignore_group_lock is false
- if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->getGroupHead()->m_groupData.locked || (PWINDOW->m_groupData.pNextWindow.lock() && PWINDOW->getGroupHead()->m_groupData.locked)))
+ if (!*PIGNOREGROUPLOCK && (GROUP->locked() || (PWINDOW->m_group && PWINDOW->m_group->locked())))
return {};
moveWindowIntoGroup(PWINDOW, PWINDOWINDIR);
@@ -2976,7 +2801,7 @@ SDispatchResult CKeybindManager::moveOutOfGroup(std::string args) {
if (!PWINDOW)
return {.success = false, .error = "No window found"};
- if (!PWINDOW->m_groupData.pNextWindow.lock())
+ if (!PWINDOW->m_group)
return {.success = false, .error = "Window not in a group"};
moveWindowOutOfGroup(PWINDOW);
@@ -2985,13 +2810,12 @@ SDispatchResult CKeybindManager::moveOutOfGroup(std::string args) {
}
SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) {
- char arg = args[0];
-
- static auto PIGNOREGROUPLOCK = CConfigValue<Hyprlang::INT>("binds:ignore_group_lock");
+ static auto PIGNOREGROUPLOCK = CConfigValue<Hyprlang::INT>("binds:ignore_group_lock");
- if (!isDirection(args)) {
- Log::logger->log(Log::ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg);
- return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)};
+ Math::eDirection dir = Math::fromChar(args[0]);
+ if (dir == Math::DIRECTION_DEFAULT) {
+ Log::logger->log(Log::ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]);
+ return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])};
}
const auto PWINDOW = Desktop::focusState()->window();
@@ -3002,35 +2826,35 @@ SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) {
return {};
if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_groupsLocked) {
- g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args);
+ g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args);
return {};
}
- const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg);
+ const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir);
- const bool ISWINDOWGROUP = PWINDOW->m_groupData.pNextWindow;
- const bool ISWINDOWGROUPLOCKED = ISWINDOWGROUP && PWINDOW->getGroupHead()->m_groupData.locked;
- const bool ISWINDOWGROUPSINGLE = ISWINDOWGROUP && PWINDOW->m_groupData.pNextWindow.lock() == PWINDOW;
+ const bool ISWINDOWGROUP = PWINDOW->m_group;
+ const bool ISWINDOWGROUPLOCKED = ISWINDOWGROUP && PWINDOW->m_group->locked();
+ const bool ISWINDOWGROUPSINGLE = ISWINDOWGROUP && PWINDOW->m_group->size() == 1;
updateRelativeCursorCoords();
// note: PWINDOWINDIR is not null implies !PWINDOW->m_isFloating
- if (PWINDOWINDIR && PWINDOWINDIR->m_groupData.pNextWindow) { // target is group
- if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->getGroupHead()->m_groupData.locked || ISWINDOWGROUPLOCKED || PWINDOW->m_groupData.deny)) {
- g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args);
+ if (PWINDOWINDIR && PWINDOWINDIR->m_group) { // target is group
+ if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->m_group->locked() || ISWINDOWGROUPLOCKED || PWINDOW->m_group->denied())) {
+ g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args);
PWINDOW->warpCursor();
} else
moveWindowIntoGroup(PWINDOW, PWINDOWINDIR);
} else if (PWINDOWINDIR) { // target is regular window
if ((!*PIGNOREGROUPLOCK && ISWINDOWGROUPLOCKED) || !ISWINDOWGROUP || (ISWINDOWGROUPSINGLE && PWINDOW->m_groupRules & Desktop::View::GROUP_SET_ALWAYS)) {
- g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args);
+ g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args);
PWINDOW->warpCursor();
} else
moveWindowOutOfGroup(PWINDOW, args);
} else if ((*PIGNOREGROUPLOCK || !ISWINDOWGROUPLOCKED) && ISWINDOWGROUP) { // no target window
moveWindowOutOfGroup(PWINDOW, args);
} else if (!PWINDOWINDIR && !ISWINDOWGROUP) { // no target in dir and not in group
- g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args);
+ g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args);
PWINDOW->warpCursor();
}
@@ -3054,13 +2878,13 @@ SDispatchResult CKeybindManager::setIgnoreGroupLock(std::string args) {
SDispatchResult CKeybindManager::denyWindowFromGroup(std::string args) {
const auto PWINDOW = Desktop::focusState()->window();
- if (!PWINDOW || (PWINDOW && PWINDOW->m_groupData.pNextWindow.lock()))
+ if (!PWINDOW || (PWINDOW && PWINDOW->m_group))
return {};
if (args == "toggle")
- PWINDOW->m_groupData.deny = !PWINDOW->m_groupData.deny;
+ PWINDOW->m_group->setDenied(!PWINDOW->m_group->denied());
else
- PWINDOW->m_groupData.deny = args == "on";
+ PWINDOW->m_group->setDenied(args == "on");
PWINDOW->updateDecorationValues();
@@ -3090,16 +2914,15 @@ SDispatchResult CKeybindManager::moveGroupWindow(std::string args) {
if (!PLASTWINDOW)
return {.success = false, .error = "No window found"};
- if (!PLASTWINDOW->m_groupData.pNextWindow.lock())
+ if (!PLASTWINDOW->m_group)
return {.success = false, .error = "Window not in a group"};
- if ((!BACK && PLASTWINDOW->m_groupData.pNextWindow->m_groupData.head) || (BACK && PLASTWINDOW->m_groupData.head)) {
- std::swap(PLASTWINDOW->m_groupData.head, PLASTWINDOW->m_groupData.pNextWindow->m_groupData.head);
- std::swap(PLASTWINDOW->m_groupData.locked, PLASTWINDOW->m_groupData.pNextWindow->m_groupData.locked);
- } else
- PLASTWINDOW->switchWithWindowInGroup(BACK ? PLASTWINDOW->getGroupPrevious() : PLASTWINDOW->m_groupData.pNextWindow.lock());
+ const auto GROUP = PLASTWINDOW->m_group;
- PLASTWINDOW->updateWindowDecos();
+ if (BACK)
+ GROUP->swapWithLast();
+ else
+ GROUP->swapWithNext();
return {};
}
@@ -3298,16 +3121,18 @@ SDispatchResult CKeybindManager::setProp(std::string args) {
if (PWINDOW->m_ruleApplicator->noFocus().valueOrDefault() != noFocus) {
// FIXME: what the fuck is going on here? -vax
- Desktop::focusState()->rawWindowFocus(nullptr);
- Desktop::focusState()->fullWindowFocus(PWINDOW);
- Desktop::focusState()->fullWindowFocus(PLASTWINDOW);
+ Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND);
+ Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND);
+ Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND);
}
if (PROP == "no_vrr")
g_pConfigManager->ensureVRR(PWINDOW->m_monitor.lock());
- for (auto const& m : g_pCompositor->m_monitors)
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
+ for (auto const& m : g_pCompositor->m_monitors) {
+ if (m->m_activeWorkspace)
+ m->m_activeWorkspace->m_space->recalculate();
+ }
return {};
}
diff --git a/src/managers/LayoutManager.cpp b/src/managers/LayoutManager.cpp
deleted file mode 100644
index 050f1d50..00000000
--- a/src/managers/LayoutManager.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-#include "LayoutManager.hpp"
-
-CLayoutManager::CLayoutManager() {
- m_layouts.emplace_back(std::make_pair<>("dwindle", &m_dwindleLayout));
- m_layouts.emplace_back(std::make_pair<>("master", &m_masterLayout));
-}
-
-IHyprLayout* CLayoutManager::getCurrentLayout() {
- return m_layouts[m_currentLayoutID].second;
-}
-
-void CLayoutManager::switchToLayout(std::string layout) {
- for (size_t i = 0; i < m_layouts.size(); ++i) {
- if (m_layouts[i].first == layout) {
- if (i == sc<size_t>(m_currentLayoutID))
- return;
-
- getCurrentLayout()->onDisable();
- m_currentLayoutID = i;
- getCurrentLayout()->onEnable();
- return;
- }
- }
-
- Log::logger->log(Log::ERR, "Unknown layout!");
-}
-
-bool CLayoutManager::addLayout(const std::string& name, IHyprLayout* layout) {
- if (std::ranges::find_if(m_layouts, [&](const auto& other) { return other.first == name || other.second == layout; }) != m_layouts.end())
- return false;
-
- m_layouts.emplace_back(std::make_pair<>(name, layout));
-
- Log::logger->log(Log::DEBUG, "Added new layout {} at {:x}", name, rc<uintptr_t>(layout));
-
- return true;
-}
-
-bool CLayoutManager::removeLayout(IHyprLayout* layout) {
- const auto IT = std::ranges::find_if(m_layouts, [&](const auto& other) { return other.second == layout; });
-
- if (IT == m_layouts.end() || IT->first == "dwindle" || IT->first == "master")
- return false;
-
- if (m_currentLayoutID == IT - m_layouts.begin())
- switchToLayout("dwindle");
-
- Log::logger->log(Log::DEBUG, "Removed a layout {} at {:x}", IT->first, rc<uintptr_t>(layout));
-
- std::erase(m_layouts, *IT);
-
- return true;
-}
-
-std::vector<std::string> CLayoutManager::getAllLayoutNames() {
- std::vector<std::string> results(m_layouts.size());
- for (size_t i = 0; i < m_layouts.size(); ++i)
- results[i] = m_layouts[i].first;
- return results;
-}
diff --git a/src/managers/LayoutManager.hpp b/src/managers/LayoutManager.hpp
deleted file mode 100644
index 80c522fb..00000000
--- a/src/managers/LayoutManager.hpp
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-
-#include "../layout/DwindleLayout.hpp"
-#include "../layout/MasterLayout.hpp"
-
-class CLayoutManager {
- public:
- CLayoutManager();
-
- IHyprLayout* getCurrentLayout();
-
- void switchToLayout(std::string);
-
- bool addLayout(const std::string& name, IHyprLayout* layout);
- bool removeLayout(IHyprLayout* layout);
- std::vector<std::string> getAllLayoutNames();
-
- private:
- enum eHyprLayouts : uint8_t {
- LAYOUT_DWINDLE = 0,
- LAYOUT_MASTER
- };
-
- int m_currentLayoutID = LAYOUT_DWINDLE;
-
- CHyprDwindleLayout m_dwindleLayout;
- CHyprMasterLayout m_masterLayout;
- std::vector<std::pair<std::string, IHyprLayout*>> m_layouts;
-};
-
-inline UP<CLayoutManager> g_pLayoutManager;
diff --git a/src/managers/SeatManager.cpp b/src/managers/SeatManager.cpp
index 5b428e37..1803a884 100644
--- a/src/managers/SeatManager.cpp
+++ b/src/managers/SeatManager.cpp
@@ -668,7 +668,7 @@ void CSeatManager::setGrab(SP<CSeatGrab> grab) {
// If this was a popup grab, focus its parent window to maintain context
if (validMapped(parentWindow)) {
- Desktop::focusState()->rawWindowFocus(parentWindow);
+ Desktop::focusState()->rawWindowFocus(parentWindow, Desktop::FOCUS_REASON_FFM);
Log::logger->log(Log::DEBUG, "[seatmgr] Refocused popup parent window {} (follow_mouse={})", parentWindow->m_title, *PFOLLOWMOUSE);
} else
g_pInputManager->refocusLastWindow(PMONITOR);
@@ -702,7 +702,7 @@ void CSeatManager::setGrab(SP<CSeatGrab> grab) {
auto candidate = Desktop::focusState()->window();
if (candidate)
- Desktop::focusState()->rawWindowFocus(candidate);
+ Desktop::focusState()->rawWindowFocus(candidate, Desktop::FOCUS_REASON_FFM);
}
if (oldGrab->m_onEnd)
diff --git a/src/managers/animation/DesktopAnimationManager.cpp b/src/managers/animation/DesktopAnimationManager.cpp
index 9a1fc081..2c450add 100644
--- a/src/managers/animation/DesktopAnimationManager.cpp
+++ b/src/managers/animation/DesktopAnimationManager.cpp
@@ -4,6 +4,7 @@
#include "../../desktop/view/LayerSurface.hpp"
#include "../../desktop/view/Window.hpp"
+#include "../../desktop/view/Group.hpp"
#include "../../desktop/Workspace.hpp"
#include "../../config/ConfigManager.hpp"
@@ -469,7 +470,7 @@ void CDesktopAnimationManager::setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnim
*w->m_alpha = 1.F;
else if (!w->isFullscreen()) {
const bool CREATED_OVER_FS = w->m_createdOverFullscreen;
- const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->hasInGroup(w);
+ const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->m_group && FSWINDOW->m_group->has(w);
*w->m_alpha = !CREATED_OVER_FS && !IS_IN_GROUP_OF_FS ? 0.f : 1.f;
}
}
diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp
index 860a4b78..e0b1a452 100644
--- a/src/managers/input/InputManager.cpp
+++ b/src/managers/input/InputManager.cpp
@@ -37,12 +37,13 @@
#include "../../render/Renderer.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../managers/EventManager.hpp"
-#include "../../managers/LayoutManager.hpp"
#include "../../managers/permissions/DynamicPermissionManager.hpp"
#include "../../helpers/time/Time.hpp"
#include "../../helpers/MiscFunctions.hpp"
+#include "../../layout/LayoutManager.hpp"
+
#include "trackpad/TrackpadGestures.hpp"
#include "../cursor/CursorShapeOverrideController.hpp"
@@ -230,6 +231,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
Vector2D surfacePos = Vector2D(-1337, -1337);
PHLWINDOW pFoundWindow;
PHLLS pFoundLayerSurface;
+ const auto FOCUS_REASON = refocus ? Desktop::FOCUS_REASON_CLICK : Desktop::FOCUS_REASON_FFM;
EMIT_HOOK_EVENT_CANCELLABLE("mouseMove", MOUSECOORDSFLOORED);
@@ -365,7 +367,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
}
}
- g_pLayoutManager->getCurrentLayout()->onMouseMove(getMouseCoordsInternal());
+ g_layoutManager->moveMouse(getMouseCoordsInternal());
// forced above all
if (!g_pInputManager->m_exclusiveLSes.empty()) {
@@ -522,7 +524,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
g_pSeatManager->setPointerFocus(nullptr, {});
if (refocus || !Desktop::focusState()->window()) // if we are forcing a refocus, and we don't find a surface, clear the kb focus too!
- Desktop::focusState()->rawWindowFocus(nullptr);
+ Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON);
return;
}
@@ -550,7 +552,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
m_foundSurfaceToFocus = foundSurface;
}
- if (m_currentlyDraggedWindow.lock() && pFoundWindow != m_currentlyDraggedWindow) {
+ if (g_layoutManager->dragController()->target() && pFoundWindow != g_layoutManager->dragController()->target()) {
g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal);
return;
}
@@ -582,7 +584,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
((pFoundWindow->m_isFloating && *PFLOATBEHAVIOR == 2) || (Desktop::focusState()->window()->m_isFloating != pFoundWindow->m_isFloating && *PFLOATBEHAVIOR != 0))) {
// enter if change floating style
if (FOLLOWMOUSE != 3 && allowKeyboardRefocus)
- Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface);
+ Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface);
g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal);
} else if (FOLLOWMOUSE == 2 || FOLLOWMOUSE == 3)
g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal);
@@ -610,7 +612,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
const bool hasNoFollowMouse = pFoundWindow && pFoundWindow->m_ruleApplicator->noFollowMouse().valueOrDefault();
if (refocus || !hasNoFollowMouse)
- Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface);
+ Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface);
}
} else
Desktop::focusState()->rawSurfaceFocus(foundSurface, pFoundWindow);
@@ -619,7 +621,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
}
if (g_pSeatManager->m_state.keyboardFocus == nullptr)
- Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface);
+ Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface);
m_lastFocusOnLS = false;
} else {
@@ -1629,13 +1631,13 @@ bool CInputManager::refocusLastWindow(PHLMONITOR pMonitor) {
if (!foundSurface && Desktop::focusState()->window() && Desktop::focusState()->window()->m_workspace && Desktop::focusState()->window()->m_workspace->isVisibleNotCovered()) {
// then the last focused window if we're on the same workspace as it
const auto PLASTWINDOW = Desktop::focusState()->window();
- Desktop::focusState()->fullWindowFocus(PLASTWINDOW);
+ Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_FFM);
} else {
// otherwise fall back to a normal refocus.
if (foundSurface && !foundSurface->m_hlSurface->keyboardFocusable()) {
const auto PLASTWINDOW = Desktop::focusState()->window();
- Desktop::focusState()->fullWindowFocus(PLASTWINDOW);
+ Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_FFM);
}
refocus();
@@ -1951,7 +1953,7 @@ void CInputManager::setCursorIconOnBorder(PHLWINDOW w) {
if (w->hasPopupAt(mouseCoords))
direction = BORDERICON_NONE;
- else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && m_currentlyDraggedWindow.expired()))
+ else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && !g_layoutManager->dragController()->target()))
direction = BORDERICON_NONE;
else {
diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp
index c1e0bbfb..ca235a23 100644
--- a/src/managers/input/InputManager.hpp
+++ b/src/managers/input/InputManager.hpp
@@ -154,12 +154,6 @@ class CInputManager {
STouchData m_touchData;
- // for dragging floating windows
- PHLWINDOWREF m_currentlyDraggedWindow;
- eMouseBindMode m_dragMode = MBIND_INVALID;
- bool m_wasDraggingWindow = false;
- bool m_dragThresholdReached = false;
-
// for refocus to be forced
PHLWINDOWREF m_forcedFocus;
diff --git a/src/managers/input/trackpad/gestures/CloseGesture.cpp b/src/managers/input/trackpad/gestures/CloseGesture.cpp
index 7beba563..0c37ee36 100644
--- a/src/managers/input/trackpad/gestures/CloseGesture.cpp
+++ b/src/managers/input/trackpad/gestures/CloseGesture.cpp
@@ -1,13 +1,13 @@
#include "CloseGesture.hpp"
#include "../../../../Compositor.hpp"
-#include "../../../../managers/LayoutManager.hpp"
#include "../../../../managers/animation/DesktopAnimationManager.hpp"
#include "../../../../render/Renderer.hpp"
#include "../../../../managers/eventLoop/EventLoopManager.hpp"
#include "../../../../managers/eventLoop/EventLoopTimer.hpp"
#include "../../../../config/ConfigValue.hpp"
#include "../../../../desktop/state/FocusState.hpp"
+#include "../../../../layout/target/Target.hpp"
constexpr const float MAX_DISTANCE = 200.F;
@@ -133,7 +133,7 @@ void CCloseTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e)
if (!window->m_isMapped)
return;
- g_pLayoutManager->getCurrentLayout()->recalculateWindow(window.lock());
+ window->layoutTarget()->recalc();
window->updateDecorationValues();
window->sendWindowSize(true);
*window->m_alpha = 1.F;
diff --git a/src/managers/input/trackpad/gestures/FloatGesture.cpp b/src/managers/input/trackpad/gestures/FloatGesture.cpp
index b2f451f1..0849bfac 100644
--- a/src/managers/input/trackpad/gestures/FloatGesture.cpp
+++ b/src/managers/input/trackpad/gestures/FloatGesture.cpp
@@ -1,9 +1,10 @@
#include "FloatGesture.hpp"
-#include "../../../../managers/LayoutManager.hpp"
#include "../../../../render/Renderer.hpp"
#include "../../../../desktop/state/FocusState.hpp"
#include "../../../../desktop/view/Window.hpp"
+#include "../../../../layout/LayoutManager.hpp"
+#include "../../../../layout/target/WindowTarget.hpp"
constexpr const float MAX_DISTANCE = 250.F;
@@ -40,8 +41,7 @@ void CFloatTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin&
return;
}
- m_window->m_isFloating = !m_window->m_isFloating;
- g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(m_window.lock());
+ g_layoutManager->changeFloatingMode(m_window->layoutTarget());
m_posFrom = m_window->m_realPosition->begun();
m_sizeFrom = m_window->m_realSize->begun();
@@ -79,8 +79,7 @@ void CFloatTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e)
if (COMPLETION < 0.2F) {
// revert the animation
g_pHyprRenderer->damageWindow(m_window.lock());
- m_window->m_isFloating = !m_window->m_isFloating;
- g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(m_window.lock());
+ g_layoutManager->changeFloatingMode(m_window->layoutTarget());
return;
}
diff --git a/src/managers/input/trackpad/gestures/MoveGesture.cpp b/src/managers/input/trackpad/gestures/MoveGesture.cpp
index 034d88fb..0dcc310f 100644
--- a/src/managers/input/trackpad/gestures/MoveGesture.cpp
+++ b/src/managers/input/trackpad/gestures/MoveGesture.cpp
@@ -2,8 +2,8 @@
#include "../../../../desktop/state/FocusState.hpp"
#include "../../../../desktop/view/Window.hpp"
-#include "../../../../managers/LayoutManager.hpp"
#include "../../../../render/Renderer.hpp"
+#include "../../../../layout/LayoutManager.hpp"
void CMoveTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {
ITrackpadGesture::begin(e);
@@ -19,7 +19,7 @@ void CMoveTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate
const auto DELTA = e.swipe ? e.swipe->delta : e.pinch->delta;
if (m_window->m_isFloating) {
- g_pLayoutManager->getCurrentLayout()->moveActiveWindow(DELTA, m_window.lock());
+ g_layoutManager->moveTarget(DELTA, m_window->layoutTarget());
m_window->m_realSize->warp();
m_window->m_realPosition->warp();
return;
@@ -52,10 +52,10 @@ void CMoveTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {
if (std::abs(m_lastDelta.x) > std::abs(m_lastDelta.y)) {
// horizontal
- g_pLayoutManager->getCurrentLayout()->moveWindowTo(m_window.lock(), m_lastDelta.x > 0 ? "r" : "l");
+ g_layoutManager->moveInDirection(m_window->layoutTarget(), m_lastDelta.x > 0 ? "r" : "l");
} else {
// vertical
- g_pLayoutManager->getCurrentLayout()->moveWindowTo(m_window.lock(), m_lastDelta.y > 0 ? "b" : "t");
+ g_layoutManager->moveInDirection(m_window->layoutTarget(), m_lastDelta.y > 0 ? "b" : "t");
}
const auto GOAL = m_window->m_realPosition->goal();
diff --git a/src/managers/input/trackpad/gestures/ResizeGesture.cpp b/src/managers/input/trackpad/gestures/ResizeGesture.cpp
index 33f018cd..ffc7704b 100644
--- a/src/managers/input/trackpad/gestures/ResizeGesture.cpp
+++ b/src/managers/input/trackpad/gestures/ResizeGesture.cpp
@@ -2,8 +2,8 @@
#include "../../../../desktop/state/FocusState.hpp"
#include "../../../../desktop/view/Window.hpp"
-#include "../../../../managers/LayoutManager.hpp"
#include "../../../../render/Renderer.hpp"
+#include "../../../../layout/LayoutManager.hpp"
void CResizeTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {
ITrackpadGesture::begin(e);
@@ -17,8 +17,8 @@ void CResizeTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpda
g_pHyprRenderer->damageWindow(m_window.lock());
- g_pLayoutManager->getCurrentLayout()->resizeActiveWindow((e.swipe ? e.swipe->delta : e.pinch->delta),
- cornerFromBox(m_window->getWindowMainSurfaceBox(), g_pInputManager->getMouseCoordsInternal()), m_window.lock());
+ g_layoutManager->resizeTarget((e.swipe ? e.swipe->delta : e.pinch->delta), m_window->layoutTarget(),
+ Layout::cornerFromBox(m_window->getWindowMainSurfaceBox(), g_pInputManager->getMouseCoordsInternal()));
m_window->m_realSize->warp();
m_window->m_realPosition->warp();
diff --git a/src/plugins/PluginAPI.cpp b/src/plugins/PluginAPI.cpp
index 2abddc90..57081183 100644
--- a/src/plugins/PluginAPI.cpp
+++ b/src/plugins/PluginAPI.cpp
@@ -3,10 +3,11 @@
#include "../debug/HyprCtl.hpp"
#include "../plugins/PluginSystem.hpp"
#include "../managers/HookSystemManager.hpp"
-#include "../managers/LayoutManager.hpp"
#include "../managers/eventLoop/EventLoopManager.hpp"
#include "../config/ConfigManager.hpp"
#include "../debug/HyprNotificationOverlay.hpp"
+#include "../layout/target/Target.hpp"
+#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include <dlfcn.h>
#include <filesystem>
@@ -62,25 +63,44 @@ APICALL std::string HyprlandAPI::invokeHyprctlCommand(const std::string& call, c
}
APICALL bool HyprlandAPI::addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout) {
+ return false;
+}
+
+APICALL bool HyprlandAPI::removeLayout(HANDLE handle, IHyprLayout* layout) {
+ return false;
+}
+
+APICALL bool HyprlandAPI::addTiledAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function<UP<Layout::ITiledAlgorithm>()>&& factory) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
- PLUGIN->m_registeredLayouts.push_back(layout);
+ PLUGIN->m_registeredAlgos.emplace_back(name);
- return g_pLayoutManager->addLayout(name, layout);
+ return Layout::Supplementary::algoMatcher()->registerTiledAlgo(name, typeInfo, std::move(factory));
}
-APICALL bool HyprlandAPI::removeLayout(HANDLE handle, IHyprLayout* layout) {
+APICALL bool HyprlandAPI::addFloatingAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function<UP<Layout::IFloatingAlgorithm>()>&& factory) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ PLUGIN->m_registeredAlgos.emplace_back(name);
+
+ return Layout::Supplementary::algoMatcher()->registerFloatingAlgo(name, typeInfo, std::move(factory));
+}
+
+APICALL bool HyprlandAPI::removeAlgo(HANDLE handle, const std::string& name) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
- std::erase(PLUGIN->m_registeredLayouts, layout);
+ std::erase(PLUGIN->m_registeredAlgos, name);
- return g_pLayoutManager->removeLayout(layout);
+ return Layout::Supplementary::algoMatcher()->unregisterAlgo(name);
}
APICALL bool HyprlandAPI::reloadConfig() {
@@ -130,7 +150,7 @@ APICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, PHLWINDOW pWindow,
pWindow->addWindowDeco(std::move(pDecoration));
- g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow);
+ pWindow->layoutTarget()->recalc();
return true;
}
diff --git a/src/plugins/PluginAPI.hpp b/src/plugins/PluginAPI.hpp
index 47a695db..568b2e0f 100644
--- a/src/plugins/PluginAPI.hpp
+++ b/src/plugins/PluginAPI.hpp
@@ -71,6 +71,11 @@ class IHyprLayout;
class IHyprWindowDecoration;
struct SConfigValue;
+namespace Layout {
+ class ITiledAlgorithm;
+ class IFloatingAlgorithm;
+};
+
/*
These methods are for the plugin to implement
Methods marked with REQUIRED are required.
@@ -172,15 +177,26 @@ namespace HyprlandAPI {
Adds a layout to Hyprland.
returns: true on success. False otherwise.
+
+ deprecated: addTiledAlgo, addFloatingAlgo
*/
- APICALL bool addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout);
+ APICALL [[deprecated]] bool addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout);
/*
Removes an added layout from Hyprland.
returns: true on success. False otherwise.
+
+ deprecated: V2 removeAlgo
+ */
+ APICALL [[deprecated]] bool removeLayout(HANDLE handle, IHyprLayout* layout);
+
+ /*
+ Algorithm fns. Used for registering and removing. Return success.
*/
- APICALL bool removeLayout(HANDLE handle, IHyprLayout* layout);
+ APICALL bool addTiledAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function<UP<Layout::ITiledAlgorithm>()>&& factory);
+ APICALL bool addFloatingAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function<UP<Layout::IFloatingAlgorithm>()>&& factory);
+ APICALL bool removeAlgo(HANDLE handle, const std::string& name);
/*
Queues a config reload. Does not take effect immediately.
diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp
index 0549c81b..7a798ac4 100644
--- a/src/plugins/PluginSystem.cpp
+++ b/src/plugins/PluginSystem.cpp
@@ -4,11 +4,11 @@
#include <ranges>
#include "../config/ConfigManager.hpp"
#include "../debug/HyprCtl.hpp"
-#include "../managers/LayoutManager.hpp"
#include "../managers/HookSystemManager.hpp"
#include "../managers/eventLoop/EventLoopManager.hpp"
#include "../managers/permissions/DynamicPermissionManager.hpp"
#include "../debug/HyprNotificationOverlay.hpp"
+#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include "../i18n/Engine.hpp"
CPluginSystem::CPluginSystem() {
@@ -156,9 +156,9 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) {
g_pHookSystem->unhook(SHP);
}
- const auto ls = plugin->m_registeredLayouts;
- for (auto const& l : ls)
- g_pLayoutManager->removeLayout(l);
+ for (const auto& l : plugin->m_registeredAlgos) {
+ Layout::Supplementary::algoMatcher()->unregisterAlgo(l);
+ }
g_pFunctionHookSystem->removeAllHooksFrom(plugin->m_handle);
diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp
index 286f10d5..ca980d12 100644
--- a/src/plugins/PluginSystem.hpp
+++ b/src/plugins/PluginSystem.hpp
@@ -23,11 +23,11 @@ class CPlugin {
HANDLE m_handle = nullptr;
- std::vector<IHyprLayout*> m_registeredLayouts;
std::vector<IHyprWindowDecoration*> m_registeredDecorations;
std::vector<std::pair<std::string, WP<HOOK_CALLBACK_FN>>> m_registeredCallbacks;
std::vector<std::string> m_registeredDispatchers;
std::vector<WP<SHyprCtlCommand>> m_registeredHyprctlCommands;
+ std::vector<std::string> m_registeredAlgos;
};
class CPluginSystem {
diff --git a/src/protocols/ForeignToplevelWlr.cpp b/src/protocols/ForeignToplevelWlr.cpp
index 89db1ad8..346c388b 100644
--- a/src/protocols/ForeignToplevelWlr.cpp
+++ b/src/protocols/ForeignToplevelWlr.cpp
@@ -377,7 +377,7 @@ CForeignToplevelWlrProtocol::CForeignToplevelWlrProtocol(const wl_interface* ifa
});
static auto P3 = g_pHookSystem->hookDynamic("activeWindow", [this](void* self, SCallbackInfo& info, std::any data) {
- const auto PWINDOW = std::any_cast<PHLWINDOW>(data);
+ const auto PWINDOW = std::any_cast<Desktop::View::SWindowActiveEvent>(data).window;
if (PWINDOW && !windowValidForForeign(PWINDOW))
return;
diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp
index 1b95ce8a..b45a2cdc 100644
--- a/src/render/Renderer.cpp
+++ b/src/render/Renderer.cpp
@@ -11,7 +11,6 @@
#include "../managers/input/InputManager.hpp"
#include "../managers/HookSystemManager.hpp"
#include "../managers/animation/AnimationManager.hpp"
-#include "../managers/LayoutManager.hpp"
#include "../desktop/view/Window.hpp"
#include "../desktop/view/LayerSurface.hpp"
#include "../desktop/view/GlobalViewMethods.hpp"
@@ -28,6 +27,8 @@
#include "../hyprerror/HyprError.hpp"
#include "../debug/HyprDebugOverlay.hpp"
#include "../debug/HyprNotificationOverlay.hpp"
+#include "../layout/LayoutManager.hpp"
+#include "../layout/space/Space.hpp"
#include "../i18n/Engine.hpp"
#include "helpers/CursorShapes.hpp"
#include "helpers/Monitor.hpp"
@@ -1305,7 +1306,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {
if (pMonitor->m_scheduledRecalc) {
pMonitor->m_scheduledRecalc = false;
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id);
+ pMonitor->m_activeWorkspace->m_space->recalculate();
}
if (!pMonitor->m_output->needsFrame && pMonitor->m_forceFullFrames == 0)
@@ -1906,7 +1907,7 @@ void CHyprRenderer::arrangeLayersForMonitor(const MONITORID& monitor) {
// damage the monitor if can
damageMonitor(PMONITOR);
- g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitor);
+ g_layoutManager->invalidateMonitorGeometries(PMONITOR);
}
void CHyprRenderer::damageSurface(SP<CWLSurfaceResource> pSurface, double x, double y, double scale) {
diff --git a/src/render/decorations/CHyprGroupBarDecoration.cpp b/src/render/decorations/CHyprGroupBarDecoration.cpp
index 47e39211..beb5efcd 100644
--- a/src/render/decorations/CHyprGroupBarDecoration.cpp
+++ b/src/render/decorations/CHyprGroupBarDecoration.cpp
@@ -2,13 +2,15 @@
#include "../../Compositor.hpp"
#include "../../config/ConfigValue.hpp"
#include "../../desktop/state/FocusState.hpp"
-#include "managers/LayoutManager.hpp"
+#include "../../desktop/view/Group.hpp"
#include <ranges>
#include <pango/pangocairo.h>
#include "../pass/TexPassElement.hpp"
#include "../pass/RectPassElement.hpp"
#include "../Renderer.hpp"
#include "../../managers/input/InputManager.hpp"
+#include "../../layout/LayoutManager.hpp"
+#include "../../layout/supplementary/DragController.hpp"
// shared things to conserve VRAM
static SP<CTexture> m_tGradientActive = makeShared<CTexture>();
@@ -65,19 +67,14 @@ eDecorationType CHyprGroupBarDecoration::getDecorationType() {
//
void CHyprGroupBarDecoration::updateWindow(PHLWINDOW pWindow) {
- if (m_window->m_groupData.pNextWindow.expired()) {
+ if (!m_window->m_group) {
m_window->removeWindowDeco(this);
return;
}
m_dwGroupMembers.clear();
- PHLWINDOW head = pWindow->getGroupHead();
- m_dwGroupMembers.emplace_back(head);
-
- PHLWINDOW curr = head->m_groupData.pNextWindow.lock();
- while (curr != head) {
- m_dwGroupMembers.emplace_back(curr);
- curr = curr->m_groupData.pNextWindow.lock();
+ for (const auto& w : m_window->m_group->windows()) {
+ m_dwGroupMembers.emplace_back(w);
}
damageEntire();
@@ -158,7 +155,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) {
rect.scale(pMonitor->m_scale).round();
- const bool GROUPLOCKED = m_window->getGroupHead()->m_groupData.locked || g_pKeybindManager->m_groupsLocked;
+ const bool GROUPLOCKED = m_window->m_group->locked() || g_pKeybindManager->m_groupsLocked;
const auto* const PCOLACTIVE = GROUPLOCKED ? GROUPCOLACTIVELOCKED : GROUPCOLACTIVE;
const auto* const PCOLINACTIVE = GROUPLOCKED ? GROUPCOLINACTIVELOCKED : GROUPCOLINACTIVE;
@@ -204,6 +201,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) {
data.tex = GRADIENTTEX;
data.blur = blur;
data.box = rect;
+ data.a = a;
if (*PGRADIENTROUNDING) {
data.round = *PGRADIENTROUNDING;
data.roundingPower = *PGRADIENTROUNDINGPOWER;
@@ -391,7 +389,7 @@ bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) {
static auto PSTACKED = CConfigValue<Hyprlang::INT>("group:groupbar:stacked");
static auto POUTERGAP = CConfigValue<Hyprlang::INT>("group:groupbar:gaps_out");
static auto PINNERGAP = CConfigValue<Hyprlang::INT>("group:groupbar:gaps_in");
- if (m_window.lock() == m_window->m_groupData.pNextWindow.lock())
+ if (m_window->m_group->size() == 1)
return false;
const float BARRELATIVEX = pos.x - assignedBoxGlobal().x;
@@ -404,95 +402,33 @@ bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) {
if (*PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP))
return false;
- PHLWINDOW pWindow = m_window->getGroupWindowByIndex(WINDOWINDEX);
+ PHLWINDOW pWindow = m_window->m_group->fromIndex(WINDOWINDEX);
- // hack
- g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow);
- if (!pWindow->m_isFloating) {
- const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked;
- g_pKeybindManager->m_groupsLocked = true;
- g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow);
- g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV;
- }
+ const auto& GROUP = m_window->m_group;
- g_pInputManager->m_currentlyDraggedWindow = pWindow;
+ // remove the window from the group
+ GROUP->remove(pWindow);
+
+ // start a move drag on it
+ g_layoutManager->dragController()->dragBegin(pWindow->layoutTarget(), MBIND_MOVE);
if (!g_pCompositor->isWindowActive(pWindow))
- Desktop::focusState()->rawWindowFocus(pWindow);
+ Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK);
return true;
}
bool CHyprGroupBarDecoration::onEndWindowDragOnDeco(const Vector2D& pos, PHLWINDOW pDraggedWindow) {
- static auto PSTACKED = CConfigValue<Hyprlang::INT>("group:groupbar:stacked");
static auto PDRAGINTOGROUP = CConfigValue<Hyprlang::INT>("group:drag_into_group");
static auto PMERGEFLOATEDINTOTILEDONGROUPBAR = CConfigValue<Hyprlang::INT>("group:merge_floated_into_tiled_on_groupbar");
static auto PMERGEGROUPSONGROUPBAR = CConfigValue<Hyprlang::INT>("group:merge_groups_on_groupbar");
- static auto POUTERGAP = CConfigValue<Hyprlang::INT>("group:groupbar:gaps_out");
- static auto PINNERGAP = CConfigValue<Hyprlang::INT>("group:groupbar:gaps_in");
- const bool FLOATEDINTOTILED = !m_window->m_isFloating && !pDraggedWindow->m_draggingTiled;
-
- g_pInputManager->m_wasDraggingWindow = false;
+ const bool FLOATEDINTOTILED = !m_window->m_isFloating && !g_layoutManager->dragController()->draggingTiled();
- if (!pDraggedWindow->canBeGroupedInto(m_window.lock()) || (*PDRAGINTOGROUP != 1 && *PDRAGINTOGROUP != 2) || (FLOATEDINTOTILED && !*PMERGEFLOATEDINTOTILEDONGROUPBAR) ||
- (!*PMERGEGROUPSONGROUPBAR && pDraggedWindow->m_groupData.pNextWindow.lock() && m_window->m_groupData.pNextWindow.lock())) {
- g_pInputManager->m_wasDraggingWindow = true;
+ if (!pDraggedWindow->canBeGroupedInto(m_window->m_group) || (*PDRAGINTOGROUP != 1 && *PDRAGINTOGROUP != 2) || (FLOATEDINTOTILED && !*PMERGEFLOATEDINTOTILEDONGROUPBAR) ||
+ (!*PMERGEGROUPSONGROUPBAR && pDraggedWindow->m_group))
return false;
- }
-
- const float BARRELATIVE = *PSTACKED ? pos.y - assignedBoxGlobal().y - (m_barHeight + *POUTERGAP) / 2 : pos.x - assignedBoxGlobal().x - m_barWidth / 2;
- const float BARSIZE = *PSTACKED ? m_barHeight + *POUTERGAP : m_barWidth + *PINNERGAP;
- const int WINDOWINDEX = BARRELATIVE < 0 ? -1 : BARRELATIVE / BARSIZE;
-
- PHLWINDOW pWindowInsertAfter = m_window->getGroupWindowByIndex(WINDOWINDEX);
- PHLWINDOW pWindowInsertEnd = pWindowInsertAfter->m_groupData.pNextWindow.lock();
- PHLWINDOW pDraggedHead = pDraggedWindow->m_groupData.pNextWindow.lock() ? pDraggedWindow->getGroupHead() : pDraggedWindow;
-
- if (!pDraggedWindow->m_groupData.pNextWindow.expired()) {
-
- // stores group data
- std::vector<PHLWINDOW> members;
- PHLWINDOW curr = pDraggedHead;
- const bool WASLOCKED = pDraggedHead->m_groupData.locked;
- do {
- members.push_back(curr);
- curr = curr->m_groupData.pNextWindow.lock();
- } while (curr != members[0]);
-
- // removes all windows
- for (const PHLWINDOW& w : members) {
- w->m_groupData.pNextWindow.reset();
- w->m_groupData.head = false;
- w->m_groupData.locked = false;
- g_pLayoutManager->getCurrentLayout()->onWindowRemoved(w);
- }
-
- // restores the group
- for (auto it = members.begin(); it != members.end(); ++it) {
- (*it)->m_isFloating = pWindowInsertAfter->m_isFloating; // match the floating state of group members
- *(*it)->m_realSize = pWindowInsertAfter->m_realSize->goal(); // match the size of group members
- *(*it)->m_realPosition = pWindowInsertAfter->m_realPosition->goal(); // match the position of group members
- if (std::next(it) != members.end())
- (*it)->m_groupData.pNextWindow = *std::next(it);
- else
- (*it)->m_groupData.pNextWindow = members[0];
- }
- members[0]->m_groupData.head = true;
- members[0]->m_groupData.locked = WASLOCKED;
- } else
- g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pDraggedWindow);
-
- pDraggedWindow->m_isFloating = pWindowInsertAfter->m_isFloating; // match the floating state of the window
-
- pWindowInsertAfter->insertWindowToGroup(pDraggedWindow);
-
- if (WINDOWINDEX == -1)
- std::swap(pDraggedHead->m_groupData.head, pWindowInsertEnd->m_groupData.head);
- m_window->setGroupCurrent(pDraggedWindow);
- pDraggedWindow->applyGroupRules();
- pDraggedWindow->updateWindowDecos();
- g_pLayoutManager->getCurrentLayout()->recalculateWindow(pDraggedWindow);
+ m_window->m_group->add(pDraggedWindow);
if (!pDraggedWindow->getDecorationByType(DECORATION_GROUPBAR))
pDraggedWindow->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(pDraggedWindow));
@@ -519,7 +455,7 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo
if (e.state == WL_POINTER_BUTTON_STATE_PRESSED)
pressedCursorPos = pos;
else if (e.state == WL_POINTER_BUTTON_STATE_RELEASED && pressedCursorPos == pos)
- g_pXWaylandManager->sendCloseWindow(m_window->getGroupWindowByIndex(WINDOWINDEX));
+ g_pXWaylandManager->sendCloseWindow(m_window->m_group->fromIndex(WINDOWINDEX));
return true;
}
@@ -532,17 +468,17 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo
const auto STACKPAD = *PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP);
if (TABPAD || STACKPAD) {
if (!g_pCompositor->isWindowActive(m_window.lock()))
- Desktop::focusState()->rawWindowFocus(m_window.lock());
+ Desktop::focusState()->rawWindowFocus(m_window.lock(), Desktop::FOCUS_REASON_CLICK);
return true;
}
- PHLWINDOW pWindow = m_window->getGroupWindowByIndex(WINDOWINDEX);
+ PHLWINDOW pWindow = m_window->m_group->fromIndex(WINDOWINDEX);
if (pWindow != m_window)
- pWindow->setGroupCurrent(pWindow);
+ pWindow->m_group->setCurrent(pWindow);
if (!g_pCompositor->isWindowActive(pWindow) && *PFOLLOWMOUSE != 3)
- Desktop::focusState()->rawWindowFocus(pWindow);
+ Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK);
if (pWindow->m_isFloating)
g_pCompositor->changeWindowZOrder(pWindow, true);
@@ -553,13 +489,13 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo
bool CHyprGroupBarDecoration::onScrollOnDeco(const Vector2D& pos, const IPointer::SAxisEvent e) {
static auto PGROUPBARSCROLLING = CConfigValue<Hyprlang::INT>("group:groupbar:scrolling");
- if (!*PGROUPBARSCROLLING || m_window->m_groupData.pNextWindow.expired())
+ if (!*PGROUPBARSCROLLING || !m_window->m_group)
return false;
if (e.delta > 0)
- m_window->setGroupCurrent(m_window->m_groupData.pNextWindow.lock());
+ m_window->m_group->moveCurrent(true);
else
- m_window->setGroupCurrent(m_window->getGroupPrevious());
+ m_window->m_group->moveCurrent(false);
return true;
}
diff --git a/src/render/decorations/DecorationPositioner.cpp b/src/render/decorations/DecorationPositioner.cpp
index 2982ba73..ce3a7a37 100644
--- a/src/render/decorations/DecorationPositioner.cpp
+++ b/src/render/decorations/DecorationPositioner.cpp
@@ -1,7 +1,7 @@
#include "DecorationPositioner.hpp"
#include "../../desktop/view/Window.hpp"
#include "../../managers/HookSystemManager.hpp"
-#include "../../managers/LayoutManager.hpp"
+#include "../../layout/target/Target.hpp"
CDecorationPositioner::CDecorationPositioner() {
static auto P = g_pHookSystem->hookDynamic("closeWindow", [this](void* call, SCallbackInfo& info, std::any data) {
@@ -278,7 +278,7 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) {
if (WINDOWDATA->extents != SBoxExtents{{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}}) {
WINDOWDATA->extents = {{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}};
- g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow);
+ pWindow->layoutTarget()->recalc();
}
}
diff --git a/src/render/pass/SurfacePassElement.cpp b/src/render/pass/SurfacePassElement.cpp
index d3027290..df410014 100644
--- a/src/render/pass/SurfacePassElement.cpp
+++ b/src/render/pass/SurfacePassElement.cpp
@@ -5,6 +5,7 @@
#include "../../protocols/core/Compositor.hpp"
#include "../../protocols/DRMSyncobj.hpp"
#include "../../managers/input/InputManager.hpp"
+#include "../../layout/LayoutManager.hpp"
#include "../Renderer.hpp"
#include <hyprutils/math/Box.hpp>
@@ -51,7 +52,7 @@ void CSurfacePassElement::draw(const CRegion& damage) {
if (!TEXTURE->m_texID)
return;
- const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_pInputManager->m_currentlyDraggedWindow && g_pInputManager->m_dragMode == MBIND_RESIZE;
+ const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE;
TRACY_GPU_ZONE("RenderSurface");
auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface);
@@ -163,7 +164,7 @@ void CSurfacePassElement::draw(const CRegion& damage) {
CBox CSurfacePassElement::getTexBox() {
const double outputX = -m_data.pMonitor->m_position.x, outputY = -m_data.pMonitor->m_position.y;
- const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_pInputManager->m_currentlyDraggedWindow && g_pInputManager->m_dragMode == MBIND_RESIZE;
+ const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE;
auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface);
CBox windowBox;