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