b2f58118e1
Remove OpenFontRender, embedded Meslo assets, custom-font APIs, and their runtime allocation overhead. Keep the taller status layout and compact card grid, with used RAM, aligned faded WiFi bars, and tighter telemetry rows.
137 lines
5.0 KiB
Lua
137 lines
5.0 KiB
Lua
-- The top bar, painted by the firmware once a second in every app. It draws in panel
|
|
-- coordinates (the firmware drops the app viewport around the call), so gui.getWidth()
|
|
-- here is the whole screen.
|
|
--
|
|
-- Invalidation is entirely local: draw() compares what it is about to paint against what
|
|
-- it last painted, so the firmware never has to tell the bar that anything changed.
|
|
|
|
local ui = require("ui")
|
|
|
|
local PAD = 6
|
|
|
|
-- The firmware reads both: `height` is the strip it keeps apps out of, `interval` is how
|
|
-- often it calls draw().
|
|
local M = {height = 44, interval = 1000}
|
|
local BAR_H = M.height
|
|
local WIFI_W = 11
|
|
local WIFI_HEIGHTS = {3, 5, 8}
|
|
local GAP = 8
|
|
local ROW_INSET = 8
|
|
|
|
-- What is currently on the panel. Every field repaints only when its value changes, so
|
|
-- the once-a-second tick costs one clock rectangle instead of a whole bar.
|
|
local shown = {}
|
|
local geometry = {}
|
|
|
|
local function clock()
|
|
return sys.isClockSynced() and os.date("%H:%M:%S") or "--:--:--"
|
|
end
|
|
|
|
local function signalBars()
|
|
local status = wifi.getStatus()
|
|
if status.state ~= "connected" then return 0 end
|
|
return status.rssi >= -60 and 3 or status.rssi >= -75 and 2 or 1
|
|
end
|
|
|
|
local function memory()
|
|
local free, total = sys.getMemory()
|
|
local used = math.max(0, (total or 0) - (free or 0))
|
|
return (used + 512) // 1024 .. "kB"
|
|
end
|
|
|
|
local function drawWifi(x, y, bars, color, muted)
|
|
for i, h in ipairs(WIFI_HEIGHTS) do
|
|
gui.fillRect(x + (i - 1) * 4, y + 8 - h, 3, h, i <= bars and color or muted)
|
|
end
|
|
end
|
|
|
|
-- Fills the leading square of the bar, which is the rect the firmware treats as back.
|
|
-- Drawn as a button rather than left as a hidden hit region: a target nobody can see is
|
|
-- one nobody finds.
|
|
local function drawBack(theme)
|
|
local inset = 3
|
|
local side = BAR_H - 1 - inset * 2
|
|
gui.roundRect(inset, inset, side, side, 4, theme.bg, theme.face[1], theme.face[2], theme.muted)
|
|
local x, y = inset + side // 2 + 1, inset + side // 2
|
|
for offset = 0, 1 do -- two passes, because a one pixel chevron reads as a speck
|
|
gui.drawLine(x + offset, y - 4, x + offset - 4, y, theme.fg)
|
|
gui.drawLine(x + offset - 4, y, x + offset, y + 4, theme.fg)
|
|
end
|
|
end
|
|
|
|
-- Leaving fullscreen is the one change the bar cannot observe: no draw happens while an
|
|
-- app owns the panel, so the state it last painted still matches the state it would paint
|
|
-- now, while the pixels are gone. Apps toggle fullscreen through here for that reason.
|
|
function M.setFullscreen(on)
|
|
gui.setFullscreen(on)
|
|
if not on then shown = {} end
|
|
end
|
|
|
|
function M.draw(hasBack)
|
|
local theme = ui.theme
|
|
gui.setTextSize(1)
|
|
local frameKey = gui.getRotation()
|
|
if geometry.key ~= frameKey then
|
|
local w = gui.getWidth()
|
|
local fontH = gui.getFontHeight()
|
|
local clockW = gui.getTextWidth("00:00:00")
|
|
local memW = gui.getTextWidth("000kB")
|
|
local clockX = w - PAD - clockW
|
|
local signalX = w - PAD - WIFI_W
|
|
local memX = signalX - GAP - memW
|
|
geometry = {
|
|
key = frameKey, w = w, titleY = (BAR_H - 1 - fontH) // 2,
|
|
topY = ROW_INSET, bottomY = BAR_H - 1 - ROW_INSET - fontH, fontH = fontH,
|
|
clockW = clockW, memW = memW, clockX = clockX, signalX = signalX,
|
|
memX = memX, rightX = math.min(clockX, memX),
|
|
}
|
|
end
|
|
local w, titleY, topY, bottomY = geometry.w, geometry.titleY, geometry.topY, geometry.bottomY
|
|
local fontH, clockW, memW = geometry.fontH, geometry.clockW, geometry.memW
|
|
local clockX, signalX, memX, rightX = geometry.clockX, geometry.signalX, geometry.memX, geometry.rightX
|
|
|
|
-- Rotation moves every slot, the theme recolors them, and an app may rename itself at
|
|
-- any time. A new app gets a fresh Lua state, so an empty cache already means "repaint
|
|
-- everything".
|
|
local name = sys.getAppName()
|
|
local key = frameKey .. "|" .. ui.themeName .. "|" .. name
|
|
if key ~= shown.key then
|
|
shown = {key = key}
|
|
gui.fillRect(0, 0, w, BAR_H, theme.bg)
|
|
gui.fillRect(0, BAR_H - 1, w, 1, theme.muted) -- a rule, so the bar reads as chrome
|
|
if hasBack then drawBack(theme) end
|
|
|
|
local titleLeft = hasBack and BAR_H + PAD or PAD
|
|
local titleRight = rightX - GAP
|
|
local available = titleRight - titleLeft
|
|
while #name > 1 and gui.getTextWidth(name) > available do name = name:sub(1, -2) end
|
|
local nameW = gui.getTextWidth(name)
|
|
local nameX = (w - nameW) // 2
|
|
nameX = math.max(titleLeft, math.min(nameX, titleRight - nameW))
|
|
gui.drawText(name, nameX, titleY, theme.fg, theme.bg)
|
|
end
|
|
|
|
local mem = memory()
|
|
if mem ~= shown.mem then
|
|
shown.mem = mem
|
|
gui.fillRect(memX, bottomY, memW, fontH, theme.bg)
|
|
gui.drawText(mem, memX, bottomY, theme.muted, theme.bg)
|
|
end
|
|
|
|
local bars = signalBars()
|
|
if bars ~= shown.bars then
|
|
shown.bars = bars
|
|
gui.fillRect(signalX, bottomY, WIFI_W, math.max(fontH + 1, 11), theme.bg)
|
|
drawWifi(signalX, bottomY, bars, theme.fg, theme.muted)
|
|
end
|
|
|
|
local time = clock()
|
|
if time ~= shown.time then
|
|
shown.time = time
|
|
gui.fillRect(clockX, topY, clockW, fontH, theme.bg)
|
|
gui.drawText(time, clockX, topY, theme.muted, theme.bg)
|
|
end
|
|
end
|
|
|
|
return M
|