Expand description
The terminal renderer for makeover_layout.
Named for the target and not for ratatui, the same way
makeover-immediate is named for the mode and not for egui.
§What a terminal actually costs you
Not colour. That was the original assumption here and it is wrong on any
terminal built this decade. Measured across the 31 shipped themes
(makeover’s well_fidelity example):
| ANSI-16 | ANSI-256 | truecolor | |
|---|---|---|---|
| a well collapses onto its face | 18/31 | 4/31 | 2/31 |
| at least one bevel edge vanishes into its face | 31/31 | 4/31 | 0 |
The threshold is 256, not 24-bit, and the two failures that survive at
truecolor are not terminal failures at all: they are the themes whose
raised surface is already white, so the lightening clamps and the well
lands exactly on its face. Those render identically in a browser.
makeover’s own well_is_distinct_from_its_face test already names them.
What a terminal costs is geometry, and no amount of colour fixes it.
An edge occupies a whole cell on each side. A cell is roughly 8x17 pixels,
so a one-pixel bevel becomes something an order of magnitude heavier, which
is why frame hands back a shrunk Rect instead of pretending the
region survived intact. There is nowhere to put a corner radius, so
radius_control and radius_container mean the same thing here. A fill
can only begin and end on a cell boundary.
That is the constraint worth designing against. It does not improve, it is not detectable, and it applies equally to the best terminal ever written.
What it does not mean is that the shape inside the cell stops mattering.
Half of a cell is still addressable, and a bevel drawn in half-blocks reads
as a lit edge where the same bevel in box-drawing reads as a line: ─ and
│ are one stroke through the middle, identical on all four sides, saying
nothing about where the light is. Half-blocks also make the two corners
where light meets shadow expressible, since a glyph that fills half a cell
leaves the other half to the second tone.
§Where fidelity does matter
At Fidelity::Ansi16 the depth vocabulary collapses outright: a well
cannot be filled distinctly on most themes and a bevel loses an edge on
every one of them, so a raised card and a well both read as a single-tone
box. Colour cannot carry the distinction, so frame carries it with the
glyphs instead.
Above that, colour carries it and the glyph fallback never fires.
Palette::shows is worth reading correctly in light of the numbers: it
is not a low-colour workaround. It is a correctness check that a fill
will be visible against what is behind it, and at truecolor it fires on
exactly the two clamping themes, which is precisely when it should.
§Overlays, selection and the focus ring
Palette::fill answers Fill::Overlay, so
Depth::Overlay needs nothing beyond the
route from a description. A test asserts that route, because a route nothing
exercises is one a refactor can quietly lose.
[Theme::selection_on] and [Theme::focus_ring] are DERIVED rather than
authored, and the direction is one-way: an authored key can fall back to a
derivation and break no theme on disk, while a key this crate started
requiring would break every theme that lacks it. So the theme format does not
change, and the promotion stays available for a theme that ever needs to tune
either. Without them a port does selection with REVERSED for want of an
on-accent foreground, and spends makeover’s border-strong on a focus ring,
which is a divider at 1.63:1 on Akari Dawn.
§The table
table is the one thing here that draws content rather than a surface. It
is a mapping layer over ratatui::widgets::Table rather than a table
implementation, because ratatui already lays tracks out, draws a header,
highlights a row and scrolls. What it has no answer for is content
measurement and narrowing, and those are what the module is.
§piece, not widget
makeover-layout owns Region::Widget, the tier between a primitive and
Region::Widget: a named assembly of primitives that each renderer draws
its own way. That is host-agnostic and sits above every renderer.
This crate’s piece module is the opposite end of the same stack:
renderer-local, the answer to what a meter looks like in cells, taking a
description plus what only a terminal knows. Two different things must not
wear one word, least of all in a crate that has to implement the tier. The
style type is piece::PieceStyle.
§A bounded number, and an option that is not offered yet
A makeover_layout::FieldKind::Range is drawn as piece::meter’s cells
with its two ends read out either side, because the ends are what the
question means and a terminal is where it would be easiest to quietly show a
figure instead. An unbounded range falls back to the text path rather than to
bounds this crate invented.
Choice::unavailable is the one place the three-tone convention’s muted is
the truth rather than the lie it warns about: that option will not answer,
and the reason it will not sits on its row.
Choice::detail takes a row of its own under the option, inset by the width
of the mark and muted for the same reason: the line is not a thing to press.
This is the host with the most room of the three, so unlike a browser’s
<select> it does not run the line into the option’s text.
Field::placeholder on a chooser needs nothing here: this renderer draws
every option of a select at once, so an unanswered one is a list with no mark
against any row rather than an empty box.
§An interval is one line
makeover_layout::FieldKind::Interval is drawn as the low end, the word
to, and the high end. One line because it is one question: two rows would
read as two questions, which is what the kind exists to stop the description
saying, and a terminal has no side-by-side boxes to fall back on.
- An open end draws the bound it falls back to, muted, because that is
where the axis ends rather than a value anybody set. With no bound to fall
back on the end stays blank, which is
piece::field’s standing position on a value this crate would have to invent. - The word rather than a dash. A dash between two numbers is a minus sign to anyone reading a signed axis, and half the measured axes are signed: audiofiles filters loudness in dBFS.
- The unit rides on each end, through the same
measuredthe typed path uses, so90 BPM to 130 BPMreads without the label being consulted.
piece::Held::Between is the second value, for the reason the description
states both names: a separator inside one string would make this crate own a
delimiter either end could contain.
§A number reads with its unit
Field::unit is drawn on the value rather than in the label. A terminal has
one line the eye is on, the number, and the label is a line above it, so
0.05 s is the reading and Attack (s) two rows up is not. A range shows it
after the readout beside the bar; a typed number after the value. Every other
kind ignores it, and which those are is FieldKind::measurable’s answer
rather than a matches! kept here.
§The bar fills along the curve
Where a value sits on the bar is Curve’s answer,
not its proportion of the extent. Under Curve::Linear those are the same
number; under a constant ratio they are not, and a bar filled linearly would
put an envelope’s whole useful half inside its first cell.
The two ends beside the bar do not move: they are f(0) and f(1).
§Substitution is renderer policy
Substituting one intent for another belongs to a renderer, never to the
description. Falling back from Fill::Well to Page is an answer for a
renderer that can always paint a colour; here it is wrong, because the page
is the surface a well is usually cut into, so the substitution produces the
exact invisibility it was meant to avoid. makeover-immediate wants that
fallback and this renderer does not.
Re-exports§
pub use makeover_layout;
Modules§
- piece
- A meter, a badge, a control, a figure and a form field.
- table
- Columns, narrowing, cell parts and the sort caret, over ratatui’s own
Table. - text
- Word wrapping that answers a height and a drawing from the same measurement.
Structs§
- Palette
- The resolved colours this renderer needs.
Enums§
- Fidelity
- How many colours the terminal can actually show.
Functions§
- activity_
lit - Whether the activity mark is lit, this far into a wait.
- frame
- Draw a region at a given
Depthand return the area left for content. - paint_
bevel - Paint a two-tone edge around the outside of
area.