1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
-- 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
-- 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.
--
-- The divisor is derived, not tuned: the first baseline sits at 0.78 of the
-- size (cap height) and the second a further 0.92 below it, so the pair
-- occupies size * 1.70. Dividing the available height by that is what makes
-- the numerals actually reach the date band instead of leaving a gap; the
-- earlier flat 0.46 left a fifth of the band empty.
--
-- 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.
-- The width cap is the font's, measured rather than guessed: two Oswald
-- digits are 1.00x the font size wide (measured at size 100: '00' is 100.0px,
-- the widest pair '88' is 98.0px), so the size a width w permits is w itself,
-- less a little breathing room at the card edges.
local avail = (bottom - date_band) - inner.y
local size = math.min(avail / 1.70, inner.w * 0.92)
-- A narrow tall cell is width-bound, which leaves the numerals floating well
-- above the date. Centre the pair in the space they cannot fill instead of
-- hanging them from the top, so the slack sits above and below them equally.
local slack = avail - size * 1.70
if slack < 0 then slack = 0 end
card.font(cr, card.FONT_CLOCK, size, true)
card.rgba(cr, colors.body)
-- 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 y1 = inner.y + slack / 2 + size * 0.78
-- Tight leading, as in the mockup: the numerals nearly touch.
local y2 = y1 + size * 0.92
-- Centred horizontally: measure(), not advance(), because ink width is what
-- centring wants. Oswald is condensed, so '10' and '03' differ in width and
-- each line is centred on its own measurement rather than a shared one.
local function centred(s, baseline)
local w = card.measure(cr, s)
card.text(cr, inner.x + (inner.w - w) / 2, baseline, s)
end
centred(os.date('%H'), y1)
centred(os.date('%M'), y2)
-- 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)
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, date_size, true)
local parts = {
{ os.date('%d'), colors.value },
{ ' / ', colors.label },
{ os.date('%b'):upper(), colors.value },
{ ' / ', colors.label },
{ os.date('%Y'), colors.value },
}
local dx = x
for _, p in ipairs(parts) do
card.rgba(cr, p[2])
card.text(cr, dx, dy, p[1])
-- advance(), not measure(): the ink width of ' / ' omits its spaces.
dx = dx + card.advance(cr, p[1])
end
end
return M
|