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, State. 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), Act, Prominence, Flow,
Nesting, Heading, Selector, Facet, Fact, Readiness, and Tone, the
one intent family they share.
Schemas: Field and Choice for forms, Column for lists and tables. A
column carries a drop priority, which replaces addressing columns by position:
addressing by nth-child means inserting a column silently hides the wrong one
on a narrow screen. budget and cutoff_for are the one narrowing rule every
native renderer applies, and Sizing holds a renderer's lengths per column.
Figures: Meter, Figure, Chart, Bar, and Track with its Span and
Placement for anything laid on a time axis.
Structure: Region for the parts of a screen, Arrangement for how a
screen is put together (list-detail, sidebar plus content, or a single region),
Share for how much room the first region takes, and Measure for how wide
content runs. Showing says how many of a region's children are visible at
once, which lets a renderer derive a tab strip or a prev/next row itself rather
than recognising a widget by name. Window and Paging carry where a reader is
in a sequence.
Adaptation: Affordance says whether a hover state, a keyboard hint, a
side panel, a swipe or a haptic exists at a given input class and window size
class, read against makeover-geometry's Density and SizeClass. It names
what appears and disappears, never how much space it takes. This was the
makeover-touch crate, which is retired; take Affordance from here.
The vocabulary was read off audiofiles, the only implementation written by a consumer with no CSS, then checked against the 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::Handover and Region::Ceded state 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. A handover is a place the app fills and
a renderer must say so when it does not; a ceded region owes nothing.
Region::described() is how a renderer tells either from the rest.
Status
On crates.io. Every public type is #[non_exhaustive] and built through
constructors and setters, so adding a member is a minor release; removing or
changing one is a major bump with a migration.
Design lives in the wiki note makeover-layout; the backlog is in GoingsOn under
the project of the same name.
Licence
MIT.