summaryrefslogtreecommitdiff
path: root/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp
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 /src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp
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.
Diffstat (limited to 'src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp')
-rw-r--r--src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp274
1 files changed, 274 insertions, 0 deletions
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();
+}