From de666acac71d161b9eb257de5899adbc2309b554 Mon Sep 17 00:00:00 2001 From: The_miro Date: Tue, 7 Jul 2026 13:31:45 +0200 Subject: [PATCH] feat(astal-menu): Columns count stepper + expanded taskbar is one module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Columns layout publishes per-workspace column/row counts to columns-state.json and handles a direct "cols +1/-1/N" message. The menu's Columns page now shows a "Columns (−)[N](+)" stepper that reads the live count and drives it. - Expanding the taskbar now fully collapses the strip (header included) via its revealer, so the panel that covers the quads reads as a single module instead of a thin strip plus a detached card below it. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01SUN7gg6GGfnToghMijLm5Y --- desktopenvs/hyprlua/astal-menu/ui/taskbar.py | 38 +++++++++++++++- desktopenvs/hyprlua/astal-menu/window.py | 7 ++- desktopenvs/hyprlua/hypr/layouts/columns.lua | 47 +++++++++++++++----- desktopenvs/hyprlua/hypr/layouts/init.lua | 3 +- 4 files changed, 81 insertions(+), 14 deletions(-) diff --git a/desktopenvs/hyprlua/astal-menu/ui/taskbar.py b/desktopenvs/hyprlua/astal-menu/ui/taskbar.py index 6a5d143..4dbe419 100644 --- a/desktopenvs/hyprlua/astal-menu/ui/taskbar.py +++ b/desktopenvs/hyprlua/astal-menu/ui/taskbar.py @@ -25,6 +25,7 @@ from lib.proc import run_json, run_text from paths import CACHE_DIR _LAYOUTS_MANIFEST = CACHE_DIR / "layouts.json" +_COLUMNS_STATE = CACHE_DIR / "columns-state.json" _LAYOUTS_STATE = CACHE_DIR / "layouts-state.json" @@ -284,11 +285,46 @@ class Taskbar(Gtk.Box): row.append(sw) page.append(row) self._fit_sws[ly["name"]] = sw - if not ly.get("dirs") and not ly.get("fit_method"): + if ly.get("stepper"): # Columns: (-)[N](+) + page.append(self._cols_stepper()) + if not ly.get("dirs") and not ly.get("fit_method") and not ly.get("stepper"): page.append(Gtk.Label(label="No adjustable options", xalign=0.0, css_classes=["net-ip"])) return page + # -- columns count stepper -------------------------------------------- + def _cols_stepper(self) -> Gtk.Widget: + row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + row.append(Gtk.Label(label="Columns", xalign=0.0, hexpand=True)) + minus = Gtk.Button(label="−") + minus.add_css_class("quad-action") + self._cols_lbl = Gtk.Label(label=str(self._read_cols())) + self._cols_lbl.add_css_class("stepper-value") + plus = Gtk.Button(label="+") + plus.add_css_class("quad-action") + minus.connect("clicked", lambda *_a: self._step_cols(-1)) + plus.connect("clicked", lambda *_a: self._step_cols(1)) + row.append(minus) + row.append(self._cols_lbl) + row.append(plus) + return row + + def _read_cols(self) -> int: + try: + data = json.loads(_COLUMNS_STATE.read_text()) + return int(data.get(str(self._active_ws), {}).get("cols", 2)) + except (FileNotFoundError, json.JSONDecodeError, ValueError, TypeError): + return 2 + + def _step_cols(self, delta: int) -> None: + try: + cur = int(self._cols_lbl.get_label()) + except ValueError: + cur = self._read_cols() + new = max(1, cur + delta) + self._cols_lbl.set_label(str(new)) + _eval(f'hl.dispatch(hl.dsp.layout("cols {"+1" if delta > 0 else "-1"}"))') + # -- sync + handlers --------------------------------------------------- def _sync_layout_controls(self) -> None: # reflect the current per-ws layout / direction / focus_fit in the tabs+options diff --git a/desktopenvs/hyprlua/astal-menu/window.py b/desktopenvs/hyprlua/astal-menu/window.py index 60bbd76..1c12994 100644 --- a/desktopenvs/hyprlua/astal-menu/window.py +++ b/desktopenvs/hyprlua/astal-menu/window.py @@ -159,10 +159,13 @@ class MenuWindow(Gtk.ApplicationWindow): # -- taskbar panel expansion ------------------------------------------ def _on_taskbar_panel(self, expanded: bool) -> None: - # Cover the quads with the taskbar's workspace/window panel (the strip itself - # collapses inside the Taskbar). Collapse any expanded quad first. + # Cover the quads with the taskbar's workspace/window panel. Fully collapse the + # taskbar strip (header included) so the expanded panel reads as ONE module + # rather than a thin strip on top plus a detached card. Collapse any quad first. if expanded and self.grid.is_expanded: self.grid.request_collapse() + self.taskbar_reveal.set_reveal_child(not expanded) + self.taskbar_reveal.set_visible(not expanded) self.grid.show_takeover() if expanded else self.grid.hide_takeover() # -- appdrawer expansion ---------------------------------------------- diff --git a/desktopenvs/hyprlua/hypr/layouts/columns.lua b/desktopenvs/hyprlua/hypr/layouts/columns.lua index 8815797..dc333a4 100644 --- a/desktopenvs/hyprlua/hypr/layouts/columns.lua +++ b/desktopenvs/hyprlua/hypr/layouts/columns.lua @@ -19,15 +19,41 @@ local state = {} -- [wsid] = { ncols, rows, orient, assign={addr=col}, seq={addr=n}, nextseq, pan={col=px} } +-- Publish per-workspace column/row counts so the astal-menu can show a live stepper. +local STATE_FILE = (os.getenv("HOME") or "") .. "/.cache/astal-menu/columns-state.json" +local function publish() + local parts = {} + for wsid, s in pairs(state) do + parts[#parts + 1] = string.format('"%s":{"cols":%d,"rows":%d,"orient":"%s"}', + tostring(wsid), s.ncols, s.rows, s.orient) + end + pcall(function() + local f = io.open(STATE_FILE, "w") + if f then f:write("{" .. table.concat(parts, ",") .. "}\n"); f:close() end + end) +end + local function ws_state(wsid) local s = state[wsid] if not s then s = { ncols = 2, rows = 2, orient = "h", assign = {}, seq = {}, nextseq = 1, pan = {} } state[wsid] = s + publish() end return s end +-- Reflow every window evenly across the current column count, by insertion order. +local function reflow(s, targets) + local addrs = {} + for _, tg in ipairs(targets) do + local ok, addr = pcall(function() return tg.window.address end) + 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 +end + local function win_ws_id(win) local ok, ws = pcall(function() return win.workspace end) if ok and ws ~= nil then @@ -185,18 +211,18 @@ local function layout_msg(ctx, cmd) elseif arg == less_cols then s.ncols = math.max(1, s.ncols - 1); changed_cols = true elseif arg == more_rows then s.rows = s.rows + 1 elseif arg == less_rows then s.rows = math.max(1, s.rows - 1) end - if changed_cols then - -- reflow all windows evenly across the new column count (by insertion order) - local addrs = {} - for _, tg in ipairs(t) do - local ok, addr = pcall(function() return tg.window.address end) - 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 - end + if changed_cols then reflow(s, t) end + publish() + elseif verb == "cols" then + -- direct column-count change (the menu's (-)[N](+) stepper). + if arg == "+" or arg == "+1" then s.ncols = s.ncols + 1 + elseif arg == "-" or arg == "-1" then s.ncols = math.max(1, s.ncols - 1) + else local n = tonumber(arg); if n then s.ncols = math.max(1, math.floor(n)) end end + reflow(s, t) + publish() elseif verb == "orient" then s.orient = (arg == "v") and "v" or "h" + publish() end end @@ -216,4 +242,5 @@ return { dir_labels = { "Horizontal", "Vertical" }, default_dir = "h", fit_method = false, + stepper = true, -- menu shows a "Columns: (-)[N](+)" count stepper } diff --git a/desktopenvs/hyprlua/hypr/layouts/init.lua b/desktopenvs/hyprlua/hypr/layouts/init.lua index 198ee00..2210a9c 100644 --- a/desktopenvs/hyprlua/hypr/layouts/init.lua +++ b/desktopenvs/hyprlua/hypr/layouts/init.lua @@ -44,7 +44,8 @@ function M.write_manifest() ',"dirs":[', table.concat(dirs, ","), "]", ',"dir_labels":[', table.concat(dlabels, ","), "]", ',"default_dir":', jstr(s.default_dir or ""), - ',"fit_method":', tostring(s.fit_method or false), "}", + ',"fit_method":', tostring(s.fit_method or false), + ',"stepper":', tostring(s.stepper or false), "}", }) end os.execute("mkdir -p '" .. MANIFEST:match("(.+)/[^/]+$") .. "' 2>/dev/null")