diff options
| -rw-r--r-- | README.md | 27 | ||||
| -rw-r--r-- | widgets/clock.lua | 41 |
2 files changed, 58 insertions, 10 deletions
@@ -100,6 +100,33 @@ bug here: ink width ignores leading and trailing spaces, so `' / '` measures 6px of ink against a 14px advance, and stepping a cursor by the ink width renders the date as `16 /SEP /2026`. +### Widgets are fluid + +A widget is handed a rectangle and must fill it, whatever shape the grid makes +it. That is what lets you retune `COLS`/`ROWS` or move a card without touching +widget code. Three rules, all learned by getting them wrong: + +**Derive every size and offset from the rect.** No fixed pixel drops between +elements. A fixed `+34` under a numeral that scales leaves a dead band in a tall +cell and overlaps in a short one. + +**Scale type off the WIDTH, not the height.** Height is what changes when you +add a row to the grid, and a card that shrinks its text every time its cell gets +shorter reads as broken beside its neighbours. Width is also usually what +constrains the text, since rows run edge to edge. Clamp it: `math.max(lo, +math.min(hi, inner.w * k))`. + +**Decide where the slack goes, and let one element absorb it.** The clock pins +its date block to the bottom and gives the numerals everything above. The +weather card stacks from the top and lets the arc take the remainder. Either is +fine; what fails is leaving the slack wherever it lands. + +An element that cannot shrink below some size should drop out rather than +overlap. The weather arc does this: below 70px it is not drawn at all, because a +curve crossing the stat rows is worse than no curve. Check a widget at several +spans before believing it is fluid, which the offscreen renderer above makes +cheap. + ## Development Run it in the foreground to see Lua output, which is the only debugging channel: diff --git a/widgets/clock.lua b/widgets/clock.lua index 978715e..c3e9712 100644 --- a/widgets/clock.lua +++ b/widgets/clock.lua @@ -1,18 +1,37 @@ -- Clock: hour stacked over minute, weekday, slashed date. -- -- Shape follows idea2.png; every colour comes from the palette. +-- +-- Fluid: every size and offset derives from the rect it is handed, and the +-- date block pins to the bottom so the slack in a tall cell falls between the +-- numerals and the date rather than piling up under everything. A widget is +-- given a rectangle and should fill it, whatever shape the grid makes it. local card = require 'lib.card' local M = {} +local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end + function M.draw(cr, rect, colors) local inner = card.card(cr, rect, colors) + local x = inner.x + local bottom = inner.y + inner.h - -- Numeral size is derived from the cell, not fixed, so the same widget fills - -- a 1x2 cell on either monitor instead of needing a per-screen constant. - -- Two stacked numerals plus the date block: allow 40% of the height each. - local size = math.min(inner.h * 0.40, inner.w * 0.95) + -- The date block is sized first, because the numerals get whatever is left. + -- Both scale off the WIDTH: height changes with the grid, and a widget that + -- shrinks its type every time its cell gets shorter reads as broken next to + -- its neighbours. Width is also what actually constrains the date line, + -- which runs edge to edge. + local day_size = clamp(inner.w * 0.055, 12, 26) + local date_size = clamp(inner.w * 0.050, 11, 24) + local date_band = day_size * 1.6 + date_size * 1.8 + + -- Two stacked numerals fill the space above the date band. Whichever of + -- height and width binds first wins, so a wide short cell is limited by its + -- height and a narrow tall one by its width, and neither overflows. + local avail = (bottom - date_band) - inner.y + local size = math.min(avail * 0.46, inner.w * 0.95) card.font(cr, card.FONT_HEAVY, size, false) card.rgba(cr, colors.body) @@ -20,7 +39,6 @@ function M.draw(cr, rect, colors) -- Baselines. Cairo's y is the baseline, so the first sits one cap-height -- down; 0.78 of the font size approximates cap height for Noto Sans and -- avoids measuring every frame. - local x = inner.x local y1 = inner.y + size * 0.78 -- Tight leading, as in the mockup: the numerals nearly touch. local y2 = y1 + size * 0.92 @@ -28,16 +46,19 @@ function M.draw(cr, rect, colors) card.text(cr, x, y1, os.date('%H')) card.text(cr, x, y2, os.date('%M')) - -- Weekday, small caps. - card.font(cr, card.FONT_UI, 15, true) + -- Weekday and date sit against the bottom edge, not at a fixed drop from the + -- numerals. That is what puts the slack of a tall cell in the middle of the + -- card instead of leaving a dead band under the date. + local dy = bottom - date_size * 0.3 + local wy = dy - date_size * 1.8 + + card.font(cr, card.FONT_UI, day_size, true) card.rgba(cr, colors.body) - local wy = y2 + 34 card.text(cr, x, wy, os.date('%A'):upper()) -- Date as "16 / SEP / 2026". The slashes take the dimmer label colour so the -- numerals read first, which is what gives the mockup's date line its rhythm. - card.font(cr, card.FONT_UI, 14, true) - local dy = wy + 24 + card.font(cr, card.FONT_UI, date_size, true) local parts = { { os.date('%d'), colors.value }, { ' / ', colors.label }, |
