Skip to main content

Crate makeover_layout

Crate makeover_layout 

Source
Expand description

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 all of 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. A description that assumes per-side edges is a CSS description wearing a neutral name. So this crate names the intent — this region is a well — and each renderer chooses an expression it can actually produce, including dropping half of one.

§Scope

Depth came first: the bevel and the surfaces it shapes. That much was settled the hard way — the vocabulary here was read off audiofiles’ ui::theme and ui::widgets, which are the only implementation written by a consumer with no CSS, then checked against both webview apps. All three agreed once Balanced Breakfast’s fills were corrected.

0.2.0 adds the rest of the description, each member drawn the same way, from what the three apps already hand-write rather than from a taxonomy:

Validation is absent on purpose rather than pending: neither app has a shared story, and a schema describing fields but not constraints acquires a constraint layer per app, which is how the divergence this crate exists to end got started.

0.3.0 closes a gap the first real adoption found, which is what adopting against goingson first was for. Selector described only the chosen option, so an unchosen one fell through to Depth::Flat and no renderer drew it; goingson’s tab strip recesses its unchosen tabs by hand and could not delete the line, because being recessed is why the chosen tab reads as coming forward. So Selector::unchosen joins chosen, and saying it needed Fill::Sunken and Depth::Sunken: a surface set back by colour with no edge, which is neither a well nor level-with.

0.7.0 adds State, the interaction axis, closing the gap that adopting against three apps rather than one made visible. The description named rest and, through Depth::pressed, pressed. It named neither focus nor disabled, so makeover-webview emitted a hover rule and stopped, and each consumer completed the primitive from outside by out-specifying a rule it did not own: 19 such rules in goingson, 21 in the MNW server, a further set in Balanced Breakfast, and three focus rings that do not match. The axis is deliberately two members wide, because hover and pressed belong where they already are. State’s own docs carry that argument.

0.8.0 finishes Field, which described a field well enough to label it and not well enough to draw it. Writing makeover-webview’s form emitter found three things missing and the renderer supplied all three from outside: the current value, a select’s options, and the placeholder. Two of those move here and one does not.

  • Field::placeholder is user-facing text sitting beside label and hint. There was never a reading on which it was renderer state; it was outside only because adding a field to a published struct is breaking.
  • Field::options moves because every renderer needs them and each was going to invent its own shape. Choice is the shape makeover-webview already arrived at, taken as-is rather than redesigned.
  • The current value stays renderer-side and is not coming here. It is the one of the three that is genuinely state: a webview reads it out of the DOM, an immediate-mode renderer holds a &mut to the app’s own field, and a description that carried it would be a form model.

0.9.0 opens RowPart, which was the last closed enum in the vocabulary, and adds RowPart::Tokens. Both halves come from the same finding, made by the first two real screens described through the router rather than by reading a stylesheet.

A goingson project card carries two trailing badges, a type and a toned status; a contact card carries a primary email and a strip of tags. Meta is one slot and one string, so both ports joined their facts with a separator and lost what the second one was: a status reads as text where it used to read as colour. Token already says exactly the right thing — a small labelled thing with a kind, a tone and an optional action — and could only ever be a node in its own right, never inside a row.

So the missing thing was permission rather than a concept. Tokens is that permission, and #[non_exhaustive] arrives with it so the next member is not a lockstep event across three renderers. The pairing is the point: this enum’s own consumer in makeover-webview carried a comment predicting it would stop compiling one day, which is a lockstep break written down and waited for rather than prevented.

Balanced Breakfast was checked before the member was added, because one consumer wanting something is not evidence. It packs a count and two icon buttons into the same single Meta slot while leaving Actions empty, so the slot was already straining under a second consumer for a different reason.

§Where the description stops

The bespoke widgets, a day-plan timeline and a kanban board and a calendar, are not describable here and will not become describable. A description expressive enough to produce a timeline is a widget library wearing a description’s name. Generate the boring 80% so the bespoke 20% gets the attention.

Region::Bespoke is how that limit is stated rather than hidden. The description names the place and the app owns the contents, so a screen containing a timeline is still a whole screen and still routable. Without it, the four goingson screens that make the app worth using would need a second, undescribed path beside the router, and two paths is how a vocabulary starts drifting from its app again.

Structs§

Choice
One option offered by a field FieldKind::offers_options accepts.
Column
One column of a table.
Field
One field of a form.

Enums§

Arrangement
How a screen is laid out.
Bevel
Which way the light falls across a two-tone edge.
Depth
How a region sits relative to the surface behind it.
Edge
One side of a bevel, named by the intent it takes.
FieldKind
What kind of value a form field takes.
Fill
A surface intent a region is filled with.
Heading
How far down the heading tree a title sits.
Notice
Something the app is telling the user, unprompted.
Priority
What a column is worth when there is not room for all of them.
Readiness
Whether the content of a region has arrived.
Region
A named part of a screen.
RowPart
The parts of a list row.
Selector
A control that picks between things.
State
An interaction state a region can be in, beside whatever Depth it is.
Token
A small labelled thing that sits inside something else.
Tone
What a region is saying, when it is saying something.
Width
How much room a column asks for.

Traits§

Intent
A colour intent this crate refers to but never resolves.