Skip to main content

max / makeover-tui

0.19.0: widget is piece, because the word went to the description makeover-layout 0.20.0 took "widget" for the third tier: a host-agnostic named assembly of primitives, sitting above every renderer. This module is the opposite end of the same stack -- renderer-local, the answer to what a meter looks like in cells -- and one word for both would have been worst exactly here, in a crate that has to implement the tier. This half moved and the description's half kept the word. A second or third party naming a widget is naming the layout kind; nothing outside this tree ever needed a word for a drawing routine. The module's own first sentence was already "the pieces every terminal app draws", so the name was in front of us. WidgetStyle is PieceStyle.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-14 15:16 UTC
Signed with PGP, not checked
Commit: 8895b6bf884780194f996c594a7f22193b73fffb
Parent: c183e4b
3 files changed, +57 insertions, -23 deletions
M Cargo.toml +2 -2
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makeover-tui"
3 - version = "0.18.0"
3 + version = "0.19.0"
4 4 edition = "2024"
5 5 description = "The terminal renderer for makeover-layout, on ratatui. Colour stops being the constraint above 256 entries; geometry never does, because an edge occupies a whole cell on every side."
6 6 license = "MIT"
@@ -19,7 +19,7 @@
19 19 # compile against an API added in a later one -- which is what `Depth::Overlay`
20 20 # would do here. The rest of the suite has pinned this way since
21 21 # makeover-webview found it the hard way.
22 - makeover-layout = "0.19.0"
22 + makeover-layout = "0.20.0"
23 23 makeover = { version = "2.5", optional = true }
24 24
25 25 [lints.rust]
M src/lib.rs +21 -3
@@ -91,6 +91,24 @@
91 91 //! highlights a row and scrolls. What it has no answer for is content
92 92 //! measurement and narrowing, and those are what the module is.
93 93 //!
94 + //! # 0.19.0: `widget` is [`piece`], because the word went to the description
95 + //!
96 + //! `makeover-layout` 0.20.0 added `Region::Widget`, the third tier between a
97 + //! primitive and `Region::Bespoke`: a named assembly of primitives that each
98 + //! renderer draws its own way. That is host-agnostic and sits *above* every
99 + //! renderer.
100 + //!
101 + //! This crate's `widget` module is the opposite end of the same stack —
102 + //! renderer-local, the answer to what a meter looks like in cells, taking a
103 + //! description plus what only a terminal knows. Two different things wearing
104 + //! one word, and the collision would have been worst exactly here, in a crate
105 + //! that has to implement the tier.
106 + //!
107 + //! So this half moved and the description's half kept the word. That direction
108 + //! is not arbitrary: a second or third party naming a widget is naming the
109 + //! layout kind, and nothing outside this tree ever needed a word for a drawing
110 + //! routine. `WidgetStyle` is `PieceStyle`.
111 + //!
94 112 //! # The correction this renderer forced
95 113 //!
96 114 //! [`makeover_layout::Fill`] briefly carried a `fallback` method, returning
@@ -148,9 +166,9 @@
148 166 /// A meter, a badge, a control, a figure and a form field.
149 167 ///
150 168 /// The pieces below the level [`table`] works at. Not feature-gated, on
151 - /// [`table`]'s footing: [`WidgetStyle`](widget::WidgetStyle) carries the tones,
152 - /// and a caller with a loaded theme reaches for `WidgetStyle::from_theme`.
153 - pub mod widget;
169 + /// [`table`]'s footing: [`PieceStyle`](piece::PieceStyle) carries the tones,
170 + /// and a caller with a loaded theme reaches for `PieceStyle::from_theme`.
171 + pub mod piece;
154 172
155 173 /// How many colours the terminal can actually show.
156 174 ///
@@ -1,5 +1,21 @@
1 1 //! The pieces every terminal app draws, drawn once.
2 2 //!
3 + //! # Called `widget` until 0.19.0
4 + //!
5 + //! Renamed because `makeover-layout` 0.20.0 took the word for something else,
6 + //! and the two meanings do not sit together. A `Region::Widget` there is
7 + //! host-agnostic: a named assembly of primitives that every renderer draws its
8 + //! own way. What is in this module is the opposite end — renderer-local, the
9 + //! answer to *what a meter looks like in cells*, taking a description plus what
10 + //! only a terminal knows.
11 + //!
12 + //! One word for both would have made the tier unreadable in the crate that
13 + //! implements it. This half moved because the other half is the ecosystem-facing
14 + //! one: a second or third party naming a widget is naming the layout kind, and
15 + //! nothing outside this tree ever needed a word for a drawing routine.
16 + //!
17 + //! `WidgetStyle` went with it and is `PieceStyle`.
18 + //!
3 19 //! Arrived in 0.16.0 out of `quasi-tui`, which had written all of them and was
4 20 //! the second consumer to do so. A meter, a badge, a control, a figure and a
5 21 //! form field are what a screen is made of below the level [`table`](crate::table)
@@ -8,7 +24,7 @@
8 24 //!
9 25 //! # What these take, and what they leave alone
10 26 //!
11 - //! Each takes a `makeover-layout` description, a [`WidgetStyle`], and whatever
27 + //! Each takes a `makeover-layout` description, a [`PieceStyle`], and whatever
12 28 //! the *host* knows that a description never carries. That last part is the
13 29 //! shape worth copying: [`field`] takes what is currently typed in the box as a
14 30 //! separate argument, because [`Field`] deliberately does not carry a value and
@@ -19,7 +35,7 @@
19 35 //! is on, so every drawing here takes `focused` as an argument and the caller
20 36 //! is what counts. What focus *looks like* is this crate's answer and not the
21 37 //! caller's, which is the point of it being here: see
22 - //! [`WidgetStyle::focused`].
38 + //! [`PieceStyle::focused`].
23 39 //!
24 40 //! # What they do not do
25 41 //!
@@ -51,7 +67,7 @@
51 67 /// two-colour terminal is the case where a `Style` carrying a foreground is a
52 68 /// foreground that will not land, and bold-and-reversed is what is left.
53 69 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
54 - pub struct WidgetStyle {
70 + pub struct PieceStyle {
55 71 /// Ordinary content, and what [`Tone::Neutral`] reads as.
56 72 pub content: Style,
57 73 /// Content one step back: a field's label, a quoted run.
@@ -100,7 +116,7 @@
100 116 pub required_marker: &'static str,
101 117 }
102 118
103 - impl Default for WidgetStyle {
119 + impl Default for PieceStyle {
104 120 /// Modifiers only, no foreground: what survives a terminal with two
105 121 /// colours.
106 122 fn default() -> Self {
@@ -127,7 +143,7 @@
127 143 }
128 144 }
129 145
130 - impl WidgetStyle {
146 + impl PieceStyle {
131 147 /// The house widgets, from a loaded theme.
132 148 ///
133 149 /// The lift this module exists for. `quasi-tui` carried every line of this
@@ -254,7 +270,7 @@
254 270 /// taken assembled, which is what [`Meter::label`] carrying the noun alone is
255 271 /// for: a terminal at one line and a tooltip want different sentence orders.
256 272 #[must_use]
257 - pub fn meter(style: &WidgetStyle, meter: &Meter<'_>) -> Line<'static> {
273 + pub fn meter(style: &PieceStyle, meter: &Meter<'_>) -> Line<'static> {
258 274 let cells = u32::from(style.meter_cells);
259 275 let filled = meter
260 276 .done
@@ -298,7 +314,7 @@
298 314 /// interaction, not for a drawing.
299 315 #[must_use]
300 316 pub fn token(
301 - style: &WidgetStyle,
317 + style: &PieceStyle,
302 318 label: &str,
303 319 kind: Token,
304 320 tone: Tone,
@@ -328,7 +344,7 @@
328 344 /// it would be an affordance that lies. Whether it is reachable at all is the
329 345 /// caller's count to keep — ask [`Act::disabled`].
330 346 #[must_use]
331 - pub fn act(style: &WidgetStyle, act: &Act<'_>, focused: bool) -> Line<'static> {
347 + pub fn act(style: &PieceStyle, act: &Act<'_>, focused: bool) -> Line<'static> {
332 348 let painted = if act.disabled() {
333 349 style.muted
334 350 } else {
@@ -347,7 +363,7 @@
347 363 /// carries as a primary-versus-secondary button. A form's submit is the case
348 364 /// this exists for.
349 365 #[must_use]
350 - pub fn filled_act(style: &WidgetStyle, label: &str, focused: bool) -> Line<'static> {
366 + pub fn filled_act(style: &PieceStyle, label: &str, focused: bool) -> Line<'static> {
351 367 Line::from(Span::styled(
352 368 format!("[ {label} ]"),
353 369 style.focused(focused, style.filled),
@@ -365,7 +381,7 @@
365 381 /// The tone lands on the value and its change rather than on the caption, which
366 382 /// is what [`Figure::tone`] means: the figure is an ordinary fact and it is the
367 383 /// movement that reads as good or bad.
368 - pub fn figure(style: &WidgetStyle, figure: &Figure<'_>, area: Rect, buf: &mut Buffer) -> u16 {
384 + pub fn figure(style: &PieceStyle, figure: &Figure<'_>, area: Rect, buf: &mut Buffer) -> u16 {
369 385 let value = match figure.change {
370 386 Some(change) => format!("{} {change}", figure.value),
371 387 None => figure.value.to_owned(),
@@ -385,7 +401,7 @@
385 401 /// field is nothing at all, which is the one field kind a terminal and a webview
386 402 /// agree on completely.
387 403 #[must_use]
388 - pub fn field_height(style: &WidgetStyle, field: &Field<'_>, width: u16) -> u16 {
404 + pub fn field_height(style: &PieceStyle, field: &Field<'_>, width: u16) -> u16 {
389 405 if !field.kind.visible() {
390 406 return 0;
391 407 }
@@ -407,7 +423,7 @@
407 423 /// `focused` marks the box rather than the label, because the box is where the
408 424 /// typing lands.
409 425 pub fn field(
410 - style: &WidgetStyle,
426 + style: &PieceStyle,
411 427 field: &Field<'_>,
412 428 held: Held<'_>,
413 429 focused: bool,
@@ -480,7 +496,7 @@
480 496 }
481 497
482 498 /// The label, marked where the field is compulsory.
483 - fn label_of(style: &WidgetStyle, field: &Field<'_>) -> String {
499 + fn label_of(style: &PieceStyle, field: &Field<'_>) -> String {
484 500 if field.required {
485 501 format!("{} {}", field.label, style.required_marker)
486 502 } else {
@@ -501,7 +517,7 @@
501 517 /// bar for this and gets it without asking; a terminal has one cell of reversed
502 518 /// video, put on the first column, which is where the first character lands.
503 519 fn empty_well(
504 - style: &WidgetStyle,
520 + style: &PieceStyle,
505 521 placeholder: &str,
506 522 well: Style,
507 523 focused: bool,
@@ -537,12 +553,12 @@
537 553
538 554 /// The style the drawings are read against: one distinguishable modifier
539 555 /// per role, so a test can say which style landed without a colour.
540 - fn style() -> WidgetStyle {
541 - WidgetStyle {
556 + fn style() -> PieceStyle {
557 + PieceStyle {
542 558 content: Style::new().add_modifier(Modifier::BOLD),
543 559 muted: Style::new().add_modifier(Modifier::DIM),
544 560 danger: Style::new().add_modifier(Modifier::CROSSED_OUT),
545 - ..WidgetStyle::default()
561 + ..PieceStyle::default()
546 562 }
547 563 }
548 564
@@ -831,7 +847,7 @@
831 847 fn the_default_style_carries_no_colour_at_all() {
832 848 // A two-colour terminal is the case where a foreground will not land,
833 849 // so the default is modifiers only rather than a placeholder palette.
834 - let style = WidgetStyle::default();
850 + let style = PieceStyle::default();
835 851 for painted in [style.content, style.danger, style.page, style.action] {
836 852 assert_eq!(painted.fg, None);
837 853 assert_eq!(painted.bg, None);