Theme a widget
Every widget in retroglyph-ui picks its colors from whichever style knobs you set on it directly
(Button::style/hovered_style, Panel::border_style/fill_style, and so on), independent of any
other widget on screen. That’s fine for one widget; for an app with several, .theme() replaces the
per-widget, per-state style calls with one shared palette.
Theme
Theme is a plain struct of
named color roles (fg, panel_bg, hover_bg, press_bg, accent, and the rest); build one, or
start from
Theme::DARK
or
Theme::LIGHT.
Every widget that draws (Panel, Tabs, List, Button, ProgressBar, and the rest of the
widget module) has a
.theme(theme) builder method that derives every style it needs from that one Theme instead of
the widget’s own per-state defaults:
let panel = Panel::new()
.title(if *dark { "Theme: Dark" } else { "Theme: Light" })
.theme(theme);
ui.draw(panel_area, &panel);
// Panel's own interior inset -- one cell in from the border on every side, the same math
// `Modal::render` uses to hand back its inner content rect.
let inner = Rect::new(
panel_area.left() + 1,
panel_area.top() + 1,
panel_area.width() - 2,
panel_area.height() - 2,
);
let tabs = Tabs::new(&TABS).select(Some(selected_tab)).theme(theme);
ui.draw(
Rect::new(inner.left(), inner.top(), inner.width(), 1),
&tabs,
);
let list_area = Rect::new(inner.left(), inner.top() + 2, inner.width(), 4);
let list = List::new(&ITEMS).theme(theme);
ui.draw_stateful(list_area, &list, list_state);
draw_toggle_button(
ui,
Rect::new(inner.left(), inner.top() + 7, 20, 1),
theme,
dark,
);
let progress_area = Rect::new(inner.left(), inner.top() + 9, inner.width(), 1);
ui.draw(progress_area, &ProgressBar::new(7, 10).theme(theme));
Theme carries no reference to “the active theme”: nothing here is global or thread-local. Each
draw call is handed whichever Theme value the app currently considers active, picked however the
app likes (a config setting, a t keypress, matching the terminal’s own light/dark preference), and
every widget re-derives its colors from it fresh every frame.
Switching at runtime
Because .theme() takes a plain value with no persistent state of its own, switching themes is just
picking a different Theme before the next frame’s draw calls, including from inside a widget the
theme itself affects, like the toggle button below:
fn draw_toggle_button(ui: &mut Ui<'_, '_, WidgetId>, rect: Rect, theme: Theme, dark: &mut bool) {
let label = if *dark {
"Switch to Light"
} else {
"Switch to Dark"
};
let button = Button::new(label).theme(theme);
if ui.show(rect, WidgetId::ToggleButton, &button).clicked() {
*dark = !*dark;
}
}
Running it
cargo run --example 17_theme_switch --features crossterm
cargo run --example 17_theme_switch --features software
cargo run --example 17_theme_switch # headless fallback, prints a few frames to stdout
See also
- Draw a panel and Handle a click, for widgets typically themed together.
examples/examples/09_widgets_dashboard.rs/examples/examples/10_widgets_interaction.rsfor the hand-threadedtheme.*style calls.theme()replaces.