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

§What a member is admitted on

A member is added when an app needs a fact the vocabulary cannot state, and refused when what it wants is presentation it should be asking a renderer for. Three tests, all of which have to pass:

  • Generic against bespoke. Is this furniture any app would have, or is it this app’s own? A rule that withholds a word until a second app has duplicated the code guarantees the duplication. What the app owns keeps Region::Handover and Region::Ceded.
  • Every host has an honest answer. A member no renderer can express without borrowing one host’s idiom is not a description.
  • It can be laid out before it is filled. See First paint is final paint below.

Count the members a thing needs before refusing it. A refusal is only worth as much as the measurement under it.

§What this crate cannot say

  • An address. What a control calls, and where a button goes. Act names the act and holds no destination.
  • A current value. A webview reads it out of the DOM and an immediate-mode renderer holds a &mut to the app’s own field. A description carrying it would be a form model.
  • What has focus. See below.
  • A duration or a clock. No estimate of time remaining, no autosave interval, no animation length.
  • A colour, a size or a position. The deferral rule.

§Reach, focus and the focus ring

Three terms, and no others, for what sits outside the description. Reach is which things can take focus and in what order; a browser reads it off the document, a TUI derives it from draw order, egui from its own id stack. Focus is which reached thing has the keyboard right now: the renderer’s, live, never described and never round-tripped through a description. The focus ring is the visible cue; the token (focus-ring, derived by makeover from the action colour) is the one shared artifact and the drawing is the renderer’s. Retired as names for any of this: “focus stroke”, “focus cue”, “wants focus”. “Caret” is a different thing — the text cursor inside a field — and keeps its name.

§The three tones, and what a colour claims

One rule for how colour says whether a thing can be used. Every renderer answers to it, and it is stated here because the description is what names the intents.

the thingintent
active, emphasised, the thing itselfcontent
inactive but usable: it still answers a presscontent-secondary
inert: disabled, or not a control at allcontent-muted

content-muted is the one with a claim in it. State::Disabled resolves to it, so a live control wearing it is telling the user it will not answer, and being wrong about that is worse than being quiet, because the user’s response is to stop trying. A sortable column heading that was never sorted, and every unchosen option in a radio group, read as dead lists if they wear it. What is legitimately muted is a caption, a hint, a placeholder, a meter’s reading, an axis label: text that was never going to answer anything.

The three are one ramp and not three colours. makeover’s Emphasis derives the quieter two from the ink, so “one step back” means the same distance in every theme and a renderer cannot land between them by picking its own.

§First paint is final paint

Nothing may change size or position after it is first drawn, and nothing may stand in for content that has not arrived yet. Both halves are absolute.

It is stated here, rather than left to each renderer, because a renderer can only reserve space the description gave it enough to size. A member whose size depends on its content therefore owes whatever makes it sizeable while the content is still absent, and that is the second admission test for a new member: not only does it compose something this crate already names, it can be laid out before it is filled.

The mechanism is a reservation, and Sort’s caret is the worked example. The caret is drawn into a box its own width whether or not the column is sorted, so pressing a heading cannot reflow the row it sits in. The box names no magnitude, which is what keeps it out of makeover-geometry’s territory. Reserve from what is known; never discover geometry from what has not arrived.

The trap is an Option that means “not yet”. Readiness::Pending is the honest way to say a region is still waiting. An optional measurement is not: a count that shows up later widens the text that prints it and moves everything beside it, which is the reflow this rule exists to forbid. So an Option on a measurement means the host will never know it — a property of the query, fixed for the life of the screen — and a renderer sizes for the answer it was handed rather than for the one it hopes is coming.

§Any width, one answer

The sibling of the rule above. That one is independence from when; this one is independence from how you got here.

A rendering is a pure function of the description and the viewport. The same description at the same width is the same output, whatever widths came before it. No renderer may carry geometry across frames, and none may narrow by counting.

The failure this forbids is ordinary enough to be the default everywhere else: a page that hides its sidebar below some width, remembers that it hid it, and does not bring it back the same way. Layout there is a function of (width, history), so dragging a window to 900 wide is a different screen depending on whether you came from 1400 or from 600. Nobody chose that; it is what measuring and remembering produce.

The mechanism is Width for what grows and Priority for what drops. Both are declared, both are read off the description, and neither needs a measurement. A renderer narrows by raising a cutoff over a total order, never by counting what fits and stopping — makeover-tui’s table states that as its own rule and tests it, and makeover-webview reaches the same place with @media and display: none, which is path-independent by construction because CSS has nowhere to keep the previous width.

Two things follow for anything new. A member that would need last frame’s size to lay out this frame is refused, the same way a member that cannot be sized before it is filled is refused. And a fact about what disappears belongs in the description, because a host that has to infer it can only infer it from a measurement.

§Where the description stops

A member is added when an app needs a fact the vocabulary cannot state, and refused when what it wants is presentation it should be asking a renderer for. It is not a quota, and the goal is every screen described.

