Skip to main content

elegance/
theme_switcher.rs

1//! Theme picker — a one-line drop-in for switching between the four
2//! built-in themes. Saves every app from re-implementing the
3//! `Select::strings`-over-name-strings boilerplate.
4
5use egui::{Id, Response, Ui, Widget};
6
7use crate::theme::BuiltInTheme;
8use crate::Select;
9
10/// A drop-in picker for the four built-in elegance themes.
11///
12/// Renders a small [`Select`](crate::Select) of the four built-in themes
13/// and — by default — installs the chosen theme into the context each
14/// frame. Bind it to a [`BuiltInTheme`] held anywhere in your app state.
15///
16/// ```no_run
17/// # use elegance::{BuiltInTheme, ThemeSwitcher};
18/// # egui::__run_test_ui(|ui| {
19/// let mut theme = BuiltInTheme::Slate;
20/// ui.add(ThemeSwitcher::new(&mut theme));
21/// # });
22/// ```
23///
24/// # Installation
25///
26/// By default the widget calls [`Theme::install`](crate::Theme::install) on
27/// the selected theme on every frame. If your app already installs a
28/// theme elsewhere (for instance from a larger preference store), call
29/// [`ThemeSwitcher::auto_install`]`(false)` to suppress that.
30#[must_use = "Add with `ui.add(...)`."]
31pub struct ThemeSwitcher<'a> {
32    current: &'a mut BuiltInTheme,
33    id_salt: Id,
34    width: f32,
35    auto_install: bool,
36}
37
38impl<'a> std::fmt::Debug for ThemeSwitcher<'a> {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        f.debug_struct("ThemeSwitcher")
41            .field("current", &*self.current)
42            .field("id_salt", &self.id_salt)
43            .field("width", &self.width)
44            .field("auto_install", &self.auto_install)
45            .finish()
46    }
47}
48
49impl<'a> ThemeSwitcher<'a> {
50    /// Create a switcher bound to a mutable [`BuiltInTheme`] slot.
51    pub fn new(current: &'a mut BuiltInTheme) -> Self {
52        Self {
53            current,
54            id_salt: Id::new("elegance_theme_switcher"),
55            width: 110.0,
56            auto_install: true,
57        }
58    }
59
60    /// Override the id salt. Only needed if multiple switchers coexist
61    /// in the same UI.
62    pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
63        self.id_salt = Id::new(id_salt);
64        self
65    }
66
67    /// Override the switcher width in points. Default: `110.0`.
68    pub fn width(mut self, width: f32) -> Self {
69        self.width = width;
70        self
71    }
72
73    /// Whether to call [`Theme::install`](crate::Theme::install) on the
74    /// selected theme every frame. Default: `true`.
75    ///
76    /// Set to `false` if the caller installs a theme elsewhere and just
77    /// wants the picker UI.
78    pub fn auto_install(mut self, auto_install: bool) -> Self {
79        self.auto_install = auto_install;
80        self
81    }
82}
83
84impl Widget for ThemeSwitcher<'_> {
85    fn ui(self, ui: &mut Ui) -> Response {
86        let Self {
87            current,
88            id_salt,
89            width,
90            auto_install,
91        } = self;
92
93        let options = BuiltInTheme::all().into_iter().map(|t| (t, t.label()));
94        // Reborrow so `current` remains usable after the Select is consumed,
95        // which lets us install the (possibly just-updated) theme below.
96        let response = ui.add(
97            Select::new(id_salt, &mut *current)
98                .options(options)
99                .width(width),
100        );
101
102        if auto_install {
103            current.theme().install(ui.ctx());
104        }
105
106        response
107    }
108}