1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-02 06:40:06 -04:00

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) <noreply@anthropic.com>
This commit is contained in:
Emil Ernerfeldt
2026-08-04 13:51:45 +02:00
parent ae472fc1b1
commit 40c9441d5a
9 changed files with 193 additions and 168 deletions

View File

@@ -35,7 +35,7 @@ use crate::{
output::FullOutput, output::FullOutput,
pass_state::PassState, pass_state::PassState,
plugin::{self, TypedPluginHandle}, plugin::{self, TypedPluginHandle},
resize, response, scroll_area, theme_plugin, resize, response, scroll_area, theme,
util::IdTypeMap, util::IdTypeMap,
viewport::ViewportClass, viewport::ViewportClass,
widget_style::{StyleArgs, WidgetStyle}, widget_style::{StyleArgs, WidgetStyle},
@@ -407,7 +407,7 @@ struct ContextImpl {
loaders: Arc<Loaders>, loaders: Arc<Loaders>,
themes: theme_plugin::Themes, themes: theme::Themes,
} }
impl ContextImpl { impl ContextImpl {
@@ -2031,7 +2031,7 @@ impl Context {
} }
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. /// 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`). /// 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. /// 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::StyleProvider<S> + Send + Sync + 'static, theme: impl theme::StyleProvider<S> + Send + Sync + 'static,
) { ) {
self.write(|ctx| ctx.themes.register::<S>(theme, false)); self.write(|ctx| ctx.themes.register::<S>(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`]. /// Overwrite any theme already registered for the specified widget [`WidgetStyle`].
/// 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::StyleProvider<S> + Send + Sync + 'static, theme: impl theme::StyleProvider<S> + Send + Sync + 'static,
) { ) {
self.write(|ctx| ctx.themes.register::<S>(theme, true)); self.write(|ctx| ctx.themes.register::<S>(theme, true));
} }

View File

@@ -417,7 +417,7 @@ pub mod response;
mod sense; mod sense;
pub mod style; pub mod style;
pub mod text_selection; pub mod text_selection;
pub mod theme_plugin; pub mod theme;
mod ui; mod ui;
mod ui_builder; mod ui_builder;
mod ui_stack; mod ui_stack;

View File

@@ -1,68 +1,17 @@
use std::{any::TypeId, sync::Arc};
use emath::Vec2; use emath::Vec2;
use epaint::{Shadow, Stroke, mutex::Mutex, text::TextWrapMode}; use epaint::{Shadow, Stroke, text::TextWrapMode};
use crate::{ use crate::{
Frame, Id, TextStyle, Ui, Frame, TextStyle,
util::IdTypeMap, theme::StyleProvider,
widget_style::{ widget_style::{
BaseStyle, ButtonStyle, CheckboxStyle, Classes, HasClasses as _, LabelStyle, BaseStyle, ButtonStyle, CheckboxStyle, HasClasses as _, LabelStyle, SELECTED_CLASS,
SELECTED_CLASS, SeparatorStyle, StyleArgs, TextVisuals, WidgetState, WidgetStyle, SeparatorStyle, StyleArgs, TextVisuals, WidgetState,
}, },
}; };
/// A cache that can be implemented to reduce computation time of a `StyleProvider`
#[derive(Debug, Default, Clone)]
pub struct ThemeCache<Theme> {
cache: IdTypeMap,
inner: Theme,
}
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
/// 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::<S>(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<S> {
/// 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::<Self>()
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct DefaultStyle; pub(super) struct DefaultStyle;
impl StyleProvider<BaseStyle> for DefaultStyle { impl StyleProvider<BaseStyle> for DefaultStyle {
fn style(&mut self, modifiers: &StyleArgs<'_>) -> BaseStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> BaseStyle {
@@ -203,105 +152,3 @@ impl StyleProvider<SeparatorStyle> for DefaultStyle {
} }
} }
} }
impl Ui {
/// Access the register theme and fetch the requested [`WidgetStyle`].
///
/// Requested widget style must implement [`WidgetStyle`].
pub fn widget_style<S: WidgetStyle + Clone + 'static>(
&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::<S>(&StyleArgs {
classes,
state,
style: self.style(),
stack: self.stack(),
ctx: self,
})
}
}
pub struct Themes {
themes: IdTypeMap,
}
type ThemeWrap<S> = Arc<Mutex<Box<dyn StyleProvider<S> + Send + Sync>>>;
impl Default for Themes {
/// Register the default egui theme
fn default() -> Self {
let mut themes = IdTypeMap::default();
themes.insert_temp::<ThemeWrap<BaseStyle>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))),
);
themes.insert_temp::<ThemeWrap<ButtonStyle>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))),
);
themes.insert_temp::<ThemeWrap<SeparatorStyle>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))),
);
themes.insert_temp::<ThemeWrap<CheckboxStyle>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))),
);
themes.insert_temp::<ThemeWrap<LabelStyle>>(
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<S: WidgetStyle + 'static>(
&mut self,
theme: impl StyleProvider<S> + Send + Sync + 'static,
force: bool,
) {
if !force
&& self
.themes
.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())
{
return;
}
self.themes
.insert_temp::<Arc<Mutex<Box<dyn StyleProvider<S> + Send + Sync>>>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(theme))),
);
}
/// Fetch the style of the current theme
pub fn get<S: WidgetStyle + 'static>(
&self,
) -> Arc<Mutex<Box<dyn StyleProvider<S> + Send + Sync>>> {
let v = self
.themes
.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>()))
}
}

View File

@@ -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<S: WidgetStyle + Clone + 'static>(
&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::<S>(&StyleArgs {
classes,
state,
style: self.style(),
stack: self.stack(),
ctx: self,
})
}
}

View File

@@ -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<S> {
/// 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::<Self>()
}
}

View File

@@ -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<Theme> {
cache: IdTypeMap,
inner: Theme,
}
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`](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::<S>(style_id) {
style
} else {
let style = self.inner.style(modifiers);
self.cache.insert_temp(style_id, style.clone());
style
}
}
}

View File

@@ -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<S> = Arc<Mutex<Box<dyn StyleProvider<S> + Send + Sync>>>;
impl Default for Themes {
/// Register the default egui theme
fn default() -> Self {
let mut themes = IdTypeMap::default();
themes.insert_temp::<ThemeWrap<BaseStyle>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))),
);
themes.insert_temp::<ThemeWrap<ButtonStyle>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))),
);
themes.insert_temp::<ThemeWrap<SeparatorStyle>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))),
);
themes.insert_temp::<ThemeWrap<CheckboxStyle>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))),
);
themes.insert_temp::<ThemeWrap<LabelStyle>>(
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<S: WidgetStyle + 'static>(
&mut self,
theme: impl StyleProvider<S> + Send + Sync + 'static,
force: bool,
) {
if !force
&& self
.themes
.get_temp::<ThemeWrap<S>>(Id::NULL)
.is_some_and(|t| t.lock().theme_type_id() == theme.theme_type_id())
{
return;
}
self.themes
.insert_temp::<ThemeWrap<S>>(Id::NULL, Arc::new(Mutex::new(Box::new(theme))));
}
/// Fetch the style of the current theme
pub fn get<S: WidgetStyle + 'static>(&self) -> ThemeWrap<S> {
let v = self.themes.get_temp::<ThemeWrap<S>>(Id::NULL);
v.unwrap_or_else(|| panic!("A style should be set for {:?}", std::any::type_name::<S>()))
}
}

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use eframe::egui::{ use eframe::egui::{
Color32, Color32,
theme_plugin::StyleProvider, theme::StyleProvider,
widget_style::{BaseStyle, ButtonStyle, HasClasses as _, StyleArgs}, widget_style::{BaseStyle, ButtonStyle, HasClasses as _, StyleArgs},
}; };
use logos::Logos; use logos::Logos;

View File

@@ -3,7 +3,7 @@
use eframe::egui::{ use eframe::egui::{
self, Button, Frame, Margin, Panel, UiBuilder, self, Button, Frame, Margin, Panel, UiBuilder,
theme_plugin::ThemeCache, theme::ThemeCache,
widget_style::{ButtonStyle, HasClasses as _}, widget_style::{ButtonStyle, HasClasses as _},
}; };