mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 21:30:03 -04:00
clean history
This commit is contained in:
@@ -35,9 +35,10 @@ use crate::{
|
||||
output::FullOutput,
|
||||
pass_state::PassState,
|
||||
plugin::{self, TypedPluginHandle},
|
||||
resize, response, scroll_area,
|
||||
resize, response, scroll_area, theme_plugin,
|
||||
util::IdTypeMap,
|
||||
viewport::ViewportClass,
|
||||
widget_style::{Classes, StyleStruct, WidgetState},
|
||||
};
|
||||
|
||||
use crate::IdMap;
|
||||
@@ -405,6 +406,8 @@ struct ContextImpl {
|
||||
is_accesskit_enabled: bool,
|
||||
|
||||
loaders: Arc<Loaders>,
|
||||
|
||||
themes: theme_plugin::Themes,
|
||||
}
|
||||
|
||||
impl ContextImpl {
|
||||
@@ -2027,6 +2030,43 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Register a [`ThemeStyle`](crate::theme_plugin::ThemeStyle) for the specified widget [`StyleStruct`](StyleStruct) `S`
|
||||
///
|
||||
/// A theme can only be added once for a specified widget.
|
||||
/// This way it's convenient to add themes in `eframe::run_simple_native`.
|
||||
/// If you want to add the theme anyway, use [`Self::replace_theme`] instead.
|
||||
pub fn add_theme<S: StyleStruct + 'static>(
|
||||
&self,
|
||||
theme: impl theme_plugin::ThemeStyle<S> + Send + Sync + 'static,
|
||||
) {
|
||||
self.write(|ctx| ctx.themes.register::<S>(theme, false));
|
||||
}
|
||||
|
||||
/// Register a [`ThemeStyle`](crate::theme_plugin::ThemeStyle) for the specified widget [`StyleStruct`](StyleStruct) `S`
|
||||
///
|
||||
/// Overwrite any theme already registered for the specified widget [`StyleStruct`](StyleStruct).
|
||||
/// If you want to avoid overwriting existing theme, use [`Self::add_theme`] instead.
|
||||
pub fn replace_theme<S: StyleStruct + 'static>(
|
||||
&self,
|
||||
theme: impl theme_plugin::ThemeStyle<S> + Send + Sync + 'static,
|
||||
) {
|
||||
self.write(|ctx| ctx.themes.register::<S>(theme, true));
|
||||
}
|
||||
|
||||
/// Compute the [`StyleStruct`] using the registered theme if available.
|
||||
///
|
||||
/// Return `None` if no theme was registered for the [`StyleStruct`]
|
||||
pub fn get_style<S: StyleStruct + Clone + 'static>(
|
||||
&self,
|
||||
classes: &Classes,
|
||||
state: WidgetState,
|
||||
base: &Style,
|
||||
) -> Option<S> {
|
||||
self.write(move |ctx| ctx.themes.get::<S>(classes, state, base))
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Tell `egui` which fonts to use.
|
||||
///
|
||||
|
||||
@@ -417,6 +417,7 @@ pub mod response;
|
||||
mod sense;
|
||||
pub mod style;
|
||||
pub mod text_selection;
|
||||
pub mod theme_plugin;
|
||||
mod ui;
|
||||
mod ui_builder;
|
||||
mod ui_stack;
|
||||
|
||||
113
crates/egui/src/theme_plugin.rs
Normal file
113
crates/egui/src/theme_plugin.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use epaint::mutex::Mutex;
|
||||
|
||||
use crate::{
|
||||
Id, Style, Ui,
|
||||
util::IdTypeMap,
|
||||
widget_style::{Classes, StyleStruct, WidgetState},
|
||||
};
|
||||
|
||||
/// A cache that can be implemented to reduce computation time of a `ThemeStyle`
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ThemeCache {
|
||||
cache: IdTypeMap,
|
||||
}
|
||||
|
||||
impl ThemeCache {
|
||||
/// Access the cache for the requested [`StyleStruct`] 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<S: StyleStruct + 'static>(
|
||||
&mut self,
|
||||
classes: &Classes,
|
||||
state: WidgetState,
|
||||
fallback: impl FnOnce() -> S,
|
||||
) -> S {
|
||||
let style_id = Id::new(classes).with(state);
|
||||
if let Some(style) = self.cache.get_temp::<S>(style_id) {
|
||||
style
|
||||
} else {
|
||||
let style = fallback();
|
||||
self.cache.insert_temp(style_id, style.clone());
|
||||
style
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Theme plugin that implement a style computation for a defined `StyleStruct`
|
||||
pub trait ThemeStyle<S> {
|
||||
/// The style according to the classes and state of the widget
|
||||
fn style(&mut self, classes: &Classes, state: WidgetState, base: &Style) -> S;
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
/// Access the installed theme plugin if there is one and fetch the requested widget style if it exist.
|
||||
/// Fallback to the default style if not found.
|
||||
///
|
||||
/// Requested widget style must implement [`StyleStruct`].
|
||||
pub fn widget_style<S: StyleStruct + Clone + 'static>(
|
||||
&self,
|
||||
id: crate::Id,
|
||||
classes: &Classes,
|
||||
) -> S {
|
||||
// If the requested `StyleStruct` is cached, return it without computing.
|
||||
// Otherwise proceed to compute the style from the widget information.
|
||||
|
||||
// Fetch the current state of the widget
|
||||
let state = self
|
||||
.ctx()
|
||||
.read_response(id)
|
||||
.map(|r| r.widget_state())
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(style) = self.get_style::<S>(classes, state, self.style()) {
|
||||
style
|
||||
} else {
|
||||
S::default_style(classes, state, self.style())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Themes {
|
||||
themes: IdTypeMap,
|
||||
}
|
||||
|
||||
impl Themes {
|
||||
/// Register a theme and the style associated
|
||||
pub(crate) fn register<S: StyleStruct + 'static>(
|
||||
&mut self,
|
||||
theme: impl ThemeStyle<S> + Send + Sync + 'static,
|
||||
force: bool,
|
||||
) {
|
||||
if !force
|
||||
&& self
|
||||
.themes
|
||||
.get_temp::<Arc<Mutex<Box<dyn ThemeStyle<S> + Send + Sync>>>>(Id::NULL)
|
||||
.is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.themes
|
||||
.insert_temp::<Arc<Mutex<Box<dyn ThemeStyle<S> + Send + Sync>>>>(
|
||||
Id::NULL,
|
||||
Arc::new(Mutex::new(Box::new(theme))),
|
||||
);
|
||||
}
|
||||
|
||||
/// Fetch the style of the current theme
|
||||
pub(crate) fn get<S: StyleStruct + 'static>(
|
||||
&self,
|
||||
classes: &Classes,
|
||||
state: WidgetState,
|
||||
base: &Style,
|
||||
) -> Option<S> {
|
||||
let v = self
|
||||
.themes
|
||||
.get_temp::<Arc<Mutex<Box<dyn ThemeStyle<S> + Send + Sync>>>>(Id::NULL);
|
||||
v.map(|engine| engine.lock().style(classes, state, base))
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::{borrow::Cow, fmt};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{self, Debug},
|
||||
};
|
||||
|
||||
use emath::Vec2;
|
||||
use epaint::{Color32, FontId, Shadow, Stroke, text::TextWrapMode};
|
||||
@@ -9,7 +12,15 @@ use crate::{
|
||||
style::{WidgetVisuals, Widgets},
|
||||
};
|
||||
|
||||
/// Each dedicated style must implement this trait to be used in the theme plugin system
|
||||
pub trait StyleStruct: Debug + Clone + Send + Sync + std::any::Any + 'static {
|
||||
fn default_style(classes: &Classes, state: WidgetState, base: &Style) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// General text style
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextVisuals {
|
||||
/// Font used
|
||||
pub font_id: FontId,
|
||||
@@ -23,6 +34,7 @@ pub struct TextVisuals {
|
||||
}
|
||||
|
||||
/// General widget style
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WidgetStyle {
|
||||
pub frame: Frame,
|
||||
|
||||
@@ -31,13 +43,69 @@ pub struct WidgetStyle {
|
||||
pub stroke: Stroke,
|
||||
}
|
||||
|
||||
impl StyleStruct for WidgetStyle {
|
||||
fn default_style(_classes: &Classes, state: WidgetState, base: &Style) -> Self {
|
||||
let visuals = base.visuals.widgets.state(state);
|
||||
let font_id = base.override_font_id.clone();
|
||||
Self {
|
||||
frame: Frame {
|
||||
fill: visuals.bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
corner_radius: visuals.corner_radius,
|
||||
inner_margin: base.spacing.button_padding.into(),
|
||||
..Default::default()
|
||||
},
|
||||
stroke: visuals.fg_stroke,
|
||||
text: TextVisuals {
|
||||
color: base
|
||||
.visuals
|
||||
.override_text_color
|
||||
.unwrap_or_else(|| visuals.text_color()),
|
||||
font_id: font_id.unwrap_or_else(|| TextStyle::Body.resolve(base)),
|
||||
strikethrough: Stroke::NONE,
|
||||
underline: Stroke::NONE,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dedicated button style
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ButtonStyle {
|
||||
pub frame: Frame,
|
||||
pub text_style: TextVisuals,
|
||||
}
|
||||
|
||||
impl StyleStruct for ButtonStyle {
|
||||
fn default_style(classes: &Classes, state: WidgetState, base: &Style) -> Self {
|
||||
let mut visuals = *base.visuals.widgets.state(state);
|
||||
let mut ws = WidgetStyle::default_style(classes, state, base);
|
||||
|
||||
if classes.has(SELECTED_CLASS) {
|
||||
visuals.weak_bg_fill = base.visuals.selection.bg_fill;
|
||||
visuals.bg_fill = base.visuals.selection.bg_fill;
|
||||
visuals.fg_stroke = base.visuals.selection.stroke;
|
||||
ws.text.color = base.visuals.selection.stroke.color;
|
||||
}
|
||||
|
||||
Self {
|
||||
frame: Frame {
|
||||
fill: visuals.weak_bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
corner_radius: visuals.corner_radius,
|
||||
outer_margin: (-Vec2::splat(visuals.expansion)).into(),
|
||||
inner_margin: (base.spacing.button_padding + Vec2::splat(visuals.expansion)
|
||||
- Vec2::splat(visuals.bg_stroke.width))
|
||||
.into(),
|
||||
..Default::default()
|
||||
},
|
||||
text_style: ws.text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dedicated checkbox style
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CheckboxStyle {
|
||||
/// Frame around
|
||||
pub frame: Frame,
|
||||
@@ -58,7 +126,28 @@ pub struct CheckboxStyle {
|
||||
pub check_stroke: Stroke,
|
||||
}
|
||||
|
||||
impl StyleStruct for CheckboxStyle {
|
||||
fn default_style(classes: &Classes, state: WidgetState, base: &Style) -> Self {
|
||||
let visuals = base.visuals.widgets.state(state);
|
||||
let ws = WidgetStyle::default_style(classes, state, base);
|
||||
Self {
|
||||
frame: Frame::new(),
|
||||
checkbox_size: base.spacing.icon_width,
|
||||
check_size: base.spacing.icon_width_inner,
|
||||
checkbox_frame: Frame {
|
||||
fill: visuals.bg_fill,
|
||||
corner_radius: visuals.corner_radius,
|
||||
stroke: visuals.bg_stroke,
|
||||
..Default::default()
|
||||
},
|
||||
text_style: ws.text,
|
||||
check_stroke: ws.stroke,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dedicated label style
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LabelStyle {
|
||||
/// Frame around
|
||||
pub frame: Frame,
|
||||
@@ -71,6 +160,7 @@ pub struct LabelStyle {
|
||||
}
|
||||
|
||||
/// Dedicated separator style
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SeparatorStyle {
|
||||
/// How much space is allocated in the layout direction
|
||||
pub spacing: f32,
|
||||
@@ -80,7 +170,7 @@ pub struct SeparatorStyle {
|
||||
}
|
||||
|
||||
/// The different state of a widget can be
|
||||
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum WidgetState {
|
||||
Noninteractive,
|
||||
#[default]
|
||||
@@ -231,7 +321,7 @@ pub type ClassName = Cow<'static, str>;
|
||||
///
|
||||
/// This can be used by styling engine to compute a different style
|
||||
/// based on the set of classes present on the widget/Ui.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, Hash)]
|
||||
pub struct Classes {
|
||||
classes: SmallVec<[ClassName; 5]>,
|
||||
}
|
||||
@@ -315,4 +405,9 @@ pub trait HasClasses {
|
||||
fn has(&self, class: impl Into<ClassName>) -> bool {
|
||||
self.classes().classes.contains(&class.into())
|
||||
}
|
||||
|
||||
/// The list of class
|
||||
fn list(&self) -> Vec<ClassName> {
|
||||
self.classes().classes.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +328,8 @@ impl<'a> Button<'a> {
|
||||
|
||||
classes.add_class_if(SELECTED_CLASS, selected.unwrap_or(false));
|
||||
|
||||
let ButtonStyle { frame, text_style } = ui.style().button_style(&classes, state);
|
||||
// let ButtonStyle { frame, text_style } = ui.style().button_style(&classes, state);
|
||||
let ButtonStyle { frame, text_style } = ui.widget_style(id, &classes);
|
||||
|
||||
let mut button_padding = if has_frame_margin {
|
||||
frame.inner_margin
|
||||
|
||||
Reference in New Issue
Block a user