egui_aesthetix/
lib.rs

1//! A programmatic and Uniform approach to theming egui Applications.
2//!
3//! This library supplies one simple theme trait which attempts to expose all the key colors, margins, paddings, spacings
4//! and other style elements that one would need to manipulate to implement a theme.
5//!
6//! It also gives defaults for more niche elements of the style that a user might not want to customize
7//! but can if they want to.
8
9#[cfg(feature = "default")]
10pub mod themes;
11
12use egui::style::ScrollStyle;
13
14/// Every custom egui theme that wishes to use the egui aesthetix crate must implement this trait.
15/// Aesthetix is structured in such a way that it is easy to customize the theme to your liking.
16///
17/// The trait is split into two parts:
18/// - The first part are the methods that have no implementation, these should just return self-explanatory values.
19///
20/// - The second part are the methods that have a default implementation, they are more complex and use all the user defined methods.
21///   the fields in these traits that don't use trait methods as values are niche and can be ignored if you don't want to customize them.
22///   If the user really wants to customize these fields, they can override the method easily enough, just copy the method you wish to override
23///   and do so. All of eguis style fields can be found here.
24pub trait Aesthetix {
25    /// The name of the theme for debugging and comparison purposes.
26    fn name(&self) -> &str;
27
28    /// The primary accent color of the theme.
29    fn primary_accent_color_visuals(&self) -> egui::Color32;
30
31    /// The secondary accent color of the theme.
32    fn secondary_accent_color_visuals(&self) -> egui::Color32;
33
34    /// Used for the main background color of the app.
35    ///
36    /// - This value is used for eguis `panel_fill` and `window_fill` fields
37    fn bg_primary_color_visuals(&self) -> egui::Color32;
38
39    /// Something just barely different from the background color.
40    ///
41    /// - This value is used for eguis `faint_bg_color` field
42    fn bg_secondary_color_visuals(&self) -> egui::Color32;
43
44    /// Very dark or light color (for corresponding theme). Used as the background of text edits,
45    /// scroll bars and others things that needs to look different from other interactive stuff.
46    ///
47    /// - This value is used for eguis `extreme_bg_color` field
48    fn bg_triage_color_visuals(&self) -> egui::Color32;
49
50    /// Background color behind code-styled monospaced labels.
51    /// Back up lighter than the background primary, secondary and triage colors.
52    ///
53    /// - This value is used for eguis `code_bg_color` field
54    fn bg_auxiliary_color_visuals(&self) -> egui::Color32;
55
56    /// The color for hyperlinks, and border contrasts.
57    fn bg_contrast_color_visuals(&self) -> egui::Color32;
58
59    /// This is great for setting the color of text for any widget.
60    ///
61    /// If text color is None (default), then the text color will be the same as the foreground stroke color
62    /// and will depend on whether the widget is being interacted with.
63    fn fg_primary_text_color_visuals(&self) -> Option<egui::Color32>;
64
65    /// Success color for text.
66    fn fg_success_text_color_visuals(&self) -> egui::Color32;
67
68    /// Warning text color.
69    fn fg_warn_text_color_visuals(&self) -> egui::Color32;
70
71    /// Error text color.
72    fn fg_error_text_color_visuals(&self) -> egui::Color32;
73
74    /// Visual dark mode.
75    /// True specifies a dark mode, false specifies a light mode.
76    fn dark_mode_visuals(&self) -> bool;
77
78    fn window_shadow(&self) -> egui::epaint::Shadow {
79        egui::epaint::Shadow {
80            spread: 2.0,
81            blur: 16.0,
82            //color: egui::Color32::from_rgba_premultiplied(0, 0, 0, 96),
83            color: egui::Color32::from_rgba_premultiplied(19, 18, 18, 96),
84            ..Default::default()
85        }
86    }
87
88    fn popup_shadow(&self) -> egui::epaint::Shadow {
89        egui::epaint::Shadow {
90            spread: 2.0,
91            blur: 16.0,
92            color: egui::Color32::from_rgba_premultiplied(19, 18, 18, 96),
93            ..Default::default()
94        }
95    }
96
97    fn text_cursor(&self) -> egui::style::TextCursorStyle {
98        egui::style::TextCursorStyle {
99            stroke: egui::Stroke::new(
100                2.0,
101                self.fg_primary_text_color_visuals().unwrap_or_default(),
102            ),
103            ..Default::default()
104        }
105    }
106
107    fn resize_corner_size(&self) -> f32 {
108        12.0
109    }
110
111    /// Horizontal and vertical margins within a menu frame.
112    /// This value is used for all margins, in windows, panes, frames etc.
113    /// Using the same value will yield a more consistent look.
114    ///
115    /// - Egui default is 6.0
116    fn margin_style(&self) -> f32 {
117        6.0
118    }
119
120    /// Button size is text size plus this on each side.
121    ///
122    /// - Egui default is { x: 6.0, y: 4.0 }
123    //fn button_padding(&self) -> egui::Vec2;
124    fn button_padding(&self) -> egui::Vec2 {
125        egui::Vec2 { x: 6.0, y: 4.0 }
126    }
127
128    /// Horizontal and vertical spacing between widgets.
129    /// If you want to override this for special cases use the `add_space` method.
130    /// This single value is added for the x and y coordinates to yield a more consistent look.
131    ///
132    /// - Egui default is 4.0
133    fn item_spacing_style(&self) -> f32 {
134        4.0
135    }
136
137    /// Scroll bar width.
138    ///
139    /// - Egui default is 6.0
140    fn scroll_bar_width_style(&self) -> f32 {
141        6.0
142    }
143
144    /// Custom rounding value for all buttons and frames.
145    ///
146    /// - Egui default is 4.0
147    fn rounding_visuals(&self) -> f32 {
148        4.0
149    }
150
151    /// Controls the sizes and distances between widgets.
152    /// The following types of spacing are implemented.
153    ///
154    /// - Spacing
155    /// - Margin
156    /// - Button Padding
157    /// - Scroll Bar width
158    fn spacing_style(&self) -> egui::style::Spacing {
159        egui::style::Spacing {
160            item_spacing: egui::Vec2 {
161                x: self.item_spacing_style(),
162                y: self.item_spacing_style(),
163            },
164            window_margin: egui::Margin {
165                left: self.margin_style(),
166                right: self.margin_style(),
167                top: self.margin_style(),
168                bottom: self.margin_style(),
169            },
170            button_padding: self.button_padding(),
171            menu_margin: egui::Margin {
172                left: self.margin_style(),
173                right: self.margin_style(),
174                top: self.margin_style(),
175                bottom: self.margin_style(),
176            },
177            indent: 18.0,
178            interact_size: egui::Vec2 { x: 40.0, y: 20.0 },
179            slider_width: 100.0,
180            combo_width: 100.0,
181            text_edit_width: 280.0,
182            icon_width: 14.0,
183            icon_width_inner: 8.0,
184            icon_spacing: 6.0,
185            tooltip_width: 600.0,
186            indent_ends_with_horizontal_line: false,
187            combo_height: 200.0,
188            scroll: ScrollStyle {
189                bar_width: self.scroll_bar_width_style(),
190                handle_min_length: 12.0,
191                bar_inner_margin: 4.0,
192                bar_outer_margin: 0.0,
193                ..Default::default()
194            },
195            ..Default::default()
196        }
197    }
198
199    /// How and when interaction happens.
200    fn interaction_style(&self) -> egui::style::Interaction {
201        egui::style::Interaction {
202            resize_grab_radius_side: 5.0,
203            resize_grab_radius_corner: 10.0,
204            show_tooltips_only_when_still: true,
205            ..Default::default()
206        }
207    }
208
209    /// The style of a widget that you cannot interact with.
210    ///
211    /// `noninteractive.bg_stroke` is the outline of windows.
212    /// `noninteractive.bg_fill` is the background color of windows.
213    /// `noninteractive.fg_stroke` is the normal text color.
214    fn custom_noninteractive_widget_visuals(&self) -> egui::style::WidgetVisuals {
215        egui::style::WidgetVisuals {
216            bg_fill: self.bg_auxiliary_color_visuals(),
217            weak_bg_fill: self.bg_auxiliary_color_visuals(),
218            bg_stroke: egui::Stroke {
219                width: 1.0,
220                color: self.bg_auxiliary_color_visuals(),
221            },
222            rounding: egui::Rounding {
223                nw: self.rounding_visuals(),
224                ne: self.rounding_visuals(),
225                sw: self.rounding_visuals(),
226                se: self.rounding_visuals(),
227            },
228            fg_stroke: egui::Stroke {
229                width: 1.0,
230                color: self.fg_primary_text_color_visuals().unwrap_or_default(),
231            },
232            expansion: 0.0,
233        }
234    }
235
236    /// The style of an interactive widget, such as a button, at rest.
237    fn widget_inactive_visual(&self) -> egui::style::WidgetVisuals {
238        egui::style::WidgetVisuals {
239            bg_fill: self.bg_auxiliary_color_visuals(),
240            weak_bg_fill: self.bg_auxiliary_color_visuals(),
241            bg_stroke: egui::Stroke {
242                width: 0.0,
243                color: egui::Color32::from_rgba_premultiplied(0, 0, 0, 0),
244            },
245            rounding: egui::Rounding {
246                nw: self.rounding_visuals(),
247                ne: self.rounding_visuals(),
248                sw: self.rounding_visuals(),
249                se: self.rounding_visuals(),
250            },
251            fg_stroke: egui::Stroke {
252                width: 1.0,
253                color: self.fg_primary_text_color_visuals().unwrap_or_default(),
254            },
255            expansion: 0.0,
256        }
257    }
258
259    /// The style of an interactive widget while you hover it, or when it is highlighted
260    fn widget_hovered_visual(&self) -> egui::style::WidgetVisuals {
261        egui::style::WidgetVisuals {
262            bg_fill: self.bg_auxiliary_color_visuals(),
263            weak_bg_fill: self.bg_auxiliary_color_visuals(),
264            bg_stroke: egui::Stroke {
265                width: 1.0,
266                color: self.fg_primary_text_color_visuals().unwrap_or_default(), //self.bg_triage_color_visuals(),
267            },
268            rounding: egui::Rounding {
269                nw: self.rounding_visuals(),
270                ne: self.rounding_visuals(),
271                sw: self.rounding_visuals(),
272                se: self.rounding_visuals(),
273            },
274            fg_stroke: egui::Stroke {
275                width: 1.5,
276                color: self.fg_primary_text_color_visuals().unwrap_or_default(),
277            },
278            expansion: 2.0,
279        }
280    }
281
282    /// The style of an interactive widget as you are clicking or dragging it.
283    fn custom_active_widget_visual(&self) -> egui::style::WidgetVisuals {
284        egui::style::WidgetVisuals {
285            bg_fill: self.bg_primary_color_visuals(),
286            weak_bg_fill: self.primary_accent_color_visuals(),
287            bg_stroke: egui::Stroke {
288                width: 1.0,
289                color: self.bg_primary_color_visuals(),
290            },
291            rounding: egui::Rounding {
292                nw: self.rounding_visuals(),
293                ne: self.rounding_visuals(),
294                sw: self.rounding_visuals(),
295                se: self.rounding_visuals(),
296            },
297            fg_stroke: egui::Stroke {
298                width: 2.0,
299                color: self.fg_primary_text_color_visuals().unwrap_or_default(),
300            },
301            expansion: 1.0,
302        }
303    }
304
305    /// The style of a button that has an open menu beneath it (e.g. a combo-box)
306    fn custom_open_widget_visual(&self) -> egui::style::WidgetVisuals {
307        egui::style::WidgetVisuals {
308            bg_fill: self.bg_secondary_color_visuals(),
309            weak_bg_fill: self.bg_secondary_color_visuals(),
310            bg_stroke: egui::Stroke {
311                width: 1.0,
312                color: self.bg_triage_color_visuals(),
313            },
314            rounding: egui::Rounding {
315                nw: self.rounding_visuals(),
316                ne: self.rounding_visuals(),
317                sw: self.rounding_visuals(),
318                se: self.rounding_visuals(),
319            },
320            fg_stroke: egui::Stroke {
321                width: 1.0,
322                color: self.bg_contrast_color_visuals(),
323            },
324            expansion: 0.0,
325        }
326    }
327
328    /// Uses the primary and secondary accent colors to build a custom selection style.
329    fn custom_selection_visual(&self) -> egui::style::Selection {
330        egui::style::Selection {
331            bg_fill: self.primary_accent_color_visuals(),
332            stroke: egui::Stroke {
333                width: 1.0,
334                color: self.fg_primary_text_color_visuals().unwrap_or_default(),
335            },
336        }
337    }
338
339    /// Edit text styles.
340    /// This is literally just a copy and pasted version of eguis `default_text_styles` function.
341    fn custom_text_styles(&self) -> std::collections::BTreeMap<egui::TextStyle, egui::FontId> {
342        use egui::FontFamily::{Monospace, Proportional};
343        [
344            (
345                egui::TextStyle::Small,
346                egui::FontId::new(10.0, Proportional),
347            ),
348            (egui::TextStyle::Body, egui::FontId::new(14.0, Proportional)),
349            (
350                egui::TextStyle::Button,
351                egui::FontId::new(14.00, Proportional),
352            ),
353            (
354                egui::TextStyle::Heading,
355                egui::FontId::new(18.0, Proportional),
356            ),
357            (
358                egui::TextStyle::Monospace,
359                egui::FontId::new(12.0, Monospace),
360            ),
361        ]
362        .into()
363    }
364
365    /// Sets the custom style for the given original [`Style`](egui::Style).
366    /// Relies on all above trait methods to build the complete style.
367    ///
368    /// Specifies the look and feel of egui.
369    fn custom_style(&self) -> egui::Style {
370        egui::style::Style {
371            // override the text styles here: Option<egui::TextStyle>
372            override_text_style: None,
373
374            // override the font id here: Option<egui::FontId>
375            override_font_id: None,
376
377            // set your text styles here:
378            text_styles: self.custom_text_styles(),
379
380            // set your drag value text style:
381            // drag_value_text_style: egui::TextStyle,
382            spacing: self.spacing_style(),
383            interaction: self.interaction_style(),
384
385            visuals: egui::Visuals {
386                dark_mode: self.dark_mode_visuals(),
387                //override_text_color: self.fg_primary_text_color_visuals(),
388                widgets: egui::style::Widgets {
389                    noninteractive: self.custom_noninteractive_widget_visuals(),
390                    inactive: self.widget_inactive_visual(),
391                    hovered: self.widget_hovered_visual(),
392                    active: self.custom_active_widget_visual(),
393                    open: self.custom_open_widget_visual(),
394                },
395                selection: self.custom_selection_visual(),
396                hyperlink_color: self.bg_contrast_color_visuals(),
397                panel_fill: self.bg_primary_color_visuals(),
398                faint_bg_color: self.bg_secondary_color_visuals(),
399                extreme_bg_color: self.bg_triage_color_visuals(),
400                code_bg_color: self.bg_auxiliary_color_visuals(),
401                warn_fg_color: self.fg_warn_text_color_visuals(),
402                error_fg_color: self.fg_error_text_color_visuals(),
403                window_rounding: egui::Rounding {
404                    nw: self.rounding_visuals(),
405                    ne: self.rounding_visuals(),
406                    sw: self.rounding_visuals(),
407                    se: self.rounding_visuals(),
408                },
409                window_shadow: self.window_shadow(),
410                window_fill: self.bg_primary_color_visuals(),
411                window_stroke: egui::Stroke {
412                    width: 1.0,
413                    color: self.bg_contrast_color_visuals(),
414                },
415                menu_rounding: egui::Rounding {
416                    nw: self.rounding_visuals(),
417                    ne: self.rounding_visuals(),
418                    sw: self.rounding_visuals(),
419                    se: self.rounding_visuals(),
420                },
421                popup_shadow: self.popup_shadow(),
422                resize_corner_size: self.resize_corner_size(),
423                text_cursor: self.text_cursor(),
424                clip_rect_margin: 3.0,
425                button_frame: true,
426                collapsing_header_frame: true,
427                indent_has_left_vline: true,
428                striped: true,
429                slider_trailing_fill: true,
430                ..Default::default()
431            },
432            animation_time: 0.083_333_336,
433            explanation_tooltips: true,
434            ..Default::default()
435        }
436    }
437}
438
439impl std::fmt::Debug for dyn Aesthetix {
440    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
441        write!(f, "{}", self.name())
442    }
443}
444
445impl PartialEq for dyn Aesthetix {
446    fn eq(&self, other: &Self) -> bool {
447        self.name() == other.name()
448    }
449}