From 13593649ac7086c451dd9c736b8dfd9de81f3406 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sun, 18 Oct 2026 14:30:32 +0200 Subject: feat(card): standard header-and-rows helper, centred text, forecast glyphs card.standard() draws the DESIGN.md card (title left, big value right, label/value rows) from one fluid size and returns where it stopped, so the forecast cards share it instead of copying breaktimer's layout five times. Co-Authored-By: Claude Sonnet 5.5 --- lib/card.lua | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/lib/card.lua b/lib/card.lua index 0869ce9..d1f37f7 100644 --- a/lib/card.lua +++ b/lib/card.lua @@ -175,6 +175,12 @@ function M.text_right(cr, x, y, s) M.text(cr, x - w, y, s) end +-- Centred text: x is the CENTRE. +function M.text_centre(cr, x, y, s) + local w = M.measure(cr, s) + M.text(cr, x - w / 2, y, s) +end + -- A rounded-rectangle path. Does not paint; the caller fills or strokes, so one -- path can serve both a fill and its border. function M.rounded_path(cr, x, y, w, h, r) @@ -218,6 +224,10 @@ M.ICON = { breaktimer = '\u{F13AB}', -- timer calendar = '\u{F0E17}', -- calendar-month immich = '\u{F02F9}', -- image-multiple + hourly = '\u{F0150}', -- clock-outline + week = '\u{F0A33}', -- calendar-week + details = '\u{F059D}', -- weather-windy + sky = '\u{F0594}', -- weather-night } -- The gap between a title's icon and its text. One constant so the measured @@ -304,6 +314,61 @@ function M.fit_unit(cr, max_w, groups, ref) return S end +-- The DESIGN.md card in one call: the title top-left, the big value top-right, +-- then label-left / value-right rows, all from one fluid base size S. +-- +-- spec.icon, spec.title the title run, as M.title draws it +-- spec.big the big value; nil drops it (a band carries the value) +-- spec.big_color defaults to colors.body +-- spec.big_font defaults to FONT_HEAVY; FONT_MONO for a glyph +-- spec.rows { {label, value, color}, ... }, color defaults to colors.value +-- spec.share fraction of inner.h the header and rows may use (default 1) +-- +-- Returns the y where the drawn content ends and S, so a card can put its +-- variable band in what is left and size the band's type off the same S. +-- The proportions are the breaktimer card's, which this generalises. +function M.standard(cr, inner, colors, spec) + local LABEL_F, BIG_F, ROW_F = 0.30, 1.0, 0.32 + local rows = spec.rows or {} + local big_font = spec.big_font or M.FONT_HEAVY + local head = { { M.title_str(spec.icon, spec.title), M.FONT_MONO, LABEL_F } } + if spec.big then head[2] = { spec.big, big_font, BIG_F } end + local groups = { head } + for _, r in ipairs(rows) do + groups[#groups + 1] = { { r[1], M.FONT_MONO, ROW_F }, + { tostring(r[2]), M.FONT_MONO, ROW_F } } + end + -- Height in units of S: the header line, the gap under it, a step per row. + local head_f = spec.big and BIG_F or LABEL_F * 1.2 + local budget = inner.h * (spec.share or 1) + local S = clamp(math.min(budget * 0.94 / (head_f + 0.5 + #rows * 0.66), + M.fit_unit(cr, inner.w * 0.96, groups, 100)), 10, 72) + local label_size, row_size = S * LABEL_F, S * ROW_F + + if spec.big then + M.font(cr, big_font, S * BIG_F, false) + M.rgba(cr, spec.big_color or colors.body) + M.text_right(cr, inner.x + inner.w, inner.y + S * BIG_F, spec.big) + end + M.font(cr, M.FONT_MONO, label_size, false) + M.title(cr, inner.x, inner.y + label_size, spec.icon, spec.title, colors) + + local step = row_size * 2.0 + local y = inner.y + S * head_f + row_size * 1.6 + local last = inner.y + S * head_f + M.font(cr, M.FONT_MONO, row_size, false) + for _, r in ipairs(rows) do + if y > inner.y + inner.h then break end + M.rgba(cr, colors.label) + M.text(cr, inner.x, y, r[1]) + M.rgba(cr, r[3] or colors.value) + M.text_right(cr, inner.x + inner.w, y, tostring(r[2])) + last = y + y = y + step + end + return last + row_size * 0.8, S +end + -- Shorten a string to `limit` characters, appending an ellipsis when it cuts. -- -- By character, never by byte: Lua's string.sub counts bytes, so slicing a -- cgit v1.2.3