diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-18 18:41:31 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-18 18:41:31 +0200 |
| commit | 7fddddcd39bb250cd2653e130c8f7899d2d3f19c (patch) | |
| tree | ca427df07676563853fb7f25f6ce4eb14e63a25b | |
| parent | 37cabe2fea2f86d481516aae6ddaeb14bac2e64f (diff) | |
| download | conky-theme-udt-7fddddcd39bb250cd2653e130c8f7899d2d3f19c.tar.gz conky-theme-udt-7fddddcd39bb250cd2653e130c8f7899d2d3f19c.zip | |
refactor: unify card no-data face and fluid sizing
Extract card.notice so every cached card's no-data state is one fluid,
theme-consistent face instead of a copy per widget, and delete the
fixed-size card.label it replaces.
Derive weather's arc geometry from its band (pad, curve room, sun dot,
time labels) instead of fixed pixels, and cap the arc at a proportion of
the card's width rather than 240px.
Derive disks' bar-fallback height and its address type from the card's
own label scale.
Standardise the big-value factor to 1.0 across the fit_unit cards and
document the big-value colour rule and the disks headline exception.
| -rw-r--r-- | DESIGN.md | 8 | ||||
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | dashboard.lua | 4 | ||||
| -rw-r--r-- | lib/card.lua | 28 | ||||
| -rw-r--r-- | widgets/breaktimer.lua | 16 | ||||
| -rw-r--r-- | widgets/cache.lua | 15 | ||||
| -rw-r--r-- | widgets/calendar.lua | 20 | ||||
| -rw-r--r-- | widgets/disks.lua | 21 | ||||
| -rw-r--r-- | widgets/network.lua | 2 | ||||
| -rw-r--r-- | widgets/slackware.lua | 16 | ||||
| -rw-r--r-- | widgets/weather.lua | 46 |
11 files changed, 77 insertions, 105 deletions
@@ -11,7 +11,9 @@ bottom: - **Header.** The title, or the glyph that stands in for it, sits top-left in the `label` colour. The card's **big number** sits at the right edge, on the - same line, in `FONT_HEAVY`. Nothing else shares that line. + same line, in `FONT_HEAVY`. Nothing else shares that line. A card whose data + lives entirely in its variable band drops the big number: `disks` has no + headline, the rings are the value. - **Body.** Rows follow one pattern: **label left, value right** (`card.text` and `card.text_right`), so the eye runs down the right edge for numbers. A row's value takes the `threshold` colour when it is a measured @@ -62,6 +64,10 @@ One language for every measured quantity, so state is read rather than decoded: widget. - `warning` is the palette's middle role. The board should show all three states in normal use, or the language is not earning its place. +- The big value's colour follows what it is: `body` for a plain number, the + threshold roles when it is a measured quantity, the phase's own role in + `breaktimer`, and `heading` when it names rather than measures (the calendar + month). ## Glyphs @@ -260,8 +260,10 @@ carries the primitives it should use rather than reaching for Cairo directly: - `card(cr, rect, colors)` draws the rounded card and returns the padded inner rect to lay out against. - `font(cr, family, size, bold)` and `rgba(cr, colour)` set the state. -- `text(cr, x, y, s)` draws at a baseline, `text_right(cr, x, y, s)` aligns to a - right edge, `label(cr, x, y, s, colors)` draws a small-caps section label. +- `text(cr, x, y, s)` draws at a baseline and `text_right(cr, x, y, s)` aligns + to a right edge. +- `notice(cr, inner, colors, title, note, hint)` draws the no-data face every + cached card shares when its sampler has not run. - `measure(cr, s)` returns the **ink** size, what the glyphs actually cover. Use it to centre or right-align. - `advance(cr, s)` returns the **x_advance**, how far the cursor moves after diff --git a/dashboard.lua b/dashboard.lua index b9316bb..95b3f79 100644 --- a/dashboard.lua +++ b/dashboard.lua @@ -155,7 +155,9 @@ local function draw(cr, sw, sh, colors) -- Name the missing widget in place, rather than leaving a blank cell. local r = rect_for(entry, sw, sh) local inner = card.card(cr, r, colors) - card.label(cr, inner.x, inner.y + 16, 'missing: ' .. entry.widget, colors) + card.font(cr, card.FONT_MONO, 16, false) + card.rgba(cr, colors.label) + card.text(cr, inner.x, inner.y + 16, ('missing: ' .. entry.widget):upper()) end end end diff --git a/lib/card.lua b/lib/card.lua index 337347e..3c520b9 100644 --- a/lib/card.lua +++ b/lib/card.lua @@ -5,6 +5,8 @@ local M = {} +local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end + -- Cairo's font API tops out at CAIRO_FONT_WEIGHT_BOLD, so a Black face cannot -- be requested by weight. Passing the family name that fontconfig registers for -- it ("Noto Sans Black") with NORMAL weight does select it: measured at size @@ -116,11 +118,29 @@ function M.card(cr, rect, colors, pad) w = rect.w - pad * 2, h = rect.h - pad * 2 } end --- A small-caps section label, as used across both mockups. -function M.label(cr, x, y, s, colors) - M.font(cr, M.FONT_MONO, 13, false) +-- The no-data face: the card chrome with a named message in it. +-- +-- Every cached card draws this when its sampler has not run, so the board keeps +-- its shape instead of showing an empty cell, which is indistinguishable from a +-- crashed widget. One helper rather than a copy per card: the same three lines +-- at the same ratios in the same colours is what makes the no-data face read the +-- same everywhere, and the fit_unit pass sizes all three off the rect exactly as +-- a populated card would. +function M.notice(cr, inner, colors, title, note, hint) + local LABEL_F, NOTE_F, HINT_F = 0.30, 0.36, 0.32 + local S = clamp(inner.h * 0.94 / (1.78 + 2 * 0.72), 10, 72) + S = math.min(S, M.fit_unit(cr, inner.w * 0.96, { + { { title, M.FONT_MONO, LABEL_F } }, + { { note, M.FONT_UI, NOTE_F } }, + { { hint, M.FONT_MONO, HINT_F } }, + }, 100)) + M.font(cr, M.FONT_MONO, S * LABEL_F, false) M.rgba(cr, colors.label) - M.text(cr, x, y, s:upper()) + M.text(cr, inner.x, inner.y + S * LABEL_F, title) + M.font(cr, M.FONT_UI, S * NOTE_F, true) + M.text(cr, inner.x, inner.y + S + S * LABEL_F * 2.6, note) + M.font(cr, M.FONT_MONO, S * HINT_F, false) + M.text(cr, inner.x, inner.y + S + S * LABEL_F * 2.6 + S * 0.72, hint) end -- Colour for a value against two thresholds. diff --git a/widgets/breaktimer.lua b/widgets/breaktimer.lua index 698be80..e6487b8 100644 --- a/widgets/breaktimer.lua +++ b/widgets/breaktimer.lua @@ -21,7 +21,7 @@ local M = {} function M.draw(cr, rect, colors) local inner = card.card(cr, rect, colors) local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end - local LABEL_F, BIG_F, ROW_F = 0.30, 0.85, 0.32 + local LABEL_F, BIG_F, ROW_F = 0.30, 1.0, 0.32 local bt = data.breaktimer_parse(data.slurp(CACHE) or '') @@ -30,19 +30,7 @@ function M.draw(cr, rect, colors) -- crash. A stopped daemon is NOT this case: the sampler still writes a cache, -- with running no, which the card draws as 'fermo'. if not bt then - local S = clamp(inner.h * 0.94 / (1.78 + 2 * 0.72), 10, 72) - S = math.min(S, card.fit_unit(cr, inner.w * 0.96, { - { { 'BREAKTIMER', card.FONT_MONO, LABEL_F } }, - { { 'no breaktimer data', card.FONT_UI, 0.36 } }, - { { 'bin/breaktimer-sample.sh', card.FONT_MONO, ROW_F } }, - }, 100)) - card.font(cr, card.FONT_MONO, S * LABEL_F, false) - card.rgba(cr, colors.label) - card.text(cr, inner.x, inner.y + S * LABEL_F, 'BREAKTIMER') - card.font(cr, card.FONT_UI, S * 0.36, true) - card.text(cr, inner.x, inner.y + S + S * LABEL_F * 2.6, 'no breaktimer data') - card.font(cr, card.FONT_MONO, S * ROW_F, false) - card.text(cr, inner.x, inner.y + S + S * LABEL_F * 2.6 + S * 0.72, + card.notice(cr, inner, colors, 'BREAKTIMER', 'no breaktimer data', 'bin/breaktimer-sample.sh') return end diff --git a/widgets/cache.lua b/widgets/cache.lua index af20347..3772458 100644 --- a/widgets/cache.lua +++ b/widgets/cache.lua @@ -22,20 +22,7 @@ function M.draw(cr, rect, colors) local c = data.du_parse(data.slurp(CACHE) or '') if not c then - local S = clamp(inner.h * 0.94 / (1.78 + 2 * 0.72), 10, 72) - S = math.min(S, card.fit_unit(cr, inner.w * 0.96, { - { { 'CACHE', card.FONT_MONO, LABEL_F } }, - { { 'no cache data', card.FONT_UI, 0.36 } }, - { { 'bin/cache-sample.sh', card.FONT_MONO, ROW_F } }, - }, 100)) - card.font(cr, card.FONT_MONO, S * LABEL_F, false) - card.rgba(cr, colors.label) - card.text(cr, inner.x, inner.y + S * LABEL_F, 'CACHE') - card.font(cr, card.FONT_UI, S * 0.36, true) - card.text(cr, inner.x, inner.y + S + S * LABEL_F * 2.6, 'no cache data') - card.font(cr, card.FONT_MONO, S * ROW_F, false) - card.text(cr, inner.x, inner.y + S + S * LABEL_F * 2.6 + S * 0.72, - 'bin/cache-sample.sh') + card.notice(cr, inner, colors, 'CACHE', 'no cache data', 'bin/cache-sample.sh') return end diff --git a/widgets/calendar.lua b/widgets/calendar.lua index 6194447..b31e3b2 100644 --- a/widgets/calendar.lua +++ b/widgets/calendar.lua @@ -52,24 +52,6 @@ local function when(e) return string.format('%02d/%02d %s', e.d, e.m, e.start or 'all day') end --- The failure face: the sampler has not run. Named, as the other cached cards --- do, rather than a blank cell that cannot be told from a crashed widget. -local function draw_empty(cr, inner, colors, label_f, note, hint) - local S = clamp(inner.h * 0.94 / (1.78 + 2 * 0.72), 10, 72) - S = math.min(S, card.fit_unit(cr, inner.w * 0.96, { - { { 'CALENDAR', card.FONT_MONO, label_f } }, - { { note, card.FONT_UI, 0.36 } }, - { { hint, card.FONT_MONO, 0.32 } }, - }, 100)) - card.font(cr, card.FONT_MONO, S * label_f, false) - card.rgba(cr, colors.label) - card.text(cr, inner.x, inner.y + S * label_f, 'CALENDAR') - card.font(cr, card.FONT_UI, S * 0.36, true) - card.text(cr, inner.x, inner.y + S + S * label_f * 2.6, note) - card.font(cr, card.FONT_MONO, S * 0.32, false) - card.text(cr, inner.x, inner.y + S + S * label_f * 2.6 + S * 0.72, hint) -end - -- The legend: one coloured dot and name per calendar, along the card's bottom. -- Wraps to a second line when the names do not fit, and drops any that still -- do not: a legend spilling off the card is worse than a short one, since the @@ -103,7 +85,7 @@ function M.draw(cr, rect, colors) local cal = data.calendar_parse(data.slurp(CACHE) or '') if not cal then - draw_empty(cr, inner, colors, LABEL_F, 'no calendar data', + card.notice(cr, inner, colors, 'CALENDAR', 'no calendar data', 'bin/calendar-sample.sh') return end diff --git a/widgets/disks.lua b/widgets/disks.lua index 3b0f221..fa97b50 100644 --- a/widgets/disks.lua +++ b/widgets/disks.lua @@ -43,20 +43,16 @@ function M.draw(cr, rect, colors) local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end local label_size = clamp(inner.w * 0.030, 10, 16) - card.font(cr, card.FONT_MONO, label_size, false) - card.rgba(cr, colors.label) - card.text(cr, inner.x, inner.y + label_size, 'DISKS') - local fs = data.df_parse(data.slurp(CACHE) or '') if #fs == 0 then - card.rgba(cr, colors.label) - card.font(cr, card.FONT_UI, label_size * 1.2, true) - card.text(cr, inner.x, inner.y + label_size * 3.4, 'no disk data') - card.font(cr, card.FONT_MONO, label_size, false) - card.text(cr, inner.x, inner.y + label_size * 5, 'bin/disks-sample.sh') + card.notice(cr, inner, colors, 'DISKS', 'no disk data', 'bin/disks-sample.sh') return end + card.font(cr, card.FONT_MONO, label_size, false) + card.rgba(cr, colors.label) + card.text(cr, inner.x, inner.y + label_size, 'DISKS') + local top = inner.y + label_size * 2.2 local avail_h = (inner.y + inner.h) - top @@ -100,7 +96,9 @@ function M.draw(cr, rect, colors) -- The address, inside the ring beneath the glyph. Only the NFS shares -- have one; the local mounts are identified by their icon alone. if sub then - card.font(cr, card.FONT_MONO, clamp(r * 0.20, 9, 12), false) + -- Tied to the card's own label scale rather than a fixed px floor, so + -- every piece of small mono text on the card tracks the same size. + card.font(cr, card.FONT_MONO, clamp(r * 0.20, label_size * 0.7, label_size), false) card.rgba(cr, colors.label) local sw_ = card.measure(cr, sub) card.text(cr, cx - sw_ / 2, cy + r * 0.55, sub) @@ -129,6 +127,7 @@ function M.draw(cr, rect, colors) end else local step = label_size * 2.6 + local bar_h = clamp(label_size * 0.5, 4, 8) local ey = top + label_size for _, e in ipairs(fs) do if ey + step > inner.y + inner.h then break end @@ -143,7 +142,7 @@ function M.draw(cr, rect, colors) card.rgba(cr, colour) card.text_right(cr, inner.x + inner.w, ey, string.format('%s / %s', card.human(e.used), card.human(e.size))) - card.bar(cr, inner.x, ey + 6, inner.w, 6, e.pct / 100, colour, colors) + card.bar(cr, inner.x, ey + label_size * 0.5, inner.w, bar_h, e.pct / 100, colour, colors) ey = ey + step end end diff --git a/widgets/network.lua b/widgets/network.lua index e0ff4cb..5d1a8ca 100644 --- a/widgets/network.lua +++ b/widgets/network.lua @@ -78,7 +78,7 @@ function M.draw(cr, rect, colors) -- Fluid type, as every other card does: one base size S drives everything, -- taken as the smaller of a height budget and a measured width fit. - local LABEL_F, BIG_F, ROW_F = 0.30, 0.62, 0.30 + local LABEL_F, BIG_F, ROW_F = 0.30, 1.0, 0.30 local groups = { { { 'NET', card.FONT_MONO, LABEL_F }, { '888.8M/s', card.FONT_HEAVY, BIG_F } }, { { '\u{F0318} 192.000.000.000', card.FONT_MONO, ROW_F } }, diff --git a/widgets/slackware.lua b/widgets/slackware.lua index 709438d..f9afb70 100644 --- a/widgets/slackware.lua +++ b/widgets/slackware.lua @@ -33,26 +33,14 @@ end function M.draw(cr, rect, colors) local inner = card.card(cr, rect, colors) local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end - local LABEL_F, BIG_F, ROW_F = 0.30, 0.85, 0.32 + local LABEL_F, BIG_F, ROW_F = 0.30, 1.0, 0.32 local kv = data.kv_parse(data.slurp(CACHE) or '') -- No cache at all: the sampler has not run. Name it, as the cache card does, -- rather than leaving a blank cell that is indistinguishable from a crash. if not kv.version then - local S = clamp(inner.h * 0.94 / (1.78 + 2 * 0.72), 10, 72) - S = math.min(S, card.fit_unit(cr, inner.w * 0.96, { - { { 'SLACKWARE', card.FONT_MONO, LABEL_F } }, - { { 'no slackware data', card.FONT_UI, 0.36 } }, - { { 'bin/slackware-sample.sh', card.FONT_MONO, ROW_F } }, - }, 100)) - card.font(cr, card.FONT_MONO, S * LABEL_F, false) - card.rgba(cr, colors.label) - card.text(cr, inner.x, inner.y + S * LABEL_F, 'SLACKWARE') - card.font(cr, card.FONT_UI, S * 0.36, true) - card.text(cr, inner.x, inner.y + S + S * LABEL_F * 2.6, 'no slackware data') - card.font(cr, card.FONT_MONO, S * ROW_F, false) - card.text(cr, inner.x, inner.y + S + S * LABEL_F * 2.6 + S * 0.72, + card.notice(cr, inner, colors, 'SLACKWARE', 'no slackware data', 'bin/slackware-sample.sh') return end diff --git a/widgets/weather.lua b/widgets/weather.lua index f50c6f1..a0c7b3a 100644 --- a/widgets/weather.lua +++ b/widgets/weather.lua @@ -12,34 +12,28 @@ local M = {} local CACHE = (os.getenv('XDG_CACHE_HOME') or (os.getenv('HOME') .. '/.cache')) .. '/udt/weather.json' --- Draw the card chrome with a message in it. Used for every no-data state, so --- the dashboard keeps its shape instead of showing an empty cell, which is --- indistinguishable from a crashed widget. -local function draw_notice(cr, inner, colors, line1, line2) - card.font(cr, card.FONT_UI, 15, true) - card.rgba(cr, colors.label) - card.text(cr, inner.x, inner.y + 24, line1) - card.font(cr, card.FONT_MONO, 12, false) - card.text(cr, inner.x, inner.y + 48, line2) -end - -- The daylight arc: a curve, its baseline, the sun, and the two times. -- -- The dot is placed by evaluating the curve at t rather than by computing a -- point on a circle: the curve IS the path, so evaluating it keeps the dot on -- the arc if the control points are ever adjusted. local function draw_arc(cr, x, y, w, h, colors, w_data, now) - local pad = 4 + -- Every dimension derives from the band, not fixed pixels: the time labels + -- take a fraction of the height, the curve's room is what they leave, and the + -- dot and edge pad follow the same proportions so the arc composes at any + -- size the cell gives it. + local label_h = math.max(10, math.min(16, h * 0.16)) + local pad = math.max(4, w * 0.012) local x0, x1 = x + pad, x + w - pad -- Room under the curve for the time labels. - local base = y + h - 20 - local top = y + 6 + local base = y + h - label_h * 1.5 + local top = y + h * 0.08 -- Cubic Bezier control points, chosen so the curve peaks near the middle at -- about two thirds of the band height. local p0 = { x0, base } - local c1 = { x0 + (x1 - x0) * 0.22, top - 10 } - local c2 = { x1 - (x1 - x0) * 0.22, top - 10 } + local c1 = { x0 + (x1 - x0) * 0.22, top - h * 0.14 } + local c2 = { x1 - (x1 - x0) * 0.22, top - h * 0.14 } local p3 = { x1, base } -- The baseline: the horizon the sun rises from and sets into. @@ -70,13 +64,13 @@ local function draw_arc(cr, x, y, w, h, colors, w_data, now) -- At night the dot is parked at an end, so it takes the dim colour: a bright -- dot sitting on the horizon would read as a sun that is still up. card.rgba(cr, day and colors.body or colors.label, 1) - cairo_arc(cr, sx, sy, 5, 0, math.pi * 2) + cairo_arc(cr, sx, sy, math.max(3, math.min(9, h * 0.045)), 0, math.pi * 2) cairo_fill(cr) -- The times, each behind its own glyph, at the ends of the arc. - card.font(cr, card.FONT_MONO, 12, false) + card.font(cr, card.FONT_MONO, label_h, false) card.rgba(cr, colors.label) - local ty = y + h - 2 + local ty = y + h - label_h * 0.15 card.text(cr, x0, ty, weather.ICON.sunrise .. ' ' .. os.date('%H:%M', w_data.sunrise)) card.text_right(cr, x1, ty, @@ -97,12 +91,13 @@ function M.draw(cr, rect, colors) -- draw (every update_interval), since this whole branch reruns each -- frame until a fetch succeeds. local envf = io.open(os.getenv('HOME') .. '/.config/udt/weather.env', 'r') - if envf then - envf:close() - draw_notice(cr, inner, colors, 'no weather data', 'waiting for first fetch') + local hint = 'waiting for first fetch' + if not envf then + hint = 'set ~/.config/udt/weather.env' else - draw_notice(cr, inner, colors, 'no weather data', 'set ~/.config/udt/weather.env') + envf:close() end + card.notice(cr, inner, colors, 'WEATHER', 'no weather data', hint) return end @@ -194,7 +189,10 @@ function M.draw(cr, rect, colors) -- This is what lets one widget sit in a 2x2 or a 2x3 cell unchanged. local bottom = inner.y + inner.h local arc_y = sy + row_step * 0.6 - local arc_h = math.min(bottom - arc_y, inner.h * 0.34, 240) + -- The cap is a proportion of the card's width, not a fixed 240px: an arch + -- taller than about 0.6 of its own width reads as a cathedral door rather + -- than a day's arc, and the ceiling has to follow a card that is resized. + local arc_h = math.min(bottom - arc_y, inner.h * 0.34, inner.w * 0.6) if arc_h >= 70 then draw_arc(cr, inner.x, arc_y, inner.w, arc_h, colors, w, now) end |