A timeline is describable. What it needs and could not previously get is two integers, where a thing starts and how long it lasts, which is Track. Slot heights, gridline colour, how overlapping things stack and which hour scrolls into view stay the renderer’s, and Track carries none of them.

A kanban board is describable, and the member is Region::Columns. Every card fact is already sayable through Row’s parts; what nothing else could say is that the columns are peers, since Arrangement offers only list-detail and sidebar-content and a board described as either is a lie about the screen. Dragging a card between columns does not enter into it: a drop’s effect is “set status”, a discrete action Row’s menu already carries, and the drag itself is affordance.

A calendar takes no members. The month grid’s primacy in calendar apps is an artifact of paper: paper cannot be queried, so it has to show every day at once as a fallback index, and routes, search and ranking do that job better. Three jobs survive that reasoning, and only one of them needs a grid:

  1. Spans across days, a stretch of leave, a trip, a sprint. You cannot see “away the 3rd to the 17th” in a list without diffing dates. This is Track with Unit::Days, and Track::days is it.
  2. Density at a glance, which weeks were heavy. That is a heatmap, and a heatmap describes as a list.
  3. Weekday periodicity, “every other Tuesday”, “the 15th is a Saturday”. This is the only job that needs the seven-column wrap, because alignment is the whole of what makes it visible.

Job 3 is the only open question, and nothing in the tree asks for it. A month grid otherwise renders as a Table: seven weekday columns, weeks as rows, blanks for the offset. If a screen wants one, measure the members it needs before adding any.

Region::Handover and Region::Ceded remain for the genuinely app-owned. 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 screens that make an app worth using would need a second, undescribed path beside the router, and two paths is how a vocabulary drifts from its app.

Region::Widget sits between that limit and the primitives, and it does not move the limit. A widget is an assembly of members this crate already has, under a name a renderer may or may not recognise. Anything that needs a member the vocabulary does not have is still a finding about the vocabulary or still bespoke; naming an assembly buys no new expressive power, which is why it is safe to let the set grow outside this crate.

Structs§

Act
Awaiting
An action is waiting on something that resolves once, in expected finite time.
Candidate
One entry in a field’s suggestion list.
Choice
One option offered by a field FieldKind::offers_options accepts.
Column
One column of a table.
Extent
A picture’s own pixel dimensions.
Facet
A named dimension a set can be narrowed by.
FacetValue
One value a Facet offers, as it currently stands.
Field
Figure
One figure with a caption: a number and what it counts.
Meter
How much of a set is done.
Nesting
How deep a row sits inside a set: a tree, an outline, a threaded list.
Paging
Where a reader is in a set that arrived in parts.
Placement
Where a thing sits on a Track, and for how long.
Share
How much of the width an arrangement’s first region takes.
Span
A window on an axis, in whatever Unit its Track counts.
ThemeChoice
One theme, as a picker offers it.
Track
A time axis: things placed by when they happen, rather than flowed.
Window
A window onto a sequence: where it starts, how much it covers, and how long the sequence is when that is known.

Enums§

Accepted
One entry in a file field’s accept list.
Arrangement
How a screen is laid out.
Bevel
Which way the light falls across a two-tone edge.
CellPart
What a table cell holds.
Change
What one line of a diff is: added, removed, or neither.
Contrast
How legible a theme measured, as a picker reports it.
Curve
One field of a form.
Depth
How a region sits relative to the surface behind it.
Edge
One side of a bevel, named by the intent it takes.
Fallback
What a group does when it runs out of room.
Family
A family of media a file can belong to.
FieldKind
What kind of value a form field takes.
Fill
A surface intent a region is filled with.
Fit
Something the user can do, and what it costs to say so.
Flow
The parts of a list row.
Heading
How far down the heading tree a title sits.
Loading
When a picture is needed.
Measure
How wide the content of a whole screen runs.
Notice
Something the app is telling the user, unprompted.
Priority
What a member is worth when there is not room for all of them.
Readiness
What is in a region right now.
Region
A named part of a screen.
RowPart
Selecting
How many of a Facet’s values may be in force, and in what shape.
Selector
A control that picks between things.
Showing
How many of a region’s children are visible at once.
Sort
Which way a column is ordered.
Standing
Whether a FacetValue is narrowing the set, and how it came to be.
State
An interaction state a region can be in, beside whatever Depth it is.
Syntax
What a run of source code is, once something has classified it.
ThemeVariant
Which ambient mode a theme is written for.
Token
A small labelled thing that sits inside something else.
Tone
What a region is saying, when it is saying something.
Unit
What a Track’s integers count.
Width
How much room a placement asks for.

Constants§

DATETIME_FORMAT
The wire format a FieldKind::DateTime value takes: YYYY-MM-DDTHH:MM, local, carrying no zone and no seconds.
DATE_FORMAT
The wire format a FieldKind::Date value takes: ISO 8601, YYYY-MM-DD.

Traits§

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