feat(astal-menu): Columns count stepper + expanded taskbar is one module
- 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SUN7gg6GGfnToghMijLm5Ymain
parent
9d1ec69a11
commit
de666acac7
|
|
@ -25,6 +25,7 @@ from lib.proc import run_json, run_text
|
||||||
from paths import CACHE_DIR
|
from paths import CACHE_DIR
|
||||||
|
|
||||||
_LAYOUTS_MANIFEST = CACHE_DIR / "layouts.json"
|
_LAYOUTS_MANIFEST = CACHE_DIR / "layouts.json"
|
||||||
|
_COLUMNS_STATE = CACHE_DIR / "columns-state.json"
|
||||||
_LAYOUTS_STATE = CACHE_DIR / "layouts-state.json"
|
_LAYOUTS_STATE = CACHE_DIR / "layouts-state.json"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -284,11 +285,46 @@ class Taskbar(Gtk.Box):
|
||||||
row.append(sw)
|
row.append(sw)
|
||||||
page.append(row)
|
page.append(row)
|
||||||
self._fit_sws[ly["name"]] = sw
|
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,
|
page.append(Gtk.Label(label="No adjustable options", xalign=0.0,
|
||||||
css_classes=["net-ip"]))
|
css_classes=["net-ip"]))
|
||||||
return page
|
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 ---------------------------------------------------
|
# -- sync + handlers ---------------------------------------------------
|
||||||
def _sync_layout_controls(self) -> None:
|
def _sync_layout_controls(self) -> None:
|
||||||
# reflect the current per-ws layout / direction / focus_fit in the tabs+options
|
# reflect the current per-ws layout / direction / focus_fit in the tabs+options
|
||||||
|
|
|
||||||
|
|
@ -159,10 +159,13 @@ class MenuWindow(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
# -- taskbar panel expansion ------------------------------------------
|
# -- taskbar panel expansion ------------------------------------------
|
||||||
def _on_taskbar_panel(self, expanded: bool) -> None:
|
def _on_taskbar_panel(self, expanded: bool) -> None:
|
||||||
# Cover the quads with the taskbar's workspace/window panel (the strip itself
|
# Cover the quads with the taskbar's workspace/window panel. Fully collapse the
|
||||||
# collapses inside the Taskbar). Collapse any expanded quad first.
|
# 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:
|
if expanded and self.grid.is_expanded:
|
||||||
self.grid.request_collapse()
|
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()
|
self.grid.show_takeover() if expanded else self.grid.hide_takeover()
|
||||||
|
|
||||||
# -- appdrawer expansion ----------------------------------------------
|
# -- appdrawer expansion ----------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,41 @@
|
||||||
|
|
||||||
local state = {} -- [wsid] = { ncols, rows, orient, assign={addr=col}, seq={addr=n}, nextseq, pan={col=px} }
|
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 function ws_state(wsid)
|
||||||
local s = state[wsid]
|
local s = state[wsid]
|
||||||
if not s then
|
if not s then
|
||||||
s = { ncols = 2, rows = 2, orient = "h", assign = {}, seq = {}, nextseq = 1, pan = {} }
|
s = { ncols = 2, rows = 2, orient = "h", assign = {}, seq = {}, nextseq = 1, pan = {} }
|
||||||
state[wsid] = s
|
state[wsid] = s
|
||||||
|
publish()
|
||||||
end
|
end
|
||||||
return s
|
return s
|
||||||
end
|
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 function win_ws_id(win)
|
||||||
local ok, ws = pcall(function() return win.workspace end)
|
local ok, ws = pcall(function() return win.workspace end)
|
||||||
if ok and ws ~= nil then
|
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 == 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 == more_rows then s.rows = s.rows + 1
|
||||||
elseif arg == less_rows then s.rows = math.max(1, s.rows - 1) end
|
elseif arg == less_rows then s.rows = math.max(1, s.rows - 1) end
|
||||||
if changed_cols then
|
if changed_cols then reflow(s, t) end
|
||||||
-- reflow all windows evenly across the new column count (by insertion order)
|
publish()
|
||||||
local addrs = {}
|
elseif verb == "cols" then
|
||||||
for _, tg in ipairs(t) do
|
-- direct column-count change (the menu's (-)[N](+) stepper).
|
||||||
local ok, addr = pcall(function() return tg.window.address end)
|
if arg == "+" or arg == "+1" then s.ncols = s.ncols + 1
|
||||||
if ok and addr then addrs[#addrs + 1] = addr end
|
elseif arg == "-" or arg == "-1" then s.ncols = math.max(1, s.ncols - 1)
|
||||||
end
|
else local n = tonumber(arg); if n then s.ncols = math.max(1, math.floor(n)) end end
|
||||||
table.sort(addrs, function(a, b) return (s.seq[a] or 0) < (s.seq[b] or 0) end)
|
reflow(s, t)
|
||||||
for i, addr in ipairs(addrs) do s.assign[addr] = ((i - 1) % s.ncols) + 1 end
|
publish()
|
||||||
end
|
|
||||||
elseif verb == "orient" then
|
elseif verb == "orient" then
|
||||||
s.orient = (arg == "v") and "v" or "h"
|
s.orient = (arg == "v") and "v" or "h"
|
||||||
|
publish()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -216,4 +242,5 @@ return {
|
||||||
dir_labels = { "Horizontal", "Vertical" },
|
dir_labels = { "Horizontal", "Vertical" },
|
||||||
default_dir = "h",
|
default_dir = "h",
|
||||||
fit_method = false,
|
fit_method = false,
|
||||||
|
stepper = true, -- menu shows a "Columns: (-)[N](+)" count stepper
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ function M.write_manifest()
|
||||||
',"dirs":[', table.concat(dirs, ","), "]",
|
',"dirs":[', table.concat(dirs, ","), "]",
|
||||||
',"dir_labels":[', table.concat(dlabels, ","), "]",
|
',"dir_labels":[', table.concat(dlabels, ","), "]",
|
||||||
',"default_dir":', jstr(s.default_dir or ""),
|
',"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
|
end
|
||||||
os.execute("mkdir -p '" .. MANIFEST:match("(.+)/[^/]+$") .. "' 2>/dev/null")
|
os.execute("mkdir -p '" .. MANIFEST:match("(.+)/[^/]+$") .. "' 2>/dev/null")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue