From 9cb223fae5c7876d87ed790900e96b9c4b569b54 Mon Sep 17 00:00:00 2001 From: adrien <221212@umons.ac.be> Date: Wed, 8 Jul 2026 17:09:20 +0200 Subject: [PATCH] Better cache, StyleArgs and deadlock avoidance --- crates/egui/src/context.rs | 15 ++- crates/egui/src/theme_plugin.rs | 111 ++++++++++--------- crates/egui/src/widget_style.rs | 14 ++- examples/styling_engine/src/custom_engine.rs | 54 +++++---- examples/styling_engine/src/main.rs | 3 +- 5 files changed, 106 insertions(+), 91 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index ec38ad815..a9129e0b2 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -38,7 +38,7 @@ use crate::{ resize, response, scroll_area, theme_plugin, util::IdTypeMap, viewport::ViewportClass, - widget_style::{Classes, WidgetState, WidgetStyle}, + widget_style::{StyleArgs, WidgetStyle}, }; use crate::IdMap; @@ -2039,7 +2039,7 @@ 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::ThemeStyle + Send + Sync + 'static, + theme: impl theme_plugin::StyleProvider + Send + Sync + 'static, ) { self.write(|ctx| ctx.themes.register::(theme, false)); } @@ -2050,19 +2050,18 @@ impl Context { /// This allow to live edit a theme. pub fn replace_widget_theme( &self, - theme: impl theme_plugin::ThemeStyle + Send + Sync + 'static, + theme: impl theme_plugin::StyleProvider + Send + Sync + 'static, ) { self.write(|ctx| ctx.themes.register::(theme, true)); } /// Compute the [`WidgetStyle`] using the registered theme. - pub(crate) fn get_widget_style( + pub fn get_widget_style( &self, - ui: &Ui, - classes: &Classes, - state: WidgetState, + modifiers: &StyleArgs<'_>, ) -> S { - self.read(move |ctx| ctx.themes.get::(ui, classes, state)) + let theme = self.read(move |ctx| ctx.themes.get::()); + theme.lock().style(modifiers) } } diff --git a/crates/egui/src/theme_plugin.rs b/crates/egui/src/theme_plugin.rs index 1d4b9dc78..3d55fd8a3 100644 --- a/crates/egui/src/theme_plugin.rs +++ b/crates/egui/src/theme_plugin.rs @@ -8,33 +8,39 @@ use crate::{ util::IdTypeMap, widget_style::{ BaseStyle, ButtonStyle, CheckboxStyle, Classes, HasClasses as _, LabelStyle, - SELECTED_CLASS, SeparatorStyle, TextVisuals, WidgetState, WidgetStyle, + SELECTED_CLASS, SeparatorStyle, StyleArgs, TextVisuals, WidgetState, WidgetStyle, }, }; /// A cache that can be implemented to reduce computation time of a `ThemeStyle` #[derive(Debug, Default, Clone)] -pub struct ThemeCache { +pub struct ThemeCache { cache: IdTypeMap, + inner: Theme, } -impl ThemeCache { +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. - pub fn get( - &mut self, - classes: &Classes, - state: WidgetState, - fallback: impl FnOnce() -> S, - ) -> S { + fn style(&mut self, modifiers: &StyleArgs<'_>) -> S { + let StyleArgs { classes, state, .. } = modifiers; let style_id = Id::new(classes).with(state); if let Some(style) = self.cache.get_temp::(style_id) { style } else { - let style = fallback(); + let style = self.inner.style(modifiers); self.cache.insert_temp(style_id, style.clone()); style } @@ -42,9 +48,9 @@ impl ThemeCache { } /// A Theme plugin that implement a style computation for a defined `WidgetStyle` -pub trait ThemeStyle { +pub trait StyleProvider { /// The style according to the classes and state of the widget - fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> S; + fn style(&mut self, modifiers: &StyleArgs<'_>) -> S; /// Help to differ the different themes fn theme_type_id(&self) -> TypeId @@ -58,9 +64,9 @@ pub trait ThemeStyle { #[derive(Debug, Clone)] struct DefaultStyle; -impl ThemeStyle for DefaultStyle { - fn style(&mut self, ui: &Ui, _classes: &Classes, state: WidgetState) -> BaseStyle { - let style = ui.style(); +impl StyleProvider for DefaultStyle { + fn style(&mut self, modifiers: &StyleArgs<'_>) -> BaseStyle { + let StyleArgs { style, state, .. } = modifiers; let spacing = &style.spacing; let widget_visuals = match state { WidgetState::Noninteractive => style.visuals.widgets.noninteractive, @@ -80,7 +86,8 @@ impl ThemeStyle for DefaultStyle { stroke: widget_visuals.fg_stroke, text: TextVisuals { color: widget_visuals.text_color(), - font_id: style + font_id: modifiers + .style .override_font_id .clone() .unwrap_or_else(|| TextStyle::Body.resolve(style)), @@ -91,9 +98,15 @@ impl ThemeStyle for DefaultStyle { } } -impl ThemeStyle for DefaultStyle { - fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> ButtonStyle { - let style = ui.style(); +impl StyleProvider for DefaultStyle { + fn style(&mut self, modifiers: &StyleArgs<'_>) -> ButtonStyle { + let StyleArgs { + ctx, + classes, + style, + state, + .. + } = modifiers; let spacing = &style.spacing; let mut widget_visuals = match state { WidgetState::Noninteractive => style.visuals.widgets.noninteractive, @@ -102,7 +115,7 @@ impl ThemeStyle for DefaultStyle { WidgetState::Active => style.visuals.widgets.active, }; - let mut ws: BaseStyle = ui.get_widget_style(classes, state); + let mut ws: BaseStyle = ctx.get_widget_style(modifiers); if classes.has(SELECTED_CLASS) { let visuals = &style.visuals; @@ -128,9 +141,11 @@ impl ThemeStyle for DefaultStyle { } } -impl ThemeStyle for DefaultStyle { - fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> CheckboxStyle { - let style = ui.style(); +impl StyleProvider for DefaultStyle { + fn style(&mut self, modifiers: &StyleArgs<'_>) -> CheckboxStyle { + let StyleArgs { + ctx, style, state, .. + } = modifiers; let spacing = &style.spacing; let widget_visuals = match state { WidgetState::Noninteractive => style.visuals.widgets.noninteractive, @@ -139,7 +154,7 @@ impl ThemeStyle for DefaultStyle { WidgetState::Active => style.visuals.widgets.active, }; - let ws: BaseStyle = ui.get_widget_style(classes, state); + let ws: BaseStyle = ctx.get_widget_style(modifiers); CheckboxStyle { frame: Frame::new(), @@ -157,9 +172,10 @@ impl ThemeStyle for DefaultStyle { } } -impl ThemeStyle for DefaultStyle { - fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> LabelStyle { - let ws: BaseStyle = ui.get_widget_style(classes, state); +impl StyleProvider for DefaultStyle { + fn style(&mut self, modifiers: &StyleArgs<'_>) -> LabelStyle { + let StyleArgs { ctx, .. } = modifiers; + let ws: BaseStyle = ctx.get_widget_style(modifiers); LabelStyle { frame: Frame { @@ -176,9 +192,10 @@ impl ThemeStyle for DefaultStyle { } } -impl ThemeStyle for DefaultStyle { - fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> SeparatorStyle { - let ws: BaseStyle = ui.get_widget_style(classes, state); +impl StyleProvider for DefaultStyle { + fn style(&mut self, modifiers: &StyleArgs<'_>) -> SeparatorStyle { + let StyleArgs { ctx, .. } = modifiers; + let ws: BaseStyle = ctx.get_widget_style(modifiers); SeparatorStyle { spacing: 6.0, @@ -202,16 +219,13 @@ impl Ui { .map(|r| r.widget_state()) .unwrap_or_default(); - self.get_widget_style::(classes, state) - } - - /// Compute the [`WidgetStyle`] using the registered theme. - pub fn get_widget_style( - &self, - classes: &Classes, - state: WidgetState, - ) -> S { - self.ctx().get_widget_style(self, classes, state) + self.get_widget_style::(&StyleArgs { + classes, + state, + style: self.style(), + stack: self.stack(), + ctx: self, + }) } } @@ -219,7 +233,7 @@ pub struct Themes { themes: IdTypeMap, } -type ThemeWrap = Arc + Send + Sync>>>; +type ThemeWrap = Arc + Send + Sync>>>; impl Default for Themes { /// Register the default egui theme @@ -261,20 +275,20 @@ impl Themes { /// Existing themes are overwritten if `force` is `true` or the new theme differs. pub(crate) fn register( &mut self, - theme: impl ThemeStyle + Send + Sync + 'static, + theme: impl StyleProvider + Send + Sync + 'static, force: bool, ) { if !force && self .themes - .get_temp:: + Send + Sync>>>>(Id::NULL) + .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>>>>( + .insert_temp:: + Send + Sync>>>>( Id::NULL, Arc::new(Mutex::new(Box::new(theme))), ); @@ -283,16 +297,11 @@ impl Themes { /// Fetch the style of the current theme pub fn get( &self, - ui: &Ui, - classes: &Classes, - state: WidgetState, - ) -> S { + ) -> Arc + Send + Sync>>> { let v = self .themes - .get_temp:: + Send + Sync>>>>(Id::NULL); + .get_temp:: + Send + Sync>>>>(Id::NULL); v.unwrap_or_else(|| panic!("A style should be set for {:?}", std::any::type_name::())) - .lock() - .style(ui, classes, state) } } diff --git a/crates/egui/src/widget_style.rs b/crates/egui/src/widget_style.rs index 4bb2a1c18..ccb1f96f1 100644 --- a/crates/egui/src/widget_style.rs +++ b/crates/egui/src/widget_style.rs @@ -7,7 +7,7 @@ use epaint::{Color32, FontId, Stroke, text::TextWrapMode}; use smallvec::SmallVec; use crate::{ - Frame, Response, TextBuffer as _, + Context, Frame, Response, Style, TextBuffer as _, UiStack, style::{WidgetVisuals, Widgets}, }; @@ -235,7 +235,15 @@ pub trait HasClasses { } /// The list of class - fn list(&self) -> Vec { - self.classes().classes.to_vec() + fn as_slice(&self) -> &[ClassName] { + &self.classes().classes } } + +pub struct StyleArgs<'a> { + pub classes: &'a Classes, + pub state: WidgetState, + pub stack: &'a UiStack, + pub style: &'a Style, + pub ctx: &'a Context, +} diff --git a/examples/styling_engine/src/custom_engine.rs b/examples/styling_engine/src/custom_engine.rs index 9af27d7b6..b49336f65 100644 --- a/examples/styling_engine/src/custom_engine.rs +++ b/examples/styling_engine/src/custom_engine.rs @@ -1,16 +1,15 @@ use std::collections::HashMap; use eframe::egui::{ - Color32, Ui, - theme_plugin::{ThemeCache, ThemeStyle}, - widget_style::{BaseStyle, ButtonStyle, Classes, HasClasses as _, WidgetState}, + Color32, + theme_plugin::StyleProvider, + widget_style::{BaseStyle, ButtonStyle, HasClasses as _, StyleArgs}, }; use logos::Logos; #[derive(Debug, Default, Clone)] pub struct ESSEngine { info: HashMap>, - cache: ThemeCache, } impl ESSEngine { @@ -76,35 +75,34 @@ impl ESSEngine { } } -impl ThemeStyle for ESSEngine { - fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> ButtonStyle { - self.cache.get(classes, state, || { - let base = ui.get_widget_style::(classes, state); - let mut default = ButtonStyle { - frame: base.frame, - text_style: base.text, - }; - for class in classes.list() { - if let Some(properties) = self.info.get(&class.to_string()) { - for (property, value) in properties { - match property.as_str() { - "fill" => { - if let Value::Color(color) = value { - default.frame.fill = *color; - } +impl StyleProvider for ESSEngine { + fn style(&mut self, modifiers: &StyleArgs<'_>) -> ButtonStyle { + let StyleArgs { classes, ctx, .. } = modifiers; + let base = ctx.get_widget_style::(modifiers); + let mut default = ButtonStyle { + frame: base.frame, + text_style: base.text, + }; + for class in classes.as_slice() { + if let Some(properties) = self.info.get(&class.to_string()) { + for (property, value) in properties { + match property.as_str() { + "fill" => { + if let Value::Color(color) = value { + default.frame.fill = *color; } - "border" => { - if let Value::Number(size) = value { - default.frame.stroke.width = *size as f32; - } - } - _ => {} } + "border" => { + if let Value::Number(size) = value { + default.frame.stroke.width = *size as f32; + } + } + _ => {} } } } - default - }) + } + default } } diff --git a/examples/styling_engine/src/main.rs b/examples/styling_engine/src/main.rs index e55e04666..25ba96676 100644 --- a/examples/styling_engine/src/main.rs +++ b/examples/styling_engine/src/main.rs @@ -3,6 +3,7 @@ use eframe::egui::{ self, Button, Frame, Margin, Panel, UiBuilder, + theme_plugin::ThemeCache, widget_style::{ButtonStyle, HasClasses as _}, }; @@ -35,7 +36,7 @@ fn main() -> eframe::Result { eframe::run_ui_native("My egui App", options, move |ui, _frame| { // Register the theme plugin and which style they implement if let Ok(engine) = ESSEngine::try_parse(&style_code) { - ui.add_widget_theme::(engine); + ui.add_widget_theme::(ThemeCache::new(engine)); } ui.scope_builder(UiBuilder::new().with_class("body"), |ui| {