material_egui/
lib.rs

1pub mod widgets;
2pub use widgets::button::Button;
3
4use egui::style::{Selection, WidgetVisuals, Widgets};
5use egui::{epaint, Color32, Context, Stroke, Style, Ui, Visuals};
6use material_colors::Argb;
7use std::str::FromStr;
8
9#[derive(Clone, Default, Debug)]
10pub struct MaterialColors {
11    pub dark: bool,
12    pub base_color: String,
13    pub zoom: f32,
14    pub primary: Color32,
15    pub on_primary: Color32,
16    pub primary_container: Color32,
17    pub on_primary_container: Color32,
18    pub inverse_primary: Color32,
19    pub primary_fixed: Color32,
20    pub primary_fixed_dim: Color32,
21    pub on_primary_fixed: Color32,
22    pub on_primary_fixed_variant: Color32,
23    pub secondary: Color32,
24    pub on_secondary: Color32,
25    pub secondary_container: Color32,
26    pub on_secondary_container: Color32,
27    pub secondary_fixed: Color32,
28    pub secondary_fixed_dim: Color32,
29    pub on_secondary_fixed: Color32,
30    pub on_secondary_fixed_variant: Color32,
31    pub tertiary: Color32,
32    pub on_tertiary: Color32,
33    pub tertiary_container: Color32,
34    pub on_tertiary_container: Color32,
35    pub tertiary_fixed: Color32,
36    pub tertiary_fixed_dim: Color32,
37    pub on_tertiary_fixed: Color32,
38    pub on_tertiary_fixed_variant: Color32,
39    pub error: Color32,
40    pub on_error: Color32,
41    pub error_container: Color32,
42    pub on_error_container: Color32,
43    pub surface_dim: Color32,
44    pub surface: Color32,
45    pub surface_bright: Color32,
46    pub surface_container_lowest: Color32,
47    pub surface_container_low: Color32,
48    pub surface_container: Color32,
49    pub surface_container_high: Color32,
50    pub surface_container_highest: Color32,
51    pub on_surface: Color32,
52    pub on_surface_variant: Color32,
53    pub outline: Color32,
54    pub outline_variant: Color32,
55    pub inverse_surface: Color32,
56    pub inverse_on_surface: Color32,
57    pub surface_variant: Color32,
58    pub background: Color32,
59    pub on_background: Color32,
60    pub shadow: Color32,
61    pub scrim: Color32,
62}
63
64fn c(i: Argb) -> Color32 {
65    Color32::from_rgba_premultiplied(i.red, i.green, i.blue, i.alpha)
66}
67
68impl MaterialColors {
69    pub fn rebuild(&mut self) -> Self {
70        *self = Self::new(self.base_color.clone(), self.dark, self.zoom).clone();
71        self.clone()
72    }
73    pub fn new(base_color: String, dark: bool, zoom: f32) -> Self {
74        let data = material_colors::theme_from_source_color(
75            Argb::from_str(&base_color).unwrap(),
76            Default::default(),
77        );
78
79        let scheme = match dark {
80            true => data.schemes.dark,
81            false => data.schemes.light,
82        };
83
84        Self {
85            base_color,
86            dark,
87            zoom,
88
89            // primary
90            primary: c(scheme.primary),
91            on_primary: c(scheme.on_primary),
92            primary_container: c(scheme.primary_container),
93            on_primary_container: c(scheme.on_primary_container),
94            inverse_primary: c(scheme.inverse_primary),
95            primary_fixed: c(scheme.primary_fixed),
96            primary_fixed_dim: c(scheme.primary_fixed_dim),
97            on_primary_fixed: c(scheme.on_primary_fixed),
98            on_primary_fixed_variant: c(scheme.on_primary_fixed_variant),
99            // secondary
100            secondary: c(scheme.secondary),
101            on_secondary: c(scheme.on_secondary),
102            secondary_container: c(scheme.secondary_container),
103            on_secondary_container: c(scheme.on_secondary_container),
104            secondary_fixed: c(scheme.secondary_fixed),
105            secondary_fixed_dim: c(scheme.secondary_fixed_dim),
106            on_secondary_fixed: c(scheme.on_secondary_fixed),
107            on_secondary_fixed_variant: c(scheme.on_secondary_fixed_variant),
108            // tertiary
109            tertiary: c(scheme.tertiary),
110            on_tertiary: c(scheme.on_tertiary),
111            tertiary_container: c(scheme.tertiary_container),
112            on_tertiary_container: c(scheme.on_tertiary_container),
113            tertiary_fixed: c(scheme.tertiary_fixed),
114            tertiary_fixed_dim: c(scheme.tertiary_fixed_dim),
115            on_tertiary_fixed: c(scheme.on_tertiary_fixed),
116            on_tertiary_fixed_variant: c(scheme.on_tertiary_fixed_variant),
117            // error
118            error: c(scheme.error),
119            on_error: c(scheme.on_error),
120            error_container: c(scheme.error_container),
121            on_error_container: c(scheme.on_error_container),
122            // surface
123            surface_dim: c(scheme.surface_dim),
124            surface: c(scheme.surface),
125            surface_bright: c(scheme.surface_bright),
126            surface_container_lowest: c(scheme.surface_container_lowest),
127            surface_container_low: c(scheme.surface_container_low),
128            surface_container: c(scheme.surface_container),
129            surface_container_high: c(scheme.surface_container_high),
130            surface_container_highest: c(scheme.surface_container_highest),
131            on_surface: c(scheme.on_surface),
132            on_surface_variant: c(scheme.on_surface_variant),
133            // outline
134            outline: c(scheme.outline),
135            outline_variant: c(scheme.outline_variant),
136            // inverse
137            inverse_surface: c(scheme.inverse_surface),
138            inverse_on_surface: c(scheme.inverse_on_surface),
139            surface_variant: c(scheme.surface_variant),
140            background: c(scheme.background),
141            on_background: c(scheme.on_background),
142            shadow: c(scheme.shadow),
143            scrim: c(scheme.scrim),
144        }
145    }
146    /// exports the default M3 theme
147    /// this function applies M3 themes to egui assets, specific implementation is subject to change
148    /// CANNOT apply zoom
149    pub fn export(&self) -> Style {
150        let p = self;
151
152        let visuals = Visuals {
153            // override_text_color: Some(p.primary),
154            collapsing_header_frame: true,
155            override_text_color: None,
156            hyperlink_color: p.on_primary,
157            // background
158            faint_bg_color: p.surface_container,
159            extreme_bg_color: p.surface_variant,
160            code_bg_color: p.surface_dim,
161
162            window_fill: p.surface_container_highest,
163            panel_fill: p.surface,
164
165            warn_fg_color: p.error_container,
166            error_fg_color: p.error,
167
168            // window_stroke: Stroke {
169            //     color: p.secondary,
170            //     width: 10.5,
171            // },
172
173            window_highlight_topmost: false,
174            // todo
175            widgets: widgets(p.clone()),
176            window_shadow: epaint::Shadow {
177                color: p.shadow,
178                ..Default::default()
179            },
180
181            popup_shadow: epaint::Shadow {
182                color: p.shadow,
183                ..Default::default()
184            },
185
186            // todo selection represents a kind of accent color for widgets outside of the widgets config
187            // this is the subject of an issue https://github.com/emilk/egui/issues/4227
188            selection: Selection {
189                bg_fill: p.secondary,
190                stroke: Stroke {
191                    width: 1.5,
192                    color: p.on_secondary,
193                },
194            },
195
196            ..Default::default()
197        };
198
199        Style {
200            visuals,
201
202            ..Default::default()
203        }
204    }
205    /// applies generated theming values to ctx
206    /// this also applies zoom value: must be stored persistently
207    pub fn apply_zoom(&mut self, ctx: &Context, first_run: &mut bool) {
208        if *first_run {
209            ctx.set_zoom_factor(self.zoom);
210            *first_run = false;
211        }
212        ctx.set_style(self.export());
213    }
214
215    /// applies generated theming values to ctx
216    pub fn apply(&mut self, ctx: &Context) {
217        ctx.set_style(self.export());
218    }
219    /// applies generated values to Ui
220    /// CANNOT apply zoom
221    pub fn apply_ui(&self, ui: &mut Ui) {
222        ui.set_style(self.export())
223    }
224
225    pub fn error_apply(&self, ui: &mut Ui) {
226        let widgets = &mut ui.style_mut().visuals.widgets;
227        // 'error buttons' are not documented in M3, so its unclear which arrangement is better
228        let active = (self.error, self.on_error);
229        let rest = (self.error_container, self.on_error_container);
230        // widgets
231        widget_maker_mut(&mut widgets.hovered, rest.0, rest.1);
232        widget_maker_mut(&mut widgets.inactive, rest.0, rest.1);
233        widget_maker_mut(&mut widgets.open, active.0, active.1);
234        widget_maker_mut(&mut widgets.active, active.0, active.1);
235    }
236}
237fn widgets(p: MaterialColors) -> Widgets {
238    let mut old = match p.dark {
239        true => Visuals::dark(),
240        false => Visuals::light(),
241    }
242    .widgets;
243
244    //   colors.visuals.widgets.noninteractive.bg_stroke.color = Color32::RED;
245    widget_maker_mut(&mut old.noninteractive, p.surface, p.on_surface);
246
247    old.noninteractive.bg_stroke.color = p.surface_variant;
248    old.noninteractive.bg_stroke.width = 4.;
249
250    widget_maker_mut(
251        &mut old.inactive,
252        p.primary_container,
253        p.on_primary_container,
254    );
255    widget_maker_mut(
256        &mut old.hovered,
257        p.tertiary_container,
258        p.on_tertiary_container,
259    );
260    widget_maker_mut(&mut old.active, p.tertiary, p.on_tertiary);
261    widget_maker_mut(&mut old.open, p.primary_container, p.on_primary_container);
262    old
263}
264fn widget_maker_mut(old: &mut WidgetVisuals, fill: Color32, text: Color32) {
265    old.bg_fill = fill;
266    old.weak_bg_fill = fill;
267    old.fg_stroke.color = text;
268    old.bg_stroke.color = fill;
269    old.expansion = 0.;
270}