Counter widget: one to three linked counters are the digits of one number, published on one message in the finest counter's unit (hours and minutes send 07:30 as 450, 07:55 + 5 min carries to 08:00); wraps or stops at the coarsest's min/max. The arithmetic is one pure module, ui/core/counter.ts, with counter.check.ts. A tile steps each counter with a press up and down and publishes once 300 ms after the last; a round face turns the selected counter on the ring (wheel, keys, a drag counted in steps), its arc where that counter stands in its turn, and a press on a counter's digits selects it. Default config is the time picker. Editor rows, widgetIssue, server type and dtype, docs

This commit is contained in:
Melvin Strobl
2026-10-16 08:58:42 +02:00
parent 4ce50b5c3a
commit 21c7961d49
16 changed files with 745 additions and 33 deletions
+63 -7
View File
@@ -59,6 +59,7 @@ still names the widget for accessibility and published values.
| **Input** | text or number | free entry |
| **Selector** | selected value | menu or row of choices |
| **Colour** | list or string | HSV, RGB or hex |
| **Counter** | `int`, `float` | one number set by up to three linked counters, such as hours and minutes; see [A counter](#a-counter) |
Colour defaults to `[h, s, v]`: hue 0–360°, saturation and brightness 0–100%.
**Sends** can select RGB (`[r, g, b]`, 0–255) or hex (`"#rrggbb"`).
@@ -125,6 +126,50 @@ dot that shows on the handle, so a reading at the setpoint is still visible.
| Order | track, dots, fill, the dots again over the fill, handle, the reading again over the handle |
| Locked | in a tile, the dots at 0.45 opacity, as the handle; on the ring, the whole ring at 0.45 |
### A counter
A Counter sets one whole number with up to three linked counters. They are the
digits of that number, counted in the unit of the finest, and the widget
publishes the number on its one message: hours and minutes publish 07:30 as
450, and 07:55 plus five minutes is 08:00.
| Field | Meaning |
|---|---|
| `counters` | 1 to 3 `{label, step, size, min, max}`, coarsest first. Absent or empty reads as the time below |
| `label` | written under the counter's digits |
| `step` | a press or a notch, in the counter's own unit: 1 hour moves the number by 60. Default 1, at least 1 |
| `size` | how many of it make one of the next coarser counter, 60 for minutes. Every counter but the coarsest needs one |
| `min`, `max` | the range, on the coarsest only and in its unit. `min` defaults to 0; no `max` is no upper bound |
| `wrap` | at either end, go round to the other (the default) or stop there |
A new counter is a time of day: `[{label: "h", step: 1, min: 0, max: 23},
{label: "min", step: 5, size: 60}]`, wrapping.
For counters 0 to n − 1, coarsest first, and the number v (`ui/core/counter.ts`,
which the firmware ports as it stands):
| Quantity | Formula |
|---|---|
| Weight wᵢ | the finest 1; each coarser one wᵢ = wᵢ₊₁ × sizeᵢ₊₁ |
| Digit dᵢ | d₀ = ⌊v / w₀⌋; dᵢ = ⌊v / wᵢ⌋ mod sizeᵢ, a remainder that is never negative |
| A step | counter i by k steps (k < 0 down): v + k × stepᵢ × wᵢ, so carry and borrow follow |
| Range | lo = min₀ × w₀. With a `max`, period = (max₀ − min₀ + 1) × w₀ and top = lo + ⌊(period − 1) / s⌋ × s, s the finest counter's step: 0–23 in 5-minute steps tops out at 1435 (23:55), one counter of step 5 up to 100 at 100 |
| Wrap | v = lo + ((v − lo) mod period): 23:55 + 5 min is 00:00, 00:00 − 1 h is 23:00 |
| Stop | v held to lo … top, as a slider is |
| No `max` | no top and nothing to wrap at: v is only held to lo or above |
Written, the digits are joined by `:`, each counter's label under its own.
Every digit but the coarsest is zero-padded to the width of size − 1 (05); the
coarsest to the width of `max` when it has one (07), and a negative one keeps
its sign in front of the padding. The number shown is the message's, held as
sent until it comes back, as a slider's is, and lo until one arrives.
In a tile each counter is a column: a press up, its digits, a press down and
its label, at the control sizes, so touch-friendly where the panel is. A press
moves the number at once and one publish follows 300 ms after the last press,
as a ring's does; holding a press does not repeat it. On a round face see
[Round dashboards](#round-dashboards).
## Charts
Choose the chart's data source:
@@ -243,13 +288,22 @@ whole face, in the order of `widgets`; `layout` is not read. In the editor, the
faces are a row above the dock: press one to show it and open its settings,
drag it along the row (or Alt+←/→) to move it, and add more with **Add widget**.
A Slider, Selector, Colour or Gauge draws on the **ring** along the edge, which
a knob's own ring turns. In a browser, drag along the ring or use the mouse
wheel (or the arrow keys once it has focus): one notch is one step, one option
(no wrap) or one hue step (`hue_step`, default 5°, wrapping). The value moves
at once and is published once, 300 ms after the last notch or when the pointer
is released. A Gauge only shows. On any other face, turning a knob only wakes
the screen.
A Slider, Selector, Colour, Counter or Gauge draws on the **ring** along the
edge, which a knob's own ring turns. In a browser, drag along the ring or use
the mouse wheel (or the arrow keys once it has focus): one notch is one step,
one option (no wrap), one hue step (`hue_step`, default 5°, wrapping) or one
step of the counter selected. The value moves at once and is published once,
300 ms after the last notch or when the pointer is released. A Gauge only
shows. On any other face, turning a knob only wakes the screen.
A Counter's ring turns one of its counters, with carry: the finest, until a
press on another's digits selects that one instead. The selection holds while
the face is shown and is the finest again once the face is left. Dragging
along its ring counts notches rather than picking a place: one per step's
share of the sweep, 300° × step / its turn (size, or max − min + 1 for the
coarsest), so the arc stays under the finger, and one per 15° for a coarsest
without `max`. The angle is counted from the last move the shorter way round,
so a drag carries on through the gap. The face has no up or down presses.
A Slider's reading (**Beside it**) is written large in the middle, under its
label; the slider's own title and value move to the line in the ring's gap.
@@ -295,6 +349,7 @@ look's muted text colour.
| Selector | a value arc from the start to the selected option (none at the first), a dot 6 across per option at i/(n−1) of the sweep (one option: the middle), the selected one under the handle; its label as the centre reading, at most 36 |
| Colour | hue h at h/360 of the sweep in 150 strokes of 2°, red at both ends; the handle filled with the colour; a swatch 96 across in the middle, with a hairline border |
| Gauge | track and value arc, no handle; the value as the centre reading |
| Counter | the digits, joined by `:`, sized as a centre reading of the whole line (07:30 is five characters: 72), each counter's label under its digits at 17, muted, 4 apart; the block centred at (c, c). With two or more counters, the one the ring turns is in `--primary`, digits and label, and pressing a counter's digits selects it. A value arc from the start to where the selected counter stands in its turn: dᵢ / sizeᵢ, as a minute hand stands, the coarsest (d₀ − min) / (max − min + 1); no arc for a coarsest without `max`. No handle and no dots |
| Gap line | centred at y = S − 28, 15, at most 140 wide, clear of the ring's end caps |
| Value | the reading as the centre reading, to its precision; a word shrinks like any other |
| Bar | an arc per row, the first outermost, each over the ring's sweep (start 120°, 300°) with round caps: a track in the look's track paint and the row's value arc in its data colour, on its own scale (none at the minimum). n rows share a band 64 thick inward from c − 12, 3 apart: stroke s = min(16, (64 − 3(n − 1)) / n), centre-line radius rᵢ = c − 12 − s/2 − i(s + 3), so up to three rows are 16 thick and the first is on the ring (c − 20). The middle is the legend, a line per row in order: a dot 8 across in the row's colour, 8, the label (muted, cut with an ellipsis), 8, the value and unit to the row's precision (— for none). Lines of 22 at 17 for up to 5 rows, 18 at 15 for 6–7, 16 at 13 for 8; the block centred at (c, c), as wide as its widest line but at most 240 and at most the width of the disc inside the innermost arc (radius c − 12 − n·s − 3(n − 1)) at the block's top and bottom, less 16. The title is at most that disc's width at the title's line (y = c − 89), less 8 |
@@ -342,6 +397,7 @@ dashboard's look's.
| Ring value set by a notch, a key or the engine | the value arc's end, the handle (slider, selector, colour) | from where it is drawn to the new fraction of the sweep | spring | each notch retargets from where it is, at the speed it has |
| Ring value set by a drag | the same | follows the pointer exactly | none | — |
| Hue across red | the colour handle | a change of more than half the sweep jumps to the other end | none | — |
| Counter across its turn | the value arc | a change of more than half the sweep (a carry, a wrap, another counter selected) jumps | none | — |
| Slider's reading on the ring, gauge on the ring | the reading's dot; the value arc | from where it is drawn to the new fraction; a dot's colour follows the fill and the handle as they pass | spring | retargets |
| Numbers on the ring | centre reading, gap line | switch at once, with the notch | none | — |
| Value face, Value and Gauge tiles | the number | counts to the new value, written to its precision | spring | retargets |