1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-03 07:10:04 -04:00

Better cache, StyleArgs and deadlock avoidance

This commit is contained in:
adrien
2026-07-08 17:09:20 +02:00
parent a0d9f9ba90
commit 9cb223fae5
5 changed files with 106 additions and 91 deletions

View File

@@ -38,7 +38,7 @@ use crate::{
resize, response, scroll_area, theme_plugin, resize, response, scroll_area, theme_plugin,
util::IdTypeMap, util::IdTypeMap,
viewport::ViewportClass, viewport::ViewportClass,
widget_style::{Classes, WidgetState, WidgetStyle}, widget_style::{StyleArgs, WidgetStyle},
}; };
use crate::IdMap; use crate::IdMap;
@@ -2039,7 +2039,7 @@ impl Context {
/// If you want to add the theme anyway, use [`Self::replace_widget_theme`] instead. /// If you want to add the theme anyway, use [`Self::replace_widget_theme`] instead.
pub fn add_widget_theme<S: WidgetStyle + 'static>( pub fn add_widget_theme<S: WidgetStyle + 'static>(
&self, &self,
theme: impl theme_plugin::ThemeStyle<S> + Send + Sync + 'static, theme: impl theme_plugin::StyleProvider<S> + Send + Sync + 'static,
) { ) {
self.write(|ctx| ctx.themes.register::<S>(theme, false)); self.write(|ctx| ctx.themes.register::<S>(theme, false));
} }
@@ -2050,19 +2050,18 @@ impl Context {
/// This allow to live edit a theme. /// This allow to live edit a theme.
pub fn replace_widget_theme<S: WidgetStyle + 'static>( pub fn replace_widget_theme<S: WidgetStyle + 'static>(
&self, &self,
theme: impl theme_plugin::ThemeStyle<S> + Send + Sync + 'static, theme: impl theme_plugin::StyleProvider<S> + Send + Sync + 'static,
) { ) {
self.write(|ctx| ctx.themes.register::<S>(theme, true)); self.write(|ctx| ctx.themes.register::<S>(theme, true));
} }
/// Compute the [`WidgetStyle`] using the registered theme. /// Compute the [`WidgetStyle`] using the registered theme.
pub(crate) fn get_widget_style<S: WidgetStyle + Clone + 'static>( pub fn get_widget_style<S: WidgetStyle + Clone + 'static>(
&self, &self,
ui: &Ui, modifiers: &StyleArgs<'_>,
classes: &Classes,
state: WidgetState,
) -> S { ) -> S {
self.read(move |ctx| ctx.themes.get::<S>(ui, classes, state)) let theme = self.read(move |ctx| ctx.themes.get::<S>());
theme.lock().style(modifiers)
} }
} }

View File

