From 40c9441d5a6b0e91a1510091dd0adaf60b8b6a90 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 4 Aug 2026 13:51:45 +0200 Subject: [PATCH] refactor: split theme_plugin.rs into a `theme` module Move each type into its own file under `crates/egui/src/theme/`: `StyleProvider`, `ThemeCache`, `DefaultStyle`, and `Themes`. The module is renamed `egui::theme_plugin` -> `egui::theme`. Co-Authored-By: Claude Opus 5 (1M context) --- crates/egui/src/context.rs | 12 +- crates/egui/src/lib.rs | 2 +- .../default_style.rs} | 165 +----------------- crates/egui/src/theme/mod.rs | 38 ++++ crates/egui/src/theme/style_provider.rs | 17 ++ crates/egui/src/theme/theme_cache.rs | 41 +++++ crates/egui/src/theme/themes.rs | 82 +++++++++ examples/styling_engine/src/custom_engine.rs | 2 +- examples/styling_engine/src/main.rs | 2 +- 9 files changed, 193 insertions(+), 168 deletions(-) rename crates/egui/src/{theme_plugin.rs => theme/default_style.rs} (51%) create mode 100644 crates/egui/src/theme/mod.rs create mode 100644 crates/egui/src/theme/style_provider.rs create mode 100644 crates/egui/src/theme/theme_cache.rs create mode 100644 crates/egui/src/theme/themes.rs diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 019314a4b..4dc2e8a17 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -35,7 +35,7 @@ use crate::{ output::FullOutput, pass_state::PassState, plugin::{self, TypedPluginHandle}, - resize, response, scroll_area, theme_plugin, + resize, response, scroll_area, theme, util::IdTypeMap, viewport::ViewportClass, widget_style::{StyleArgs, WidgetStyle}, @@ -407,7 +407,7 @@ struct ContextImpl { loaders: Arc, - themes: theme_plugin::Themes, + themes: theme::Themes, } impl ContextImpl { @@ -2031,7 +2031,7 @@ impl Context { } impl Context { - /// Register a [`StyleProvider`](crate::theme_plugin::StyleProvider) for the specified widget type. + /// Register a [`StyleProvider`](crate::theme::StyleProvider) for the specified widget type. /// /// A theme can only be added once for a specified widget. /// If a theme is already registered for this widget, this is a no-op (useful for `eframe::run_simple_native`). @@ -2039,18 +2039,18 @@ impl Context { /// If you want to add the theme anyway, use [`Self::replace_widget_theme`] instead. pub fn add_widget_theme( &self, - theme: impl theme_plugin::StyleProvider + Send + Sync + 'static, + theme: impl theme::StyleProvider + Send + Sync + 'static, ) { self.write(|ctx| ctx.themes.register::(theme, false)); } - /// Register a [`StyleProvider`](crate::theme_plugin::StyleProvider) for the specified widget. + /// Register a [`StyleProvider`](crate::theme::StyleProvider) for the specified widget. /// /// Overwrite any theme already registered for the specified widget [`WidgetStyle`]. /// This allow to live edit a theme. pub fn replace_widget_theme( &self, - theme: impl theme_plugin::StyleProvider + Send + Sync + 'static, + theme: impl theme::StyleProvider + Send + Sync + 'static, ) { self.write(|ctx| ctx.themes.register::(theme, true)); } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 7d2f2d52f..cb159e446 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -417,7 +417,7 @@ pub mod response; mod sense; pub mod style; pub mod text_selection; -pub mod theme_plugin; +pub mod theme; mod ui; mod ui_builder; mod ui_stack; diff --git a/crates/egui/src/theme_plugin.rs b/crates/egui/src/theme/default_style.rs similarity index 51% rename from crates/egui/src/theme_plugin.rs rename to crates/egui/src/theme/default_style.rs index b889e991b..4842f415d 100644 --- a/crates/egui/src/theme_plugin.rs +++ b/crates/egui/src/theme/default_style.rs @@ -1,68 +1,17 @@ -use std::{any::TypeId, sync::Arc}; - use emath::Vec2; -use epaint::{Shadow, Stroke, mutex::Mutex, text::TextWrapMode}; +use epaint::{Shadow, Stroke, text::TextWrapMode}; use crate::{ - Frame, Id, TextStyle, Ui, - util::IdTypeMap, + Frame, TextStyle, + theme::StyleProvider, widget_style::{ - BaseStyle, ButtonStyle, CheckboxStyle, Classes, HasClasses as _, LabelStyle, - SELECTED_CLASS, SeparatorStyle, StyleArgs, TextVisuals, WidgetState, WidgetStyle, + BaseStyle, ButtonStyle, CheckboxStyle, HasClasses as _, LabelStyle, SELECTED_CLASS, + SeparatorStyle, StyleArgs, TextVisuals, WidgetState, }, }; -/// A cache that can be implemented to reduce computation time of a `StyleProvider` -#[derive(Debug, Default, Clone)] -pub struct ThemeCache { - cache: IdTypeMap, - inner: Theme, -} - -impl ThemeCache { - pub fn new(theme: Theme) -> Self { - Self { - cache: IdTypeMap::default(), - inner: theme, - } - } -} - -impl, S: WidgetStyle> StyleProvider for ThemeCache { - /// Access the cache for the requested [`WidgetStyle`] based on the [`Classes`] and - /// the [`WidgetState`] - /// - /// If no entry match the parameter then compute the fallback style and - /// save the output for later. - fn style(&mut self, modifiers: &StyleArgs<'_>) -> S { - let StyleArgs { classes, state, .. } = modifiers; - let style_id = Id::new((classes, state)); - if let Some(style) = self.cache.get_temp::(style_id) { - style - } else { - let style = self.inner.style(modifiers); - self.cache.insert_temp(style_id, style.clone()); - style - } - } -} - -/// A Theme plugin that implement a style computation for a defined `WidgetStyle` -pub trait StyleProvider { - /// The style according to the classes and state of the widget - fn style(&mut self, modifiers: &StyleArgs<'_>) -> S; - - /// Help to differ the different themes - fn theme_type_id(&self) -> TypeId - where - Self: 'static, - { - TypeId::of::() - } -} - #[derive(Debug, Clone)] -struct DefaultStyle; +pub(super) struct DefaultStyle; impl StyleProvider for DefaultStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> BaseStyle { @@ -203,105 +152,3 @@ impl StyleProvider for DefaultStyle { } } } - -impl Ui { - /// Access the register theme and fetch the requested [`WidgetStyle`]. - /// - /// Requested widget style must implement [`WidgetStyle`]. - pub fn widget_style( - &self, - id: crate::Id, - classes: &Classes, - ) -> S { - // Fetch the current state of the widget - let state = self - .read_response(id) - .map(|r| r.widget_state()) - .unwrap_or_default(); - - self.get_widget_style::(&StyleArgs { - classes, - state, - style: self.style(), - stack: self.stack(), - ctx: self, - }) - } -} - -pub struct Themes { - themes: IdTypeMap, -} - -type ThemeWrap = Arc + Send + Sync>>>; - -impl Default for Themes { - /// Register the default egui theme - fn default() -> Self { - let mut themes = IdTypeMap::default(); - - themes.insert_temp::>( - Id::NULL, - Arc::new(Mutex::new(Box::new(DefaultStyle))), - ); - - themes.insert_temp::>( - Id::NULL, - Arc::new(Mutex::new(Box::new(DefaultStyle))), - ); - - themes.insert_temp::>( - Id::NULL, - Arc::new(Mutex::new(Box::new(DefaultStyle))), - ); - - themes.insert_temp::>( - Id::NULL, - Arc::new(Mutex::new(Box::new(DefaultStyle))), - ); - - themes.insert_temp::>( - Id::NULL, - Arc::new(Mutex::new(Box::new(DefaultStyle))), - ); - - Self { themes } - } -} - -impl Themes { - /// Register a [`StyleProvider`] for the specified widget [`WidgetStyle`] `S` - /// - /// Existing themes are overwritten if `force` is `true` or the new theme differs. - pub(crate) fn register( - &mut self, - theme: impl StyleProvider + Send + Sync + 'static, - force: bool, - ) { - if !force - && self - .themes - .get_temp:: + Send + Sync>>>>(Id::NULL) - .is_some_and(|t| t.lock().theme_type_id() == theme.theme_type_id()) - { - return; - } - - self.themes - .insert_temp:: + Send + Sync>>>>( - Id::NULL, - Arc::new(Mutex::new(Box::new(theme))), - ); - } - - /// Fetch the style of the current theme - pub fn get( - &self, - ) -> Arc + Send + Sync>>> { - let v = self - .themes - .get_temp:: + Send + Sync>>>>(Id::NULL); - - v.unwrap_or_else(|| panic!("A style should be set for {:?}", std::any::type_name::())) - } -} diff --git a/crates/egui/src/theme/mod.rs b/crates/egui/src/theme/mod.rs new file mode 100644 index 000000000..9aa880831 --- /dev/null +++ b/crates/egui/src/theme/mod.rs @@ -0,0 +1,38 @@ +//! Theming: pluggable [`StyleProvider`]s that compute the style of each widget. + +mod default_style; +mod style_provider; +mod theme_cache; +mod themes; + +pub use self::{style_provider::StyleProvider, theme_cache::ThemeCache, themes::Themes}; + +use crate::{ + Ui, + widget_style::{Classes, StyleArgs, WidgetStyle}, +}; + +impl Ui { + /// Access the register theme and fetch the requested [`WidgetStyle`]. + /// + /// Requested widget style must implement [`WidgetStyle`]. + pub fn widget_style( + &self, + id: crate::Id, + classes: &Classes, + ) -> S { + // Fetch the current state of the widget + let state = self + .read_response(id) + .map(|r| r.widget_state()) + .unwrap_or_default(); + + self.get_widget_style::(&StyleArgs { + classes, + state, + style: self.style(), + stack: self.stack(), + ctx: self, + }) + } +} diff --git a/crates/egui/src/theme/style_provider.rs b/crates/egui/src/theme/style_provider.rs new file mode 100644 index 000000000..379da16c7 --- /dev/null +++ b/crates/egui/src/theme/style_provider.rs @@ -0,0 +1,17 @@ +use std::any::TypeId; + +use crate::widget_style::StyleArgs; + +/// A Theme plugin that implement a style computation for a defined `WidgetStyle` +pub trait StyleProvider { + /// The style according to the classes and state of the widget + fn style(&mut self, modifiers: &StyleArgs<'_>) -> S; + + /// Help to differ the different themes + fn theme_type_id(&self) -> TypeId + where + Self: 'static, + { + TypeId::of::() + } +} diff --git a/crates/egui/src/theme/theme_cache.rs b/crates/egui/src/theme/theme_cache.rs new file mode 100644 index 000000000..f9b2cdcaa --- /dev/null +++ b/crates/egui/src/theme/theme_cache.rs @@ -0,0 +1,41 @@ +use crate::{ + Id, + theme::StyleProvider, + util::IdTypeMap, + widget_style::{StyleArgs, WidgetStyle}, +}; + +/// A cache that can be implemented to reduce computation time of a `StyleProvider` +#[derive(Debug, Default, Clone)] +pub struct ThemeCache { + cache: IdTypeMap, + inner: Theme, +} + +impl ThemeCache { + pub fn new(theme: Theme) -> Self { + Self { + cache: IdTypeMap::default(), + inner: theme, + } + } +} + +impl, S: WidgetStyle> StyleProvider for ThemeCache { + /// Access the cache for the requested [`WidgetStyle`] based on the [`Classes`](crate::widget_style::Classes) and + /// the [`WidgetState`](crate::widget_style::WidgetState) + /// + /// If no entry match the parameter then compute the fallback style and + /// save the output for later. + fn style(&mut self, modifiers: &StyleArgs<'_>) -> S { + let StyleArgs { classes, state, .. } = modifiers; + let style_id = Id::new((classes, state)); + if let Some(style) = self.cache.get_temp::(style_id) { + style + } else { + let style = self.inner.style(modifiers); + self.cache.insert_temp(style_id, style.clone()); + style + } + } +} diff --git a/crates/egui/src/theme/themes.rs b/crates/egui/src/theme/themes.rs new file mode 100644 index 000000000..5f2ed9d75 --- /dev/null +++ b/crates/egui/src/theme/themes.rs @@ -0,0 +1,82 @@ +use std::sync::Arc; + +use epaint::mutex::Mutex; + +use crate::{ + Id, + theme::{StyleProvider, default_style::DefaultStyle}, + util::IdTypeMap, + widget_style::{ + BaseStyle, ButtonStyle, CheckboxStyle, LabelStyle, SeparatorStyle, WidgetStyle, + }, +}; + +pub struct Themes { + themes: IdTypeMap, +} + +type ThemeWrap = Arc + Send + Sync>>>; + +impl Default for Themes { + /// Register the default egui theme + fn default() -> Self { + let mut themes = IdTypeMap::default(); + + themes.insert_temp::>( + Id::NULL, + Arc::new(Mutex::new(Box::new(DefaultStyle))), + ); + + themes.insert_temp::>( + Id::NULL, + Arc::new(Mutex::new(Box::new(DefaultStyle))), + ); + + themes.insert_temp::>( + Id::NULL, + Arc::new(Mutex::new(Box::new(DefaultStyle))), + ); + + themes.insert_temp::>( + Id::NULL, + Arc::new(Mutex::new(Box::new(DefaultStyle))), + ); + + themes.insert_temp::>( + Id::NULL, + Arc::new(Mutex::new(Box::new(DefaultStyle))), + ); + + Self { themes } + } +} + +impl Themes { + /// Register a [`StyleProvider`] for the specified widget [`WidgetStyle`] `S` + /// + /// Existing themes are overwritten if `force` is `true` or the new theme differs. + pub(crate) fn register( + &mut self, + theme: impl StyleProvider + Send + Sync + 'static, + force: bool, + ) { + if !force + && self + .themes + .get_temp::>(Id::NULL) + .is_some_and(|t| t.lock().theme_type_id() == theme.theme_type_id()) + { + return; + } + + self.themes + .insert_temp::>(Id::NULL, Arc::new(Mutex::new(Box::new(theme)))); + } + + /// Fetch the style of the current theme + pub fn get(&self) -> ThemeWrap { + let v = self.themes.get_temp::>(Id::NULL); + + v.unwrap_or_else(|| panic!("A style should be set for {:?}", std::any::type_name::())) + } +} diff --git a/examples/styling_engine/src/custom_engine.rs b/examples/styling_engine/src/custom_engine.rs index b49336f65..f5e854a89 100644 --- a/examples/styling_engine/src/custom_engine.rs +++ b/examples/styling_engine/src/custom_engine.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use eframe::egui::{ Color32, - theme_plugin::StyleProvider, + theme::StyleProvider, widget_style::{BaseStyle, ButtonStyle, HasClasses as _, StyleArgs}, }; use logos::Logos; diff --git a/examples/styling_engine/src/main.rs b/examples/styling_engine/src/main.rs index 25ba96676..3e2222f50 100644 --- a/examples/styling_engine/src/main.rs +++ b/examples/styling_engine/src/main.rs @@ -3,7 +3,7 @@ use eframe::egui::{ self, Button, Frame, Margin, Panel, UiBuilder, - theme_plugin::ThemeCache, + theme::ThemeCache, widget_style::{ButtonStyle, HasClasses as _}, };