Skip to main content

max / makeover-tui

An unchosen option is not a disabled one A radio group drew every option it was not showing as chosen in style.muted, which is what State::Disabled resolves to. Every option in that list answers a press, so a five-option field read as one live row and four dead ones. It takes the secondary content intent now. Found by the content-muted sweep (makeover-layout de765137); same shape as the sortable-but-unsorted heading, in a different widget.
Wiki
three-tone-convention
Author: Max Johnson <me@maxj.phd> · 2026-08-16 19:00 UTC
Signed with PGP, not checked
Commit: 230bf63f7c302c644023e1fc9342f430a1a363ad
Parent: 55bfdf4
2 files changed, +31 insertions, -2 deletions
M Cargo.toml +1 -1
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makeover-tui"
3 - version = "0.25.0"
3 + version = "0.25.1"
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"
M src/piece.rs +30 -1
@@ -455,7 +455,12 @@
455 455 let mark = if chosen { "(*)" } else { "( )" };
456 456 rows += text::draw(
457 457 &format!("{mark} {}", choice.label),
458 - if chosen { well } else { style.muted },
458 + // An option that is not chosen is still an option: pressing
459 + // it chooses it. So it takes the secondary content intent
460 + // and not the muted one, which is what disabled looks like
461 + // (`State::Disabled` resolves to it). Muted here read as a
462 + // list of five where four were greyed out.
463 + if chosen { well } else { style.secondary },
459 464 below(area, used + rows),
460 465 buf,
461 466 );
@@ -556,6 +561,7 @@
556 561 fn style() -> PieceStyle {
557 562 PieceStyle {
558 563 content: Style::new().add_modifier(Modifier::BOLD),
564 + secondary: Style::new().add_modifier(Modifier::ITALIC),
559 565 muted: Style::new().add_modifier(Modifier::DIM),
560 566 danger: Style::new().add_modifier(Modifier::CROSSED_OUT),
561 567 ..PieceStyle::default()
@@ -818,6 +824,29 @@
818 824 assert_eq!(field_height(&style, &field_, 20), 3);
819 825 }
820 826
827 + #[test]
828 + fn an_unchosen_option_does_not_read_as_disabled() {
829 + // The three-tone convention: muted is inert, and every option in this
830 + // list answers a press. Drawn muted, a five-option radio read as one
831 + // live row and four dead ones.
832 + let style = style();
833 + let mut field_ = Field::new(FieldKind::Radio, "size", "Size");
834 + let options = [Choice::plain("small"), Choice::plain("large")];
835 + field_.options = &options;
836 + let mut buf = buffer(20, 5);
837 + field(
838 + &style,
839 + &field_,
840 + Held::Text("large"),
841 + false,
842 + buf.area,
843 + &mut buf,
844 + );
845 + let unchosen = buf.cell((0, 1)).expect("the first option").style();
846 + assert_eq!(unchosen.add_modifier, style.secondary.add_modifier);
847 + assert_ne!(unchosen.add_modifier, style.muted.add_modifier);
848 + }
849 +
821 850 #[test]
822 851 fn a_checkbox_reads_a_bool_rather_than_a_submitted_string() {
823 852 // `Held::On` exists so a host's own submission convention -- quasi