@@ -8,33 +8,39 @@ use crate::{
util::IdTypeMap, util::IdTypeMap,
widget_style::{ widget_style::{
BaseStyle, ButtonStyle, CheckboxStyle, Classes, HasClasses as _, LabelStyle, 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` /// A cache that can be implemented to reduce computation time of a `ThemeStyle`
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct ThemeCache { pub struct ThemeCache<Theme> {
cache: IdTypeMap, cache: IdTypeMap,
inner: Theme,
} }
impl ThemeCache { impl<Theme> ThemeCache<Theme> {
pub fn new(theme: Theme) -> Self {
Self {
cache: IdTypeMap::default(),
inner: theme,
}
}
}
impl<Theme: StyleProvider<S>, S: WidgetStyle> StyleProvider<S> for ThemeCache<Theme> {
/// Access the cache for the requested [`WidgetStyle`] based on the [`Classes`] and /// Access the cache for the requested [`WidgetStyle`] based on the [`Classes`] and
/// the [`WidgetState`] /// the [`WidgetState`]
/// ///
/// If no entry match the parameter then compute the fallback style and /// If no entry match the parameter then compute the fallback style and
/// save the output for later. /// save the output for later.
pub fn get<S: WidgetStyle + 'static>( fn style(&mut self, modifiers: &StyleArgs<'_>) -> S {
&mut self, let StyleArgs { classes, state, .. } = modifiers;
classes: &Classes,
state: WidgetState,
fallback: impl FnOnce() -> S,
) -> S {
let style_id = Id::new(classes).with(state); let style_id = Id::new(classes).with(state);
if let Some(style) = self.cache.get_temp::<S>(style_id) { if let Some(style) = self.cache.get_temp::<S>(style_id) {
style style
} else { } else {
let style = fallback(); let style = self.inner.style(modifiers);
self.cache.insert_temp(style_id, style.clone()); self.cache.insert_temp(style_id, style.clone());
style style
} }
@@ -42,9 +48,9 @@ impl ThemeCache {
} }
/// A Theme plugin that implement a style computation for a defined `WidgetStyle` /// A Theme plugin that implement a style computation for a defined `WidgetStyle`
pub trait ThemeStyle<S> { pub trait StyleProvider<S> {
/// The style according to the classes and state of the widget /// 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 /// Help to differ the different themes
fn theme_type_id(&self) -> TypeId fn theme_type_id(&self) -> TypeId
@@ -58,9 +64,9 @@ pub trait ThemeStyle<S> {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct DefaultStyle; struct DefaultStyle;
impl ThemeStyle<BaseStyle> for DefaultStyle { impl StyleProvider<BaseStyle> for DefaultStyle {
fn style(&mut self, ui: &Ui, _classes: &Classes, state: WidgetState) -> BaseStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> BaseStyle {
let style = ui.style(); let StyleArgs { style, state, .. } = modifiers;
let spacing = &style.spacing; let spacing = &style.spacing;
let widget_visuals = match state { let widget_visuals = match state {
WidgetState::Noninteractive => style.visuals.widgets.noninteractive, WidgetState::Noninteractive => style.visuals.widgets.noninteractive,
@@ -80,7 +86,8 @@ impl ThemeStyle<BaseStyle> for DefaultStyle {
stroke: widget_visuals.fg_stroke, stroke: widget_visuals.fg_stroke,
text: TextVisuals { text: TextVisuals {
color: widget_visuals.text_color(), color: widget_visuals.text_color(),
font_id: style font_id: modifiers
.style
.override_font_id .override_font_id
.clone() .clone()
.unwrap_or_else(|| TextStyle::Body.resolve(style)), .unwrap_or_else(|| TextStyle::Body.resolve(style)),
@@ -91,9 +98,15 @@ impl ThemeStyle<BaseStyle> for DefaultStyle {
} }
} }
impl ThemeStyle<ButtonStyle> for DefaultStyle { impl StyleProvider<ButtonStyle> for DefaultStyle {
fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> ButtonStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> ButtonStyle {
let style = ui.style(); let StyleArgs {
ctx,
classes,
style,
state,
..
} = modifiers;
let spacing = &style.spacing; let spacing = &style.spacing;
let mut widget_visuals = match state { let mut widget_visuals = match state {
WidgetState::Noninteractive => style.visuals.widgets.noninteractive, WidgetState::Noninteractive => style.visuals.widgets.noninteractive,
@@ -102,7 +115,7 @@ impl ThemeStyle<ButtonStyle> for DefaultStyle {
WidgetState::Active => style.visuals.widgets.active, 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) { if classes.has(SELECTED_CLASS) {
let visuals = &style.visuals; let visuals = &style.visuals;
@@ -128,9 +141,11 @@ impl ThemeStyle<ButtonStyle> for DefaultStyle {
} }
} }
impl ThemeStyle<CheckboxStyle> for DefaultStyle { impl StyleProvider<CheckboxStyle> for DefaultStyle {
fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> CheckboxStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> CheckboxStyle {
let style = ui.style(); let StyleArgs {
ctx, style, state, ..
} = modifiers;
let spacing = &style.spacing; let spacing = &style.spacing;
let widget_visuals = match state { let widget_visuals = match state {
WidgetState::Noninteractive => style.visuals.widgets.noninteractive, WidgetState::Noninteractive => style.visuals.widgets.noninteractive,
@@ -139,7 +154,7 @@ impl ThemeStyle<CheckboxStyle> for DefaultStyle {
WidgetState::Active => style.visuals.widgets.active, WidgetState::Active => style.visuals.widgets.active,
}; };
let ws: BaseStyle = ui.get_widget_style(classes, state); let ws: BaseStyle = ctx.get_widget_style(modifiers);
CheckboxStyle { CheckboxStyle {
frame: Frame::new(), frame: Frame::new(),
@@ -157,9 +172,10 @@ impl ThemeStyle<CheckboxStyle> for DefaultStyle {
} }
} }
impl ThemeStyle<LabelStyle> for DefaultStyle { impl StyleProvider<LabelStyle> for DefaultStyle {
fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> LabelStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> LabelStyle {
let ws: BaseStyle = ui.get_widget_style(classes, state); let StyleArgs { ctx, .. } = modifiers;
let ws: BaseStyle = ctx.get_widget_style(modifiers);
LabelStyle { LabelStyle {
frame: Frame { frame: Frame {
@@ -176,9 +192,10 @@ impl ThemeStyle<LabelStyle> for DefaultStyle {
} }
} }
impl ThemeStyle<SeparatorStyle> for DefaultStyle { impl StyleProvider<SeparatorStyle> for DefaultStyle {
fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> SeparatorStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> SeparatorStyle {
let ws: BaseStyle = ui.get_widget_style(classes, state); let StyleArgs { ctx, .. } = modifiers;
let ws: BaseStyle = ctx.get_widget_style(modifiers);
SeparatorStyle { SeparatorStyle {
spacing: 6.0, spacing: 6.0,
@@ -202,16 +219,13 @@ impl Ui {
.map(|r| r.widget_state()) .map(|r| r.widget_state())
.unwrap_or_default(); .unwrap_or_default();
self.get_widget_style::<S>(classes, state) self.get_widget_style::<S>(&StyleArgs {
} classes,
state,
/// Compute the [`WidgetStyle`] using the registered theme. style: self.style(),
pub fn get_widget_style<S: WidgetStyle + Clone + 'static>( stack: self.stack(),
&self, ctx: self,
classes: &Classes, })
state: WidgetState,
) -> S {
self.ctx().get_widget_style(self, classes, state)
} }
} }
@@ -219,7 +233,7 @@ pub struct Themes {
themes: IdTypeMap, themes: IdTypeMap,
} }
type ThemeWrap<S> = Arc<Mutex<Box<dyn ThemeStyle<S> + Send + Sync>>>; type ThemeWrap<S> = Arc<Mutex<Box<dyn StyleProvider<S> + Send + Sync>>>;
impl Default for Themes { impl Default for Themes {
/// Register the default egui theme /// Register the default egui theme
@@ -261,20 +275,20 @@ impl Themes {
/// Existing themes are overwritten if `force` is `true` or the new theme differs. /// Existing themes are overwritten if `force` is `true` or the new theme differs.
pub(crate) fn register<S: WidgetStyle + 'static>( pub(crate) fn register<S: WidgetStyle + 'static>(
&mut self, &mut self,
theme: impl ThemeStyle<S> + Send + Sync + 'static, theme: impl StyleProvider<S> + Send + Sync + 'static,
force: bool, force: bool,
) { ) {
if !force if !force
&& self && self
.themes .themes
.get_temp::<Arc<Mutex<Box<dyn ThemeStyle<S> + Send + Sync>>>>(Id::NULL) .get_temp::<Arc<Mutex<Box<dyn StyleProvider<S> + Send + Sync>>>>(Id::NULL)
.is_some_and(|t| t.lock().theme_type_id() == theme.theme_type_id()) .is_some_and(|t| t.lock().theme_type_id() == theme.theme_type_id())
{ {
return; return;
} }
self.themes self.themes
.insert_temp::<Arc<Mutex<Box<dyn ThemeStyle<S> + Send + Sync>>>>( .insert_temp::<Arc<Mutex<Box<dyn StyleProvider<S> + Send + Sync>>>>(
Id::NULL, Id::NULL,
Arc::new(Mutex::new(Box::new(theme))), Arc::new(Mutex::new(Box::new(theme))),
); );
@@ -283,16 +297,11 @@ impl Themes {
/// Fetch the style of the current theme /// Fetch the style of the current theme
pub fn get<S: WidgetStyle + 'static>( pub fn get<S: WidgetStyle + 'static>(
&self, &self,
ui: &Ui, ) -> Arc<Mutex<Box<dyn StyleProvider<S> + Send + Sync>>> {
classes: &Classes,
state: WidgetState,
) -> S {
let v = self let v = self
.themes .themes
.get_temp::<Arc<Mutex<Box<dyn ThemeStyle<S> + Send + Sync>>>>(Id::NULL); .get_temp::<Arc<Mutex<Box<dyn StyleProvider<S> + Send + Sync>>>>(Id::NULL);
v.unwrap_or_else(|| panic!("A style should be set for {:?}", std::any::type_name::<S>())) v.unwrap_or_else(|| panic!("A style should be set for {:?}", std::any::type_name::<S>()))
.lock()
.style(ui, classes, state)
} }
} }

View File

@@ -7,7 +7,7 @@ use epaint::{Color32, FontId, Stroke, text::TextWrapMode};
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::{ use crate::{
Frame, Response, TextBuffer as _, Context, Frame, Response, Style, TextBuffer as _, UiStack,
style::{WidgetVisuals, Widgets}, style::{WidgetVisuals, Widgets},
}; };
@@ -235,7 +235,15 @@ pub trait HasClasses {
} }
/// The list of class /// The list of class
fn list(&self) -> Vec<ClassName> { fn as_slice(&self) -> &[ClassName] {
self.classes().classes.to_vec() &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,
}

View File

@@ -1,16 +1,15 @@
use std::collections::HashMap; use std::collections::HashMap;
use eframe::egui::{ use eframe::egui::{
Color32, Ui, Color32,
theme_plugin::{ThemeCache, ThemeStyle}, theme_plugin::StyleProvider,
widget_style::{BaseStyle, ButtonStyle, Classes, HasClasses as _, WidgetState}, widget_style::{BaseStyle, ButtonStyle, HasClasses as _, StyleArgs},
}; };
use logos::Logos; use logos::Logos;
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct ESSEngine { pub struct ESSEngine {
info: HashMap<String, Vec<(String, Value)>>, info: HashMap<String, Vec<(String, Value)>>,
cache: ThemeCache,
} }
impl ESSEngine { impl ESSEngine {
@@ -76,15 +75,15 @@ impl ESSEngine {
} }
} }
impl ThemeStyle<ButtonStyle> for ESSEngine { impl StyleProvider<ButtonStyle> for ESSEngine {
fn style(&mut self, ui: &Ui, classes: &Classes, state: WidgetState) -> ButtonStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> ButtonStyle {
self.cache.get(classes, state, || { let StyleArgs { classes, ctx, .. } = modifiers;
let base = ui.get_widget_style::<BaseStyle>(classes, state); let base = ctx.get_widget_style::<BaseStyle>(modifiers);
let mut default = ButtonStyle { let mut default = ButtonStyle {
frame: base.frame, frame: base.frame,
text_style: base.text, text_style: base.text,
}; };
for class in classes.list() { for class in classes.as_slice() {
if let Some(properties) = self.info.get(&class.to_string()) { if let Some(properties) = self.info.get(&class.to_string()) {
for (property, value) in properties { for (property, value) in properties {
match property.as_str() { match property.as_str() {
@@ -104,7 +103,6 @@ impl ThemeStyle<ButtonStyle> for ESSEngine {
} }
} }
default default
})
} }
} }

View File

@@ -3,6 +3,7 @@
use eframe::egui::{ use eframe::egui::{
self, Button, Frame, Margin, Panel, UiBuilder, self, Button, Frame, Margin, Panel, UiBuilder,
theme_plugin::ThemeCache,
widget_style::{ButtonStyle, HasClasses as _}, widget_style::{ButtonStyle, HasClasses as _},
}; };
@@ -35,7 +36,7 @@ fn main() -> eframe::Result {
eframe::run_ui_native("My egui App", options, move |ui, _frame| { eframe::run_ui_native("My egui App", options, move |ui, _frame| {
// Register the theme plugin and which style they implement // Register the theme plugin and which style they implement
if let Ok(engine) = ESSEngine::try_parse(&style_code) { if let Ok(engine) = ESSEngine::try_parse(&style_code) {
ui.add_widget_theme::<ButtonStyle>(engine); ui.add_widget_theme::<ButtonStyle>(ThemeCache::new(engine));
} }
ui.scope_builder(UiBuilder::new().with_class("body"), |ui| { ui.scope_builder(UiBuilder::new().with_class("body"), |ui| {