| 1 |
# makeover-geometry |
| 2 |
|
| 3 |
The invariant half of the make-family design system. [`makeover`](https://makenot.work/git/max/makeover) |
| 4 |
resolves colour, which varies by theme; this crate carries everything that does |
| 5 |
not. |
| 6 |
|
| 7 |
## Spacing is a relationship, not a size |
| 8 |
|
| 9 |
The Mac OS 8 Human Interface Guidelines specify white space by what two things |
| 10 |
are being separated, and define no grid unit. A control and its satellite |
| 11 |
pop-up sit 4 pixels apart; peers stacked in a list get 6; a group box's inner |
| 12 |
margin is 10; separated groups and rows of push buttons get 12. |
| 13 |
|
| 14 |
So the vocabulary here names the relationship and lets the size follow, the same |
| 15 |
way `surface-raised` names an intent and lets the hex follow: |
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
| `gap-bound` | a control and the thing it belongs to | |
| 20 |
| `gap-peer` | items of the same kind in a list | |
| 21 |
| `gap-group` | a container's inner margin, sibling groups | |
| 22 |
| `gap-section` | separated groups, rows of actions | |
| 23 |
| `gap-pane` | panel padding, content shells | |
| 24 |
| `gap-page` | the outermost shell margin | |
| 25 |
|
| 26 |
Whether a gap should be 6px or 8px is unanswerable on its own. Whether two |
| 27 |
things are peers is not. That is the whole argument for the layer. |
| 28 |
|
| 29 |
A raw `step-*` scale sits underneath for distances no relationship describes, |
| 30 |
such as an optical nudge inside a badge. Reaching for it is a smell worth a |
| 31 |
second look. |
| 32 |
|
| 33 |
## Ratios, not pixel counts |
| 34 |
|
| 35 |
The deliberate departure from the HIG, which is written in hard device pixels |
| 36 |
because in 1997 there was one pixel density and one text size. |
| 37 |
|
| 38 |
Every step is a ratio of one base unit, `--geometry-base`, which defaults to |
| 39 |
`1rem`. At that base the ratios land exactly on the HIG's numbers, so nothing |
| 40 |
is lost in translation. What is gained: the layout tracks the user's text size |
| 41 |
instead of fighting it, an accessibility setting is one value rather than a |
| 42 |
sweep, and the scale means the same thing at any display density. |
| 43 |
|
| 44 |
```css |
| 45 |
--step-snug: calc(var(--geometry-base) * 3 / 8); /* 6px at the default base */ |
| 46 |
--gap-peer: var(--step-snug); |
| 47 |
``` |
| 48 |
|
| 49 |
## Density presets |
| 50 |
|
| 51 |
Because no call site names a size, a preset can change what every relationship |
| 52 |
resolves to without touching one of them. Mobile and desktop builds should |
| 53 |
differ mostly by which preset they emit. |
| 54 |
|
| 55 |
The presets are not scaled copies of each other, which is also why a single |
| 56 |
base scalar could not express this. Touch is a claim about the contact patch |
| 57 |
and nothing else: a fingertip is coarse, so adjacent things that do different |
| 58 |
things need more room between them to be hit reliably. |
| 59 |
|
| 60 |
|
| 61 |
|
| 62 |
| bound | 4 | 4 | |
| 63 |
| peer | 6 | 10 | |
| 64 |
| group | 10 | 12 | |
| 65 |
| section | 12 | 16 | |
| 66 |
| pane | 24 | 24 | |
| 67 |
| page | 32 | 32 | |
| 68 |
|
| 69 |
`gap-peer`, `gap-group` and `gap-section` separate distinct tap targets, so |
| 70 |
they open. `gap-pane` and `gap-page` are shells rather than targets, so the |
| 71 |
input device has no opinion about them and they hold. How much screen you have |
| 72 |
is a separate question from what you are pointing with, and it does not belong |
| 73 |
on this axis. `gap-bound` never moves: it is the one relationship that says |
| 74 |
"these are one object", and separating it would say the opposite. |
| 75 |
|
| 76 |
The one rule across presets is that Touch never resolves tighter than Pointer. |
| 77 |
|
| 78 |
```rust |
| 79 |
use makeover_geometry::{Density, geometry_css_vars, gap_css_overrides}; |
| 80 |
|
| 81 |
let mut css = geometry_css_vars(Density::Pointer); |
| 82 |
css.push_str(&gap_css_overrides(".ui-mode-mobile", Density::Touch)); |
| 83 |
``` |
| 84 |
|
| 85 |
The override block emits only the relational layer. The base and the scale are |
| 86 |
declared once. |
| 87 |
|
| 88 |
## Surfaces, and why a terminal is not a third preset |
| 89 |
|
| 90 |
A terminal is not a density. It is a surface whose smallest representable step |
| 91 |
is one cell rather than one pixel, and that single difference is the whole of |
| 92 |
it. `Ratio::quanta` takes a quantum and answers in whole units of it, so the |
| 93 |
relational vocabulary reaches a character grid with nothing added. |
| 94 |
|
| 95 |
Quantising is not a terminal special case either. A display quantises to the |
| 96 |
pixel; it is only that rounding 6.0 to the nearest pixel is uninteresting, while |
| 97 |
rounding three eighths of a cell to the nearest cell decides the layout. |
| 98 |
|
| 99 |
```rust |
| 100 |
use makeover_geometry::{Density, Gap, Surface}; |
| 101 |
|
| 102 |
let t = Surface::terminal(); |
| 103 |
assert_eq!(t.gap(Gap::Peer, Density::Pointer), 0); // cells |
| 104 |
assert_eq!(t.gap(Gap::Section, Density::Pointer), 1); |
| 105 |
assert_eq!(t.gap(Gap::Page, Density::Pointer), 2); |
| 106 |
``` |
| 107 |
|
| 108 |
`bound` and `peer` collapsing to zero cells is correct rather than lossy: in a |
| 109 |
grid that dense, both relationships are expressed by adjacency. A coarse surface |
| 110 |
genuinely has fewer distinctions available, and the model should say so instead |
| 111 |
of inventing a gap to keep six names apart. What it must never do is *invert* |
| 112 |
two relationships, and there is a test across several quanta pinning that. |
| 113 |
|
| 114 |
So three orthogonal axes: `Gap` is what is being separated, `Density` is who is |
| 115 |
operating it, `Surface` is what it is drawn on. |
| 116 |
|
| 117 |
## Consumers |
| 118 |
|
| 119 |
Web surfaces bake the CSS in at build time; unlike colour, none of this changes |
| 120 |
at runtime, so there is nothing for JS to apply on load. egui and ratatui |
| 121 |
surfaces resolve through `Surface` instead, which is the reason this is a crate |
| 122 |
rather than a stylesheet. |
| 123 |
|
| 124 |
## Licence |
| 125 |
|
| 126 |
MIT. |
| 127 |
|