Skip to main content

max / makeover-immediate

0.26.0: an unchosen option is not the emphasised one Every option in a choice field was drawn at palette.content, chosen or not, so a list said nothing about which one was picked beyond the dot egui paints. The chosen one keeps content; the rest take the new content-secondary field. Not content-muted, which carries a claim: State::Disabled resolves to it, so a live option wearing it says it will not answer. makeover-tui made the same fix in the same widget at 230bf63. Palette grows a field rather than the whole ramp, which is this crate's standing rule (see the action doc comment): it carries what is drawn. Breaking for anyone constructing a Palette by hand, so a minor bump and a forward fix.
Wiki
three-tone-convention
Author: Max Johnson <me@maxj.phd> · 2026-08-16 21:40 UTC
Signed with PGP, not checked
Commit: ecee9465adbf10dc0875ddd8d3e66b59e813776a
Parent: 1072834
4 files changed, +49 insertions, -3 deletions
M Cargo.toml +1 -1
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makeover-immediate"
3 - version = "0.25.0"
3 + version = "0.26.0"
4 4 edition = "2024"
5 5 description = "The immediate-mode renderer for makeover-layout. Immediate mode is the constraint that matters, not the library: no cascade, no retained tree, one stroke per widget. Backed by egui."
6 6 license = "MIT"
M src/lib.rs +46 -2
@@ -224,6 +224,23 @@
224 224 /// thing here that draws any: until then this crate painted surfaces and
225 225 /// edges and let the caller's own egui visuals answer for text.
226 226 pub content: Color32,
227 + /// `content-secondary`.
228 + ///
229 + /// Inactive but usable: it still answers a press. The middle tone of the
230 + /// three (wiki `three-tone-convention`), and the one an unchosen option in
231 + /// a choice field takes. Added 0.26.0 for that widget, which drew every
232 + /// option at full `content` and so said nothing about which one was
233 + /// chosen beyond the dot egui paints.
234 + ///
235 + /// Not [`content_muted`](Self::content_muted), which carries a claim:
236 + /// `State::Disabled` resolves to it, so a live control wearing it tells the
237 + /// user it will not answer. `makeover-tui` draws the same widget the same
238 + /// way from `makeover-tui@230bf63`.
239 + ///
240 + /// A step of `content` toward the page, derived at load by `makeover`
241 + /// rather than authored, so it is read off the resolved theme here like
242 + /// any other token and never re-derived.
243 + pub content_secondary: Color32,
227 244 /// `content-muted`.
228 245 ///
229 246 /// A field's hint, and what
@@ -594,6 +611,22 @@
594 611 .map_or(value, |opt| opt.label)
595 612 }
596 613
614 + /// What one option in a choice field is drawn in.
615 + ///
616 + /// The chosen one is the emphasised thing and takes `content`; the rest take
617 + /// [`content_secondary`](Palette::content_secondary), because an option that is
618 + /// not chosen is still an option and pressing it chooses it. Muted would be the
619 + /// lie: [`State::Disabled`] resolves to it, so a five-option field read as one
620 + /// live row and four dead ones. `makeover-tui` draws it the same way
621 + /// (`makeover-tui@230bf63`); wiki `three-tone-convention` is the table.
622 + fn option_color(value: &str, option: &str, palette: &Palette) -> Color32 {
623 + if value == option {
624 + palette.content
625 + } else {
626 + palette.content_secondary
627 + }
628 + }
629 +
597 630 /// The control alone, without its label, hint or error.
598 631 fn control(
599 632 ui: &mut Ui,
@@ -654,7 +687,7 @@
654 687 let picked = ui.radio_value(
655 688 value,
656 689 opt.value.to_owned(),
657 - RichText::new(opt.label).color(palette.content),
690 + RichText::new(opt.label).color(option_color(value, opt.value, palette)),
658 691 );
659 692 answered = Some(match answered {
660 693 Some(prev) => prev.union(picked),
@@ -682,7 +715,7 @@
682 715 ui.selectable_value(
683 716 value,
684 717 opt.value.to_owned(),
685 - RichText::new(opt.label).color(palette.content),
718 + RichText::new(opt.label).color(option_color(value, opt.value, palette)),
686 719 );
687 720 }
688 721 })
@@ -805,6 +838,7 @@
805 838 bevel_dark: Color32::BLACK,
806 839 elevation: Color32::from_black_alpha(46),
807 840 content: Color32::from_rgb(5, 5, 5),
841 + content_secondary: Color32::from_rgb(55, 55, 55),
808 842 content_muted: Color32::from_rgb(6, 6, 6),
809 843 action: Color32::from_rgb(7, 7, 7),
810 844 danger: Color32::from_rgb(8, 8, 8),
@@ -969,6 +1003,16 @@
969 1003 );
970 1004 }
971 1005
1006 + #[test]
1007 + fn an_unchosen_option_is_secondary_and_never_muted() {
1008 + let p = palette(Color32::from_rgb(4, 4, 4));
1009 + assert_eq!(option_color("wav", "wav", &p), p.content);
1010 + assert_eq!(option_color("wav", "aiff", &p), p.content_secondary);
1011 + // The whole point of the distinction: muted is what Disabled resolves
1012 + // to, so an option wearing it would claim it does not answer a press.
1013 + assert_ne!(option_color("wav", "aiff", &p), p.content_muted);
1014 + }
1015 +
972 1016 #[test]
973 1017 fn a_hidden_field_draws_nothing_and_answers_nothing() {
974 1018 // Where the two renderers legitimately part: a webview still emits an
@@ -428,6 +428,7 @@
428 428 bevel_dark: Color32::BLACK,
429 429 elevation: Color32::from_black_alpha(46),
430 430 content: Color32::from_rgb(6, 6, 6),
431 + content_secondary: Color32::from_rgb(56, 56, 56),
431 432 content_muted: Color32::from_rgb(7, 7, 7),
432 433 action: Color32::from_rgb(8, 8, 8),
433 434 danger: Color32::from_rgb(9, 9, 9),
@@ -268,6 +268,7 @@
268 268 bevel_dark: Color32::from_rgb(7, 7, 7),
269 269 elevation: Color32::from_black_alpha(40),
270 270 content: Color32::from_rgb(20, 20, 20),
271 + content_secondary: Color32::from_rgb(120, 120, 120),
271 272 content_muted: Color32::from_rgb(21, 21, 21),
272 273 action: Color32::from_rgb(22, 22, 22),
273 274 danger: Color32::from_rgb(23, 23, 23),