Some half-finished ideas around how to improve the styling and theming story for egui.
Background
Styling for egui is currently supplied by egui::Style which controls spacing, colors, etc for the whole of egui. There is no convenient way of changing the syling of a portion of the UI, except for changing out or modifying the Style temporarily, and then changing it back.
We would like to have a system that can support CSS-like selectors, so that users can easily style their ui based on the Style Modifiers (see below):
It would be very beneficial if such styling could be set in a single text file and live-loaded.
Action plan
Proposal
Style modifiers
Here are some things that could influence the style of a widget:
- widget type (button, slider, label, …)
- interact state (disabled, inactive, active, hovered, active)
- text modifier (header, small, weak, strong, code, …)
- per-
Ui class (”settings_panel”)
- per-widget class (”exit_button”)
For instance, a user may want to change the sizes of all buttons within the "settings_panel".
The per-Ui identifier would need to be a hierarchial stack, so the query to a theme would be something like:
Give me the WidgetStyle for a button that is hovered that is nested in a “options”→”internals”
We could also consider having dark/light mode as a modifier, allowing users to specify both variants in one theme file.
WidgetStyle
Let’s start with this:
pub struct WidgetStyle {
/// Background color, stroke, margin, and shadow.
pub frame: Frame,
/// What font to use and at what size.
pub text: TextStyle,
/// Color and width of e.g. checkbox checkmark.
/// Also text color.
///
/// Note that this is different from the frame border color.
pub stroke: Stroke,
}
pub struct TextStyle {
pub font: FontId,
pub underlined: bool,
…
}
If each widget as given a WidgetStyle it could then use it both for sizing (frame margins and font size) and its visual styling. The current theme would select a WidgetStyle based on some given style modifiers, and its interaction state (computed at the start of the frame, thanks to #3936).
WidgetStyle would be used by all built-in widgets (button, checkbox, slider, …) but also each Window and Ui.
Example
fn button_ui(ui: &mut Ui, text: &str) {
let id = ui.next_auto_id(); // so we can read the interact state
let style = ui.style_of_interactive(id, "button");
let galley = ui.format_text(style, text);
let (rect, response) = ui.allocate(galley.size + style.margin.size);
style.frame.paint(rect, ui);
style.painter().text(rect, galley);
}
Speed
We must make sure egui isn’t slowed down by this new theming. We should be able to aggressively cache the WidgetStyle lookups based on a hash of the input modifiers.
Theme plugins
We could start by having a plugin system for the theming, something like:
trait ThemePlugin {
fn widget_visuals(&self, modifiers: &StyleModifiers) -> WidgetStyle;
}
We could then start with a simple rule engine, but still allow users to implement much more advanced ones (e.g. more and more CSS-like).
Rule-engine
Eventually we want a fully customizable sytem where rules set in one theme file will control the look of the whole UI. Such a rule system has a few open questions to resolve:
- How do we distinguish between different modifier types? Do we need to?
- How do we specify if a rule applies to:
- The widget
- The widget and all children
- Just the children
Rules
The rules can apply partial settings or modifiers. For instance, a rule can set the font and increase the brightness of the text.
Exactly how to specify the rules (i.e. in what language) is outside the scope of this issue, but here is a few examples of the kind of rules one could maybe want to do:
button hovered: {
stroke.color.intensity: +2
}
// Make disabled things less bright:
disabled: {
frame.fill.intensity: -2
stroke.color.intensity: -2
}
// Make hovered interactive widgets brighter:
interactive hovered: {
frame.fill.intensity -2
stoke.colors.intensity: -2
}
small: {
text.size: -2
}
heading: {
text.size: 20
}
code: {
text.font: "monospace"
frame.fill: "gray"
}
weak: {
frame.fill.intensity: -2
stoke.colors.intensity: -2
}
strong: {
frame.fill.intensity: +2
stoke.colors.intensity: +2
}
hyperlink: {
stoke.colors.intensity: "blue"
text.underlined: true
}
window: {
frame.fill: "gray" // wait, this will add fill for all children of windows!?
}
Color palette
We also need a color palette, indexable by brightness and opacity
https://www.radix-ui.com/colors/docs/palette-composition/understanding-the-scale
// Color modifiers
intensity +2 // modify
opacity 50% // set
In the GUI code users should be able to refer to colors both using aliases (”blue”, “header”, …) and hard-coded colors (#ff0000).
Dark mode vs light mode
We should also consider supporting both light and dark mode within the same theme. That is, one theme file should be able to set both a dark and a light theme. Perhaps “dark” and “light” is just another style modifier?
Some half-finished ideas around how to improve the styling and theming story for egui.
Background
Styling for egui is currently supplied by
egui::Stylewhich controls spacing, colors, etc for the whole of egui. There is no convenient way of changing the syling of a portion of the UI, except for changing out or modifying theStyletemporarily, and then changing it back.We would like to have a system that can support CSS-like selectors, so that users can easily style their ui based on the Style Modifiers (see below):
It would be very beneficial if such styling could be set in a single text file and live-loaded.
Action plan
Border#4019StyleModifiers("classes")WidgetStyleselection it via a plugin system (ThemePlugin).ThemePluginStyleModifiersProposal
Style modifiers
Here are some things that could influence the style of a widget:
Uiclass (”settings_panel”)For instance, a user may want to change the sizes of all buttons within the
"settings_panel".The per-
Uiidentifier would need to be a hierarchial stack, so the query to a theme would be something like:We could also consider having dark/light mode as a modifier, allowing users to specify both variants in one theme file.
WidgetStyle
Let’s start with this:
If each widget as given a
WidgetStyleit could then use it both for sizing (framemargins and font size) and its visual styling. The current theme would select aWidgetStylebased on some given style modifiers, and its interaction state (computed at the start of the frame, thanks to #3936).WidgetStylewould be used by all built-in widgets (button, checkbox, slider, …) but also eachWindowandUi.Example
Speed
We must make sure
eguiisn’t slowed down by this new theming. We should be able to aggressively cache theWidgetStylelookups based on a hash of the input modifiers.Theme plugins
We could start by having a plugin system for the theming, something like:
We could then start with a simple rule engine, but still allow users to implement much more advanced ones (e.g. more and more CSS-like).
Rule-engine
Eventually we want a fully customizable sytem where rules set in one theme file will control the look of the whole UI. Such a rule system has a few open questions to resolve:
Rules
The rules can apply partial settings or modifiers. For instance, a rule can set the font and increase the brightness of the text.
Exactly how to specify the rules (i.e. in what language) is outside the scope of this issue, but here is a few examples of the kind of rules one could maybe want to do:
Color palette
We also need a color palette, indexable by brightness and opacity
https://www.radix-ui.com/colors/docs/palette-composition/understanding-the-scale
In the GUI code users should be able to refer to colors both using aliases (”blue”, “header”, …) and hard-coded colors (
#ff0000).Dark mode vs light mode
We should also consider supporting both light and dark mode within the same theme. That is, one theme file should be able to set both a dark and a light theme. Perhaps “dark” and “light” is just another style modifier?