Files
community-plugins/cat/cat.luau
T
00da2cd291 cat: 1.2.0 — click popup panel with the cat and CPU % (#103)
Clicking the bar widget now toggles an attached panel showing the same
cat glyph at panel size plus the CPU percentage, replacing the old
notification. The widget publishes its visible state (glyph, cpu,
color, pace) through the plugin's shared state store and the panel
re-renders on each update, keeping the big cat frame-synced with the
bar cat.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:34:35 -04:00

177 lines
5.4 KiB
Luau

--!nonstrict
-- Cat: an animated running cat bar widget. Pace reflects CPU usage, sampled
-- from /proc/stat, with separately configurable walk/run thresholds. Color
-- follows the theme's secondary role by default, or a pinned custom color.
--
-- The cat shape is a custom pictographic font (fonts/catwalk2.otf, traced
-- from the MIT-licensed CatWalk plasmoid by Driglu4it) so it can be colored
-- like any other bar text: "a" = idle/asleep, "b".."f" = the 5-frame run
-- cycle. Each glyph's advance width is trimmed to the shared ink extent
-- (non-negative left bearing, no dead trailing space) so bar layout doesn't
-- overlap the previous widget or leave a large gap before the next one.
--
-- Font registration is process-global and keyed by file path/family, so this
-- filename must change (not just its contents) whenever the glyph set changes
-- during development, or the shell keeps rendering whatever it cached the
-- first time it saw this path.
local catFont = noctalia.loadFont("fonts/catwalk2.otf")
local GLYPH_IDLE = "a"
local RUN_GLYPHS = { "b", "c", "d", "e", "f" }
local TICK_MS = 80
local WALK_MAX_MS = 380
local WALK_MIN_MS = 180
local RUN_MAX_MS = 160
local RUN_MIN_MS = 55
local catSize = noctalia.getConfig("cat_size")
local showCpuPercent = noctalia.getConfig("show_cpu_percent")
local walkThreshold = noctalia.getConfig("walk_threshold")
local runThreshold = noctalia.getConfig("run_threshold")
local pollIntervalMs = noctalia.getConfig("poll_interval") * 1000
local colorMode = noctalia.getConfig("cat_color_mode")
local customColor = noctalia.getConfig("cat_color")
local frameIndex = 1
local frameElapsed = 0
local frameDurationMs = WALK_MAX_MS
local sampleElapsed = pollIntervalMs -- sample immediately on first tick
local cpuPercent = 0
local prevTotal, prevIdle = nil, nil
local function sampleCpu()
local stat = noctalia.readFile("/proc/stat")
if not stat then
return
end
local line = stat:match("^cpu%s+(.-)\n") or stat:match("^cpu%s+(.-)$")
if not line then
return
end
local fields = {}
for n in line:gmatch("%d+") do
table.insert(fields, tonumber(n))
end
local idle = (fields[4] or 0) + (fields[5] or 0)
local total = 0
for _, v in fields do
total += v
end
if prevTotal then
local totalDelta = total - prevTotal
local idleDelta = idle - prevIdle
if totalDelta > 0 then
cpuPercent = math.clamp((totalDelta - idleDelta) / totalDelta * 100, 0, 100)
end
end
prevTotal = total
prevIdle = idle
end
-- Returns ("idle" | "walk" | "run", frame-duration-ms-for-that-pace).
local function paceFor(cpu)
local runFloor = math.max(runThreshold, walkThreshold + 1)
if cpu < walkThreshold then
return "idle", WALK_MAX_MS
elseif cpu < runFloor then
local range = math.max(runFloor - walkThreshold, 1)
local t = math.clamp((cpu - walkThreshold) / range, 0, 1)
return "walk", WALK_MAX_MS - t * (WALK_MAX_MS - WALK_MIN_MS)
else
local range = math.max(100 - runFloor, 1)
local t = math.clamp((cpu - runFloor) / range, 0, 1)
return "run", RUN_MAX_MS - t * (RUN_MAX_MS - RUN_MIN_MS)
end
end
local function resolveColor()
if colorMode == "custom" and customColor and customColor ~= "" then
return customColor
end
return "secondary"
end
-- The click panel (cat_panel.luau) runs in a separate runtime; it mirrors the
-- bar cat from this shared-state snapshot, republished whenever the visible
-- glyph, whole CPU percent, or color changes (so at most once per animation
-- frame, and only on sample/theme changes while idle).
local lastPublished = nil
local function publishState(pace, glyph, color)
local cpu = math.floor(cpuPercent)
local key = `{glyph}|{cpu}|{color}|{pace}`
if key == lastPublished then
return
end
lastPublished = key
noctalia.state.set("cat", { glyph = glyph, cpu = cpu, color = color, pace = pace })
end
local function render(pace)
local glyph = pace == "idle" and GLYPH_IDLE or RUN_GLYPHS[frameIndex]
local color = resolveColor()
publishState(pace, glyph, color)
local children = {
ui.label({ text = glyph, fontFamily = catFont, baseline = "inkCentered", fontSize = catSize, color = color }),
}
if showCpuPercent then
table.insert(children, ui.label({ text = `{math.floor(cpuPercent)}%`, fontSize = 11, color = color }))
end
local container = barWidget.isVertical() and ui.column or ui.row
barWidget.render(container({ gap = 4, align = "center" }, children))
barWidget.setTooltip(`Cat — CPU {math.floor(cpuPercent)}% ({pace})`)
end
noctalia.setUpdateInterval(TICK_MS)
function update()
sampleElapsed += TICK_MS
if sampleElapsed >= pollIntervalMs then
sampleElapsed = 0
sampleCpu()
end
local pace, duration = paceFor(cpuPercent)
frameDurationMs = duration
if pace == "idle" then
frameElapsed = 0
frameIndex = 1
else
frameElapsed += TICK_MS
if frameElapsed >= frameDurationMs then
frameElapsed = 0
frameIndex = frameIndex % #RUN_GLYPHS + 1
end
end
render(pace)
end
function onClick()
noctalia.togglePanel("dotnetrob/cat:panel")
end
function onConfigChanged()
catSize = noctalia.getConfig("cat_size")
showCpuPercent = noctalia.getConfig("show_cpu_percent")
walkThreshold = noctalia.getConfig("walk_threshold")
runThreshold = noctalia.getConfig("run_threshold")
pollIntervalMs = noctalia.getConfig("poll_interval") * 1000
colorMode = noctalia.getConfig("cat_color_mode")
customColor = noctalia.getConfig("cat_color")
render((paceFor(cpuPercent)))
end