From d6cb0909ae674619a5ada8925b470a9b6dd28ea4 Mon Sep 17 00:00:00 2001 From: The_miro Date: Tue, 4 Aug 2026 18:09:36 +0200 Subject: [PATCH] feat(columns): open a workspace's first window in the middle column least_col() now breaks count ties by nearness to the middle column, so the first window of a workspace (every column still empty) lands centred instead of pinned to the left edge, and a restored batch fans out from the middle. reflow() keeps a lone window in the middle too, so the menu's column stepper doesn't shove it back to the left. Applied identically to hyprlua and hyprdrive. Co-Authored-By: Claude Opus 5 --- .../hyprdrive/hypr/layouts/columns.lua | 31 +++++++++++++++++-- desktopenvs/hyprlua/hypr/layouts/columns.lua | 31 +++++++++++++++++-- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/desktopenvs/hyprdrive/hypr/layouts/columns.lua b/desktopenvs/hyprdrive/hypr/layouts/columns.lua index 649ec77..45fac35 100644 --- a/desktopenvs/hyprdrive/hypr/layouts/columns.lua +++ b/desktopenvs/hyprdrive/hypr/layouts/columns.lua @@ -17,6 +17,11 @@ -- SCROLLS. Resizing up/down retunes the focused window's height weight. -- Weights and per-column pan persist across relayouts. -- +-- A workspace's FIRST window opens in the middle column (an even column count has no +-- true middle — it takes the left of the two), so a lone window is centred rather than +-- stuck against the left edge; further windows stack into the column you're working in +-- and otherwise fill outward from the middle. +-- -- This is a real custom layout (hl.layout.register), not the built-in scrolling engine — -- that one has a single shared tape; this pans each column apart. -- @@ -123,6 +128,11 @@ local function ws_state(wsid, area) return s end +-- The MIDDLE column — where a workspace's first window lands, and the tie-break for +-- every "emptiest column" pick, so a lone window sits centred on screen instead of pinned +-- to the left edge. An even column count has no true middle; take the left of the two. +local function mid_col(s) return math.floor((s.ncols + 1) / 2) end + -- Reflow every window evenly across the current column count, by insertion order. local function reflow(s, targets) local addrs = {} @@ -131,7 +141,13 @@ local function reflow(s, targets) if ok and addr then addrs[#addrs + 1] = addr end end table.sort(addrs, function(a, b) return (s.seq[a] or 0) < (s.seq[b] or 0) end) - for i, addr in ipairs(addrs) do s.assign[addr] = ((i - 1) % s.ncols) + 1 end + if #addrs == 1 then + -- a lone window keeps the middle (changing the column count from the menu + -- shouldn't shove it to the left edge). + s.assign[addrs[1]] = mid_col(s) + else + for i, addr in ipairs(addrs) do s.assign[addr] = ((i - 1) % s.ncols) + 1 end + end for k in pairs(s.pair) do s.pair[k] = nil end -- a redistribute breaks parked pairs end @@ -177,9 +193,18 @@ local function columnize(s, targets) end local fresh = {} for addr in pairs(present) do if not s.assign[addr] then fresh[#fresh + 1] = addr end end + -- emptiest column, ties broken by nearness to the middle — so the first window of a + -- workspace (every column still empty) opens in the middle column, and a fresh batch + -- fills outward from there rather than left-to-right. local function least_col() - local best, bestn = 1, math.huge - for c = 1, s.ncols do if counts[c] < bestn then best, bestn = c, counts[c] end end + local mid = mid_col(s) + local best, bestn, bestd = mid, math.huge, math.huge + for c = 1, s.ncols do + local d = math.abs(c - mid) + if counts[c] < bestn or (counts[c] == bestn and d < bestd) then + best, bestn, bestd = c, counts[c], d + end + end return best end if #fresh == 1 and next(s.assign) ~= nil then diff --git a/desktopenvs/hyprlua/hypr/layouts/columns.lua b/desktopenvs/hyprlua/hypr/layouts/columns.lua index 10c728b..d6cbea4 100644 --- a/desktopenvs/hyprlua/hypr/layouts/columns.lua +++ b/desktopenvs/hyprlua/hypr/layouts/columns.lua @@ -17,6 +17,11 @@ -- SCROLLS. Resizing up/down retunes the focused window's height weight. -- Weights and per-column pan persist across relayouts. -- +-- A workspace's FIRST window opens in the middle column (an even column count has no +-- true middle — it takes the left of the two), so a lone window is centred rather than +-- stuck against the left edge; further windows stack into the column you're working in +-- and otherwise fill outward from the middle. +-- -- This is a real custom layout (hl.layout.register), not the built-in scrolling engine — -- that one has a single shared tape; this pans each column apart. -- @@ -123,6 +128,11 @@ local function ws_state(wsid, area) return s end +-- The MIDDLE column — where a workspace's first window lands, and the tie-break for +-- every "emptiest column" pick, so a lone window sits centred on screen instead of pinned +-- to the left edge. An even column count has no true middle; take the left of the two. +local function mid_col(s) return math.floor((s.ncols + 1) / 2) end + -- Reflow every window evenly across the current column count, by insertion order. local function reflow(s, targets) local addrs = {} @@ -131,7 +141,13 @@ local function reflow(s, targets) if ok and addr then addrs[#addrs + 1] = addr end end table.sort(addrs, function(a, b) return (s.seq[a] or 0) < (s.seq[b] or 0) end) - for i, addr in ipairs(addrs) do s.assign[addr] = ((i - 1) % s.ncols) + 1 end + if #addrs == 1 then + -- a lone window keeps the middle (changing the column count from the menu + -- shouldn't shove it to the left edge). + s.assign[addrs[1]] = mid_col(s) + else + for i, addr in ipairs(addrs) do s.assign[addr] = ((i - 1) % s.ncols) + 1 end + end for k in pairs(s.pair) do s.pair[k] = nil end -- a redistribute breaks parked pairs end @@ -177,9 +193,18 @@ local function columnize(s, targets) end local fresh = {} for addr in pairs(present) do if not s.assign[addr] then fresh[#fresh + 1] = addr end end + -- emptiest column, ties broken by nearness to the middle — so the first window of a + -- workspace (every column still empty) opens in the middle column, and a fresh batch + -- fills outward from there rather than left-to-right. local function least_col() - local best, bestn = 1, math.huge - for c = 1, s.ncols do if counts[c] < bestn then best, bestn = c, counts[c] end end + local mid = mid_col(s) + local best, bestn, bestd = mid, math.huge, math.huge + for c = 1, s.ncols do + local d = math.abs(c - mid) + if counts[c] < bestn or (counts[c] == bestn and d < bestd) then + best, bestn, bestd = c, counts[c], d + end + end return best end if #fresh == 1 and next(s.assign) ~= nil then