aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md24
-rw-r--r--lib/card.lua28
-rw-r--r--widgets/cache.lua73
-rw-r--r--widgets/gpu.lua48
4 files changed, 132 insertions, 41 deletions
diff --git a/README.md b/README.md
index f8f642f..8c56090 100644
--- a/README.md
+++ b/README.md
@@ -181,11 +181,16 @@ widget code. Three rules, all learned by getting them wrong:
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))`.
+**Fill the cell with type, and shrink only on collision.** Height is what
+changes when you add a row to the grid. Size type off the width alone and a tall
+cell is left sparse; size it off the height alone and a short cell shrinks the
+text to a whisper. The robust way is a measured fit: `card.fit_unit()` returns
+the largest base size at which every row still fits the width, and the widget
+takes the smaller of that and its height budget. Larger cell, larger type; a row
+that would collide pulls the size back down. The GPU and cache cards do this. A
+card that cannot afford the measuring pass can still key off width with a clamp,
+`math.max(lo, math.min(hi, inner.w * k))`; the clock, weather, system and disks
+cards do.
**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
@@ -314,9 +319,12 @@ for nil, string, table, boolean, NaN and negative arguments. It matters because
the parser's input is a file written by a network fetch, and a raise here is a
blank dashboard rather than a message.
-**Card type scales off the cell WIDTH, not its height.** Height changes with the
-grid, and scaling off it made the weather text shrink to a whisper beside the
-clock's numerals when its cell got shorter.
+**Type keyed on one dimension alone leaves the card broken or sparse.** The
+weather text once shrank to a whisper beside the clock when its cell got shorter
+because the size keyed on height; a card keyed only on width leaves a tall cell
+half empty. Fill the cell instead: `card.fit_unit()` measures the rows and
+returns the largest size that fits the width, the widget takes the smaller of
+that and its height budget, and a row that would collide pulls the size down.
**A bare `df` would silently mis-read every disk figure.** The user's shell
aliases `df` to `df -h`, so `bin/disks-sample.sh` calls `/usr/bin/df -P -B1` by
diff --git a/lib/card.lua b/lib/card.lua
index 185703c..c83e6bc 100644
--- a/lib/card.lua
+++ b/lib/card.lua
@@ -205,6 +205,34 @@ function M.ring(cr, cx, cy, r, frac, colour, colors, width)
cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT)
end
+-- The largest base size S at which every row of pieces still fits max_w,
+-- measured at a reference size and scaled.
+--
+-- `groups` is a list of rows; each row is a list of { text, font, factor }
+-- pieces drawn at factor*S on that row. A widget uses this to derive one
+-- fluid size for the whole card: the cell's height decides how large the
+-- content grows, and this pulls S back down only where a row would overrun
+-- the width, so type fills the card instead of leaving empty space and never
+-- collides. Returns math.huge when there is nothing to measure, leaving the
+-- caller's height budget in charge.
+function M.fit_unit(cr, max_w, groups, ref)
+ if not (max_w and max_w > 0) then return math.huge end
+ ref = ref or 100
+ local S = math.huge
+ for _, row in ipairs(groups) do
+ local w = 0
+ for _, piece in ipairs(row) do
+ M.font(cr, piece[2], ref * piece[3], false)
+ w = w + (M.measure(cr, piece[1]))
+ end
+ if w > 0 then
+ local fit = ref * max_w / w
+ if fit < S then S = fit end
+ end
+ end
+ return S
+end
+
-- Bytes to a short human string: 1181116006 -> '1.1G'.
-- The card formats its own numbers because the samplers pass raw bytes.
function M.human(bytes)
diff --git a/widgets/cache.lua b/widgets/cache.lua
index 0209f1a..40025eb 100644
--- a/widgets/cache.lua
+++ b/widgets/cache.lua
@@ -14,32 +14,68 @@ 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_size = clamp(inner.w * 0.036, 10, 16)
- local big_size = clamp(inner.w * 0.095, 22, 46)
-
- card.font(cr, card.FONT_MONO, label_size, false)
- card.rgba(cr, colors.label)
- card.text(cr, inner.x, inner.y + label_size, 'CACHE')
+ -- Fluid type. One base size S drives every element. The height budget makes
+ -- the content fill the cell; the measured width fit pulls S down only where
+ -- the header or a row would overrun the card. A tall cell gets larger type
+ -- instead of empty space, and nothing collides.
+ local LABEL_F, ROW_F = 0.30, 0.32
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.font(cr, card.FONT_UI, label_size * 1.2, true)
- card.text(cr, inner.x, inner.y + label_size * 3.4, 'no cache data')
- card.font(cr, card.FONT_MONO, label_size, false)
- card.text(cr, inner.x, inner.y + label_size * 5, 'bin/cache-sample.sh')
+ card.text(cr, inner.x, inner.y + S, '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')
return
end
- card.font(cr, card.FONT_HEAVY, big_size, false)
- card.rgba(cr, colors.body)
- card.text(cr, inner.x, inner.y + label_size + big_size, c.total.label)
+ local items = c.items or {}
+ local names = {}
+ for i, e in ipairs(items) do
+ local name = e.name
+ if #name > 18 then name = name:sub(1, 17) .. '\u{2026}' end
+ names[i] = name
+ end
- local ey = inner.y + label_size + big_size + label_size * 2
- local step = label_size * 2.6
+ local row_n = #items
+ local S_h = inner.h * 0.94 / (1.78 + row_n * 0.64)
+ local groups = {
+ { { 'CACHE', card.FONT_MONO, LABEL_F }, { c.total.label, card.FONT_HEAVY, 1.0 } },
+ }
+ for i = 1, row_n do
+ groups[#groups + 1] = { { names[i], card.FONT_MONO, ROW_F },
+ { items[i].label, card.FONT_MONO, ROW_F } }
+ end
+ local S = clamp(math.min(S_h, card.fit_unit(cr, inner.w * 0.96, groups, 100)), 10, 72)
+ local label_size = S * LABEL_F
+ local row_size = S * ROW_F
card.font(cr, card.FONT_MONO, label_size, false)
- for i, e in ipairs(c.items) do
+ card.rgba(cr, colors.label)
+ card.text(cr, inner.x, inner.y + S, 'CACHE')
+
+ -- The total shares the header line with the label, set on the same baseline
+ -- and right-aligned: the width carries the number instead of an otherwise
+ -- empty row beneath it.
+ card.font(cr, card.FONT_HEAVY, S, false)
+ card.rgba(cr, colors.body)
+ card.text_right(cr, inner.x + inner.w, inner.y + S, c.total.label)
+
+ local ey = inner.y + S + label_size * 2.6
+ local step = row_size * 2.0
+
+ card.font(cr, card.FONT_MONO, row_size, false)
+ for i, e in ipairs(items) do
if ey + step > inner.y + inner.h then break end
-- The biggest offender is the point of the card, so it carries colour:
-- critical when it is over half the total, warning otherwise. The rest
@@ -50,10 +86,7 @@ function M.draw(cr, rect, colors)
colour = share > 0.5 and colors.critical or colors.warning
end
card.rgba(cr, colors.label)
- -- Long names are truncated rather than allowed to collide with the size.
- local name = e.name
- if #name > 18 then name = name:sub(1, 17) .. '\u{2026}' end
- card.text(cr, inner.x, ey, name)
+ card.text(cr, inner.x, ey, names[i])
card.rgba(cr, colour)
card.text_right(cr, inner.x + inner.w, ey, e.label)
ey = ey + step
diff --git a/widgets/gpu.lua b/widgets/gpu.lua
index 87e22f3..7933e3b 100644
--- a/widgets/gpu.lua
+++ b/widgets/gpu.lua
@@ -24,26 +24,48 @@ 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_size = clamp(inner.w * 0.030, 11, 18)
- local big_size = clamp(inner.w * 0.085, 26, 56)
- local row_size = clamp(inner.w * 0.030, 11, 17)
+ local pkg = data.sensor('xe', 'temp2_input')
+ local big_txt = pkg and string.format('%d\u{00B0}', pkg) or '--'
+
+ -- Fluid type. One base size S drives every element. The cell's height sets
+ -- how large the content grows; the measured width fit pulls S back down
+ -- only where the header or a row would overrun the card. So a tall cell
+ -- gets larger type instead of empty space, and nothing ever collides.
+ local LABEL_F, ROW_F = 0.32, 0.34
+ local row_n = #TEMPS + 1 -- VRAM + FAN
+ local S_h = inner.h * 0.94 / (2.088 + row_n * 0.646)
+ local groups = {
+ { { 'GPU', card.FONT_MONO, LABEL_F }, { big_txt, card.FONT_HEAVY, 1.0 } },
+ { { 'ARC B580', card.FONT_MONO, LABEL_F } },
+ }
+ for _, t in ipairs(TEMPS) do
+ groups[#groups + 1] = { { t[3], card.FONT_MONO, ROW_F },
+ { '888\u{00B0}', card.FONT_MONO, ROW_F } }
+ end
+ groups[#groups + 1] = { { 'FAN', card.FONT_MONO, ROW_F },
+ { '8888 RPM', card.FONT_MONO, ROW_F } }
+ local S = clamp(math.min(S_h, card.fit_unit(cr, inner.w * 0.96, groups, 100)), 10, 72)
+ local label_size = S * LABEL_F
+ local row_size = S * ROW_F
local y = inner.y
+ -- The package temperature is the headline, since there is no load to show.
+ -- It shares the header line with the label, set on the same baseline and
+ -- right-aligned, so the card's width carries the number rather than leaving
+ -- the right half empty under a left-set value.
+ card.font(cr, card.FONT_HEAVY, S, false)
+ card.rgba(cr, pkg and card.threshold(pkg, 75, 85, colors) or colors.label)
+ card.text_right(cr, inner.x + inner.w, y + S, big_txt)
+
card.font(cr, card.FONT_MONO, label_size, false)
card.rgba(cr, colors.label)
- card.text(cr, inner.x, y + label_size, 'GPU')
- card.rgba(cr, colors.value)
- card.text_right(cr, inner.x + inner.w, y + label_size, 'ARC B580')
+ card.text(cr, inner.x, y + S, 'GPU')
- -- The package temperature is the headline, since there is no load to show.
- local pkg = data.sensor('xe', 'temp2_input')
- card.font(cr, card.FONT_HEAVY, big_size, false)
- card.rgba(cr, pkg and card.threshold(pkg, 75, 85, colors) or colors.label)
- card.text(cr, inner.x, y + label_size + big_size,
- pkg and string.format('%d\u{00B0}', pkg) or '--')
+ card.rgba(cr, colors.value)
+ card.text(cr, inner.x, y + S + label_size * 1.7, 'ARC B580')
- local ey = y + label_size + big_size + 18
+ local ey = y + S + label_size * 3.4
local step = row_size * 1.9
card.font(cr, card.FONT_MONO, row_size, false)