aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-18 18:41:31 +0200
committerDanilo M. <danix@danix.xyz>2026-09-18 18:41:31 +0200
commit7fddddcd39bb250cd2653e130c8f7899d2d3f19c (patch)
treeca427df07676563853fb7f25f6ce4eb14e63a25b
parent37cabe2fea2f86d481516aae6ddaeb14bac2e64f (diff)
downloadconky-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.md8
-rw-r--r--README.md6
-rw-r--r--dashboard.lua4
-rw-r--r--lib/card.lua28
-rw-r--r--widgets/breaktimer.lua16
-rw-r--r--widgets/cache.lua15
-rw-r--r--widgets/calendar.lua20
-rw-r--r--widgets/disks.lua21
-rw-r--r--widgets/network.lua2
-rw-r--r--widgets/slackware.lua16
-rw-r--r--widgets/weather.lua46
11 files changed, 77 insertions, 105 deletions
diff --git a/DESIGN.md b/DESIGN.md
index 2249fac..34e8d95 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -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
diff --git a/README.md b/README.md
index 5ec6b61..2269a70 100644
--- a/README.md
+++ b/README.md
@@ -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