max / makeover-layout
git clone https://makenot.work/git/max/makeover-layout.git
git clone git@ssh.makenot.work:max/makeover-layout.git
| Name | Size | |
|---|---|---|
| dist/ | ||
| scripts/ | ||
| src/ | ||
| .gitignore | 99 B | |
| bento.toml | 333 B | |
| build.rs | 651 B | |
| Cargo.toml | 1.8 KB | |
| LICENSE | 1.0 KB | |
| README.md | 3.3 KB | |
| rust-toolchain.toml | 86 B |
README
makeover-layout
The renderer-agnostic half of the make-family design system.
makeover answers what colour, and varies by theme. makeover-geometry
answers how much space, and varies by density and surface. This crate answers
what the thing is, and varies by nothing.
The deferral rule
A description names intents and relationships, never values. Say Fill::Raised,
never #D9DDF4. Say Gap::Peer, never 6px. What is left once colour and
spacing are deferred is composition: which edges are lit, what inverts on press,
what nests in what.
The constraint that shapes it: a renderer that can only paint rectangles has to
be able to express the result. egui has no box-shadow: inset and one stroke per
widget with no per-side control. A terminal has box-drawing characters and one
cell of resolution, and cannot draw a two-tone lit edge at all. So this crate
names the intent, and each renderer picks an expression it can produce, including
dropping half of one.
What is here
Depth: Bevel, Edge, Fill, Depth. Fill and bevel are named together,
because naming them apart lets them disagree: a region can carry a raised bevel
over a recessed fill. Depth makes that unrepresentable.
Components: Token (a badge does not answer a click, a chip does), Notice
(a toast is transient, a banner is persistent), RowPart, Heading, Selector,
Readiness, and Tone, the one intent family they share.
Schemas: Field for forms, Column for lists and tables. A column carries a
drop priority, which is what replaces addressing columns by position. Addressing columns by
nth-child means inserting a column silently hides the wrong one on a narrow
screen.
Structure: Region for the parts of a screen, Arrangement for how a screen
is put together. Two arrangements, because the two apps have two. Showing says
how many of a region’s children are visible at once, which is what lets a
renderer derive a tab strip or a prev/next row for itself instead of recognising
a widget by name.
The vocabulary was read off audiofiles, the only implementation written by a consumer with no CSS, then checked against both webview apps.
What is not here, on purpose
Validation. Field describes fields, not constraints. A schema that describes
fields without constraints acquires a constraint layer per app, which is the
divergence this crate exists to end.
Where the description stops
The bespoke widgets: a day-plan timeline, a kanban board, a calendar. A description expressive enough to produce those is not a description any more, it is a widget library with one app on top. Generate the boring 80% so the bespoke 20% gets the attention.
Region::Bespoke states that limit instead of hiding it. The description names
the place, the app owns the contents, and a screen with a timeline in it is still
a whole screen. Without that member the description would cover only the boring
screens, and the interesting ones would need a second path beside the router.
One opaque member, and Region::described() is how a renderer tells it from the
rest.
Status
On crates.io. A vocabulary correction ships as a major bump with a migration rather than as an edit.
Design lives in the wiki note makeover-layout; the backlog is in GoingsOn under
the project of the same name.
Licence
MIT.