aboutsummaryrefslogtreecommitdiffstats
path: root/widgets/clock.lua
blob: ba8f37ad5937c966f0231d9a56e6e7c1f4eada65 (plain)
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