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:
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
38
crates/egui/src/theme/mod.rs
Normal file
38
crates/egui/src/theme/mod.rs
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
17
crates/egui/src/theme/style_provider.rs
Normal file
17
crates/egui/src/theme/style_provider.rs
Normal 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>()
|
||||||
|
}
|
||||||
|
}
|
||||||
41
crates/egui/src/theme/theme_cache.rs
Normal file
41
crates/egui/src/theme/theme_cache.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
crates/egui/src/theme/themes.rs
Normal file
82
crates/egui/src/theme/themes.rs
Normal 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>()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 _},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user