noctalia-plugin: bar widget, toolbar panel and tile, with Luau tests and a flake check
This commit is contained in:
1 parent
c8626dbe3e
commit
127d44040e
15 files changed
+530
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user