From febf550a2053c36c97626922fc2696faadb68ddb Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:10:00 -0400 Subject: algo/scrolling: add expel, consume, and consume_or_expel (#13869) ref https://github.com/hyprwm/Hyprland/discussions/13403\#discussioncomment-15972750 --- .../tiled/scrolling/ScrollingAlgorithm.cpp | 68 ++++++++++++++++++++-- 1 file changed, 62 insertions(+), 6 deletions(-) (limited to 'src/layout/algorithm/tiled/scrolling') diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp index 93a83045..5685d650 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp @@ -1358,18 +1358,74 @@ std::expected CScrollingAlgorithm::layoutMsg(const std::strin g_pCompositor->warpCursorTo(pTargetData->target->window()->middle()); } } - } else if (ARGS[0] == "promote") { + } else if (ARGS[0] == "promote" || ARGS[0] == "consume" || ARGS[0] == "expel" || ARGS[0] == "consume_or_expel") { 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); + const auto CURRENT_COL = TDATA->column.lock(); + if (!CURRENT_COL) + return std::unexpected("no current col"); - TDATA->column->remove(TDATA->target.lock()); + // expel a target from srcCol into its own new column at insertIdx + auto expelTarget = [&](SP tdata, SP srcCol, std::optional insertIdx) { + auto col = !insertIdx ? m_scrollingData->add() : m_scrollingData->add(*insertIdx); + srcCol->remove(tdata->target.lock()); + col->add(tdata); + m_scrollingData->centerOrFitCol(col); + }; + + // consume the first target from adjCol into dstCol + auto consumeTarget = [&](SP dstCol, SP adjCol) { + const auto target = adjCol->targetDatas.front(); + adjCol->remove(target->target.lock()); + dstCol->add(target); + m_scrollingData->centerOrFitCol(dstCol); + }; + + if (ARGS[0] == "promote") { + auto idx = m_scrollingData->idx(CURRENT_COL); + expelTarget(TDATA, CURRENT_COL, idx == -1 ? std::nullopt : std::optional{idx}); + } else if (ARGS[0] == "expel") { + if (CURRENT_COL->targetDatas.size() < 2) + return std::unexpected("column has only one window"); + + const auto lastTarget = CURRENT_COL->targetDatas.back(); + const auto currentIdx = m_scrollingData->idx(CURRENT_COL); + const auto NEXT_COL = m_scrollingData->next(CURRENT_COL); + const auto insertIdx = !NEXT_COL ? std::nullopt : std::optional{currentIdx}; + + expelTarget(lastTarget, CURRENT_COL, insertIdx); + } else if (ARGS[0] == "consume") { + const auto NEXT_COL = m_scrollingData->next(CURRENT_COL); + if (!NEXT_COL) + return std::unexpected("no next column"); + + consumeTarget(CURRENT_COL, NEXT_COL); + } else if (ARGS[0] == "consume_or_expel") { + if (ARGS.size() < 2) + return std::unexpected("not enough args"); + + const std::string& direction = ARGS[1]; + const bool prev = direction == "prev"; + const bool next = direction == "next"; + + if (!prev && !next) + return std::unexpected("invalid direction, expected prev or next"); + + if (CURRENT_COL->targetDatas.size() > 1) { + const auto currentIdx = m_scrollingData->idx(CURRENT_COL); + expelTarget(TDATA, CURRENT_COL, prev ? currentIdx - 1 : currentIdx); + } else { + const auto ADJ_COL = prev ? m_scrollingData->prev(CURRENT_COL) : m_scrollingData->next(CURRENT_COL); + if (!ADJ_COL) + return std::unexpected("no adjacent column"); - col->add(TDATA); + CURRENT_COL->remove(TDATA->target.lock()); + ADJ_COL->add(TDATA); + m_scrollingData->centerOrFitCol(ADJ_COL); + } + } m_scrollingData->recalculate(); } else if (ARGS[0] == "swapcol") { -- cgit v1.2.3