noctalia-plugin: bar widget, toolbar panel and tile, with Luau tests and a flake check

This commit is contained in:
elwrcl committed 2026-10-04 05:58:08 +03:00
1 parent c8626dbe3e
commit 127d44040e
15 files changed
+530

No files matched your search

+137
View File
@@ -0,0 +1,137 @@
--!nonstrict
-- valw toolbar panel: the four modes, the timer and the options, drawn
-- with Noctalia's own components. The choices live in valw's toolbar.toml:
-- read with `valw toolbar --state`, changed with `valw toolbar --set`.
-- A mode click closes the panel, waits for it to go, then captures.
local MODES = {
{ id = "screen", glyph = "device-desktop" },
{ id = "region", glyph = "crop" },
{ id = "window", glyph = "app-window" },
{ id = "zoom", glyph = "zoom-in" },
}
local OPTIONS = { "cursor", "preview", "sound" }
-- The decoded `--state` JSON; nil until it arrives.
local state = nil
local unavailable = false
local function lastLine(text)
local last = ""
for line in string.gmatch(text or "", "[^\n]+") do
last = line
end
return last
end
-- Exit 3 is a cancel (Esc): not an error.
local function report(result)
if result.exitCode ~= 0 and result.exitCode ~= 3 then
noctalia.notifyError("valw", lastLine(result.stderr))
end
end
local render
local function capture(mode)
panel.close()
noctalia.runAsync("sleep 0.3 && valw toolbar --run " .. mode, report)
end
-- `key` and `value` are always one of our own tokens, never user text.
local function remember(key, value)
state[key] = value
render()
noctalia.runAsync("valw toolbar --set " .. key .. "=" .. tostring(value), report)
end
local function modeButton(m)
return ui.button({
text = noctalia.tr("mode." .. m.id),
glyph = m.glyph,
variant = if state.mode == m.id then "primary" else "outline",
onClick = function()
capture(m.id)
end,
})
end
local function optionRow(key)
return ui.row({ gap = 8, align = "center", justify = "space_between" }, {
ui.label({ text = noctalia.tr("option." .. key), color = "on_surface" }),
ui.toggle({
checked = state[key],
onChange = function(value)
remember(key, value == "true")
end,
}),
})
end
render = function()
if unavailable then
panel.render(ui.column({ padding = 16 }, {
ui.label({ text = noctalia.tr("panel.unavailable"), color = "error" }),
}))
return
end
if state == nil then
return
end
local modes = {}
for _, m in ipairs(MODES) do
table.insert(modes, modeButton(m))
end
local timers, selected = {}, 0
for i, t in ipairs(state.timers) do
table.insert(timers, if t == 0 then noctalia.tr("timer.off") else tostring(t) .. " s")
if t == state.timer then
selected = i - 1
end
end
local rows = {
ui.row({ gap = 8, justify = "space_between" }, modes),
ui.separator({}),
ui.row({ gap = 8, align = "center", justify = "space_between" }, {
ui.label({ text = noctalia.tr("timer.label"), color = "on_surface" }),
ui.select({
options = timers,
selectedIndex = selected,
onChange = function(index)
remember("timer", state.timers[tonumber(index) + 1])
end,
}),
}),
}
for _, key in ipairs(OPTIONS) do
table.insert(rows, optionRow(key))
end
panel.render(ui.column({ gap = 12, padding = 16 }, rows))
end
local function fail(message)
unavailable = true
noctalia.notifyError("valw", message)
render()
end
function onOpen(_context)
unavailable = false
if not noctalia.commandExists("valw") then
fail(noctalia.tr("panel.unavailable"))
return
end
noctalia.runAsync("valw toolbar --state", function(result)
if result.exitCode ~= 0 then
fail(lastLine(result.stderr))
return
end
local decoded = noctalia.json.decode(result.stdout)
if type(decoded) ~= "table" then
fail(noctalia.tr("panel.unavailable"))
return
end
state = decoded
render()
end)
end