mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Renaming, comment & better default system
This commit is contained in:
@@ -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, StyleStruct, WidgetState},
|
widget_style::{Classes, WidgetState, WidgetStyle},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::IdMap;
|
use crate::IdMap;
|
||||||
@@ -2031,37 +2031,36 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Register a [`ThemeStyle`](crate::theme_plugin::ThemeStyle) for the specified widget [`StyleStruct`](StyleStruct) `S`
|
/// Register a [`ThemeStyle`](crate::theme_plugin::ThemeStyle) 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.
|
||||||
/// This way it's convenient to add themes in `eframe::run_simple_native`.
|
/// If a theme is already registered for this widget, this is a no-op (useful for `eframe::run_simple_native`).
|
||||||
/// If you want to add the theme anyway, use [`Self::replace_theme`] instead.
|
///
|
||||||
pub fn add_theme<S: StyleStruct + 'static>(
|
/// If you want to add the theme anyway, use [`Self::replace_widget_theme`] instead.
|
||||||
|
pub fn add_widget_theme<S: WidgetStyle + 'static>(
|
||||||
&self,
|
&self,
|
||||||
theme: impl theme_plugin::ThemeStyle<S> + Send + Sync + 'static,
|
theme: impl theme_plugin::ThemeStyle<S> + Send + Sync + 'static,
|
||||||
) {
|
) {
|
||||||
self.write(|ctx| ctx.themes.register::<S>(theme, false));
|
self.write(|ctx| ctx.themes.register::<S>(theme, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a [`ThemeStyle`](crate::theme_plugin::ThemeStyle) for the specified widget [`StyleStruct`](StyleStruct) `S`
|
/// Register a [`ThemeStyle`](crate::theme_plugin::ThemeStyle) for the specified widget.
|
||||||
///
|
///
|
||||||
/// Overwrite any theme already registered for the specified widget [`StyleStruct`](StyleStruct).
|
/// Overwrite any theme already registered for the specified widget [`WidgetStyle`](WidgetStyle).
|
||||||
/// If you want to avoid overwriting existing theme, use [`Self::add_theme`] instead.
|
/// This allow to live edit a theme.
|
||||||
pub fn replace_theme<S: StyleStruct + 'static>(
|
pub fn replace_widget_theme<S: WidgetStyle + 'static>(
|
||||||
&self,
|
&self,
|
||||||
theme: impl theme_plugin::ThemeStyle<S> + Send + Sync + 'static,
|
theme: impl theme_plugin::ThemeStyle<S> + Send + Sync + 'static,
|
||||||
) {
|
) {
|
||||||
self.write(|ctx| ctx.themes.register::<S>(theme, true));
|
self.write(|ctx| ctx.themes.register::<S>(theme, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the [`StyleStruct`] using the registered theme if available.
|
/// Compute the [`WidgetStyle`] using the registered theme.
|
||||||
///
|
pub(crate) fn get_widget_style<S: WidgetStyle + Clone + 'static>(
|
||||||
/// Return `None` if no theme was registered for the [`StyleStruct`]
|
|
||||||
pub fn get_style<S: StyleStruct + Clone + 'static>(
|
|
||||||
&self,
|
&self,
|
||||||
classes: &Classes,
|
classes: &Classes,
|
||||||
state: WidgetState,
|
state: WidgetState,
|
||||||
) -> Option<S> {
|
) -> S {
|
||||||
self.write(move |ctx| ctx.themes.get::<S>(classes, state))
|
self.write(move |ctx| ctx.themes.get::<S>(classes, state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
use std::sync::Arc;
|
use std::{any::TypeId, sync::Arc};
|
||||||
|
|
||||||
use epaint::mutex::Mutex;
|
use emath::Vec2;
|
||||||
|
use epaint::{Shadow, Stroke, mutex::Mutex, text::TextWrapMode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Id, Ui,
|
Frame, Id, Spacing, Style, TextStyle, Ui, Visuals,
|
||||||
|
style::Widgets,
|
||||||
util::IdTypeMap,
|
util::IdTypeMap,
|
||||||
widget_style::{Classes, StyleStruct, WidgetState},
|
widget_style::{
|
||||||
|
BaseStyle, ButtonStyle, CheckboxStyle, Classes, HasClasses as _, LabelStyle,
|
||||||
|
SELECTED_CLASS, SeparatorStyle, 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`
|
||||||
@@ -15,12 +20,12 @@ pub struct ThemeCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ThemeCache {
|
impl ThemeCache {
|
||||||
/// Access the cache for the requested [`StyleStruct`] 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: StyleStruct + 'static>(
|
pub fn get<S: WidgetStyle + 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
classes: &Classes,
|
classes: &Classes,
|
||||||
state: WidgetState,
|
state: WidgetState,
|
||||||
@@ -37,45 +42,201 @@ impl ThemeCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Theme plugin that implement a style computation for a defined `StyleStruct`
|
/// A Theme plugin that implement a style computation for a defined `WidgetStyle`
|
||||||
pub trait ThemeStyle<S> {
|
pub trait ThemeStyle<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, classes: &Classes, state: WidgetState) -> S;
|
fn style(&mut self, themes: &Themes, classes: &Classes, state: WidgetState) -> S;
|
||||||
|
|
||||||
|
/// Help to differ the different themes
|
||||||
|
fn theme_type_id(&self) -> TypeId
|
||||||
|
where
|
||||||
|
Self: 'static,
|
||||||
|
{
|
||||||
|
TypeId::of::<Self>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct DefaultStyle;
|
||||||
|
|
||||||
|
impl ThemeStyle<BaseStyle> for DefaultStyle {
|
||||||
|
fn style(&mut self, _themes: &Themes, _classes: &Classes, state: WidgetState) -> BaseStyle {
|
||||||
|
let visuals = Widgets::dark();
|
||||||
|
let spacing = Spacing::default();
|
||||||
|
|
||||||
|
let visuals = match state {
|
||||||
|
WidgetState::Noninteractive => visuals.noninteractive,
|
||||||
|
WidgetState::Inactive => visuals.inactive,
|
||||||
|
WidgetState::Hovered => visuals.hovered,
|
||||||
|
WidgetState::Active => visuals.active,
|
||||||
|
};
|
||||||
|
|
||||||
|
BaseStyle {
|
||||||
|
frame: Frame {
|
||||||
|
fill: visuals.bg_fill,
|
||||||
|
stroke: visuals.bg_stroke,
|
||||||
|
corner_radius: visuals.corner_radius,
|
||||||
|
inner_margin: spacing.button_padding.into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
stroke: visuals.fg_stroke,
|
||||||
|
text: TextVisuals {
|
||||||
|
color: visuals.text_color(),
|
||||||
|
font_id: TextStyle::Body.resolve(&Style::default()),
|
||||||
|
strikethrough: Stroke::NONE,
|
||||||
|
underline: Stroke::NONE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeStyle<ButtonStyle> for DefaultStyle {
|
||||||
|
fn style(&mut self, themes: &Themes, classes: &Classes, state: WidgetState) -> ButtonStyle {
|
||||||
|
let widget_visuals = Widgets::dark();
|
||||||
|
let spacing = Spacing::default();
|
||||||
|
|
||||||
|
let mut widget_visuals = match state {
|
||||||
|
WidgetState::Noninteractive => widget_visuals.noninteractive,
|
||||||
|
WidgetState::Inactive => widget_visuals.inactive,
|
||||||
|
WidgetState::Hovered => widget_visuals.hovered,
|
||||||
|
WidgetState::Active => widget_visuals.active,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ws: BaseStyle = themes.get(classes, state);
|
||||||
|
|
||||||
|
if classes.has(SELECTED_CLASS) {
|
||||||
|
let visuals = Visuals::default();
|
||||||
|
widget_visuals.weak_bg_fill = visuals.selection.bg_fill;
|
||||||
|
widget_visuals.bg_fill = visuals.selection.bg_fill;
|
||||||
|
widget_visuals.fg_stroke = visuals.selection.stroke;
|
||||||
|
ws.text.color = visuals.selection.stroke.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonStyle {
|
||||||
|
frame: Frame {
|
||||||
|
fill: widget_visuals.weak_bg_fill,
|
||||||
|
stroke: widget_visuals.bg_stroke,
|
||||||
|
corner_radius: widget_visuals.corner_radius,
|
||||||
|
outer_margin: (-Vec2::splat(widget_visuals.expansion)).into(),
|
||||||
|
inner_margin: (spacing.button_padding + Vec2::splat(widget_visuals.expansion)
|
||||||
|
- Vec2::splat(widget_visuals.bg_stroke.width))
|
||||||
|
.into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
text_style: ws.text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeStyle<CheckboxStyle> for DefaultStyle {
|
||||||
|
fn style(&mut self, themes: &Themes, classes: &Classes, state: WidgetState) -> CheckboxStyle {
|
||||||
|
let widget_visuals = Widgets::dark();
|
||||||
|
let spacing = Spacing::default();
|
||||||
|
|
||||||
|
let widget_visuals = match state {
|
||||||
|
WidgetState::Noninteractive => widget_visuals.noninteractive,
|
||||||
|
WidgetState::Inactive => widget_visuals.inactive,
|
||||||
|
WidgetState::Hovered => widget_visuals.hovered,
|
||||||
|
WidgetState::Active => widget_visuals.active,
|
||||||
|
};
|
||||||
|
|
||||||
|
let ws: BaseStyle = themes.get(classes, state);
|
||||||
|
|
||||||
|
CheckboxStyle {
|
||||||
|
frame: Frame::new(),
|
||||||
|
checkbox_size: spacing.icon_width,
|
||||||
|
check_size: spacing.icon_width_inner,
|
||||||
|
checkbox_frame: Frame {
|
||||||
|
fill: widget_visuals.bg_fill,
|
||||||
|
corner_radius: widget_visuals.corner_radius,
|
||||||
|
stroke: widget_visuals.bg_stroke,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
text_style: ws.text,
|
||||||
|
check_stroke: ws.stroke,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeStyle<LabelStyle> for DefaultStyle {
|
||||||
|
fn style(&mut self, themes: &Themes, classes: &Classes, state: WidgetState) -> LabelStyle {
|
||||||
|
let ws: BaseStyle = themes.get(classes, state);
|
||||||
|
|
||||||
|
LabelStyle {
|
||||||
|
frame: Frame {
|
||||||
|
fill: ws.frame.fill,
|
||||||
|
inner_margin: 0.0.into(),
|
||||||
|
outer_margin: 0.0.into(),
|
||||||
|
stroke: Stroke::NONE,
|
||||||
|
shadow: Shadow::NONE,
|
||||||
|
corner_radius: 0.into(),
|
||||||
|
},
|
||||||
|
text: ws.text,
|
||||||
|
wrap_mode: TextWrapMode::Wrap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeStyle<SeparatorStyle> for DefaultStyle {
|
||||||
|
fn style(&mut self, themes: &Themes, classes: &Classes, state: WidgetState) -> SeparatorStyle {
|
||||||
|
let ws: BaseStyle = themes.get(classes, state);
|
||||||
|
|
||||||
|
SeparatorStyle {
|
||||||
|
spacing: 6.0,
|
||||||
|
stroke: ws.frame.stroke,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ui {
|
impl Ui {
|
||||||
/// Access the installed theme plugin if there is one and fetch the requested widget style if it exist.
|
/// Access the register theme and fetch the requested [`WidgetStyle`].
|
||||||
/// Fallback to the default style if not found.
|
|
||||||
///
|
///
|
||||||
/// Requested widget style must implement [`StyleStruct`].
|
/// Requested widget style must implement [`WidgetStyle`].
|
||||||
pub fn widget_style<S: StyleStruct + Clone + 'static>(
|
pub fn widget_style<S: WidgetStyle + Clone + 'static>(
|
||||||
&self,
|
&self,
|
||||||
id: crate::Id,
|
id: crate::Id,
|
||||||
classes: &Classes,
|
classes: &Classes,
|
||||||
) -> S {
|
) -> S {
|
||||||
// Fetch the current state of the widget
|
// Fetch the current state of the widget
|
||||||
let state = self
|
let state = self
|
||||||
.ctx()
|
|
||||||
.read_response(id)
|
.read_response(id)
|
||||||
.map(|r| r.widget_state())
|
.map(|r| r.widget_state())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if let Some(style) = self.get_style::<S>(classes, state) {
|
self.get_widget_style::<S>(classes, state)
|
||||||
style
|
|
||||||
} else {
|
|
||||||
S::default_style(classes, state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
pub struct Themes {
|
||||||
pub(crate) struct Themes {
|
|
||||||
themes: IdTypeMap,
|
themes: IdTypeMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ThemeWrap<S> = Arc<Mutex<Box<dyn ThemeStyle<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))),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self { themes }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Themes {
|
impl Themes {
|
||||||
/// Register a theme and the style associated
|
/// Register a [`ThemeStyle`](crate::theme_plugin::ThemeStyle) for the specified widget [`WidgetStyle`](WidgetStyle) `S`
|
||||||
pub(crate) fn register<S: StyleStruct + 'static>(
|
///
|
||||||
|
/// Existing themes are overwritten if `force` is `true` or the new theme differs.
|
||||||
|
pub(crate) fn register<S: WidgetStyle + 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
theme: impl ThemeStyle<S> + Send + Sync + 'static,
|
theme: impl ThemeStyle<S> + Send + Sync + 'static,
|
||||||
force: bool,
|
force: bool,
|
||||||
@@ -84,10 +245,11 @@ impl Themes {
|
|||||||
&& self
|
&& self
|
||||||
.themes
|
.themes
|
||||||
.get_temp::<Arc<Mutex<Box<dyn ThemeStyle<S> + Send + Sync>>>>(Id::NULL)
|
.get_temp::<Arc<Mutex<Box<dyn ThemeStyle<S> + Send + Sync>>>>(Id::NULL)
|
||||||
.is_some()
|
.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 ThemeStyle<S> + Send + Sync>>>>(
|
||||||
Id::NULL,
|
Id::NULL,
|
||||||
@@ -96,14 +258,13 @@ impl Themes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch the style of the current theme
|
/// Fetch the style of the current theme
|
||||||
pub(crate) fn get<S: StyleStruct + 'static>(
|
pub fn get<S: WidgetStyle + 'static>(&self, classes: &Classes, state: WidgetState) -> S {
|
||||||
&self,
|
|
||||||
classes: &Classes,
|
|
||||||
state: WidgetState,
|
|
||||||
) -> Option<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 ThemeStyle<S> + Send + Sync>>>>(Id::NULL);
|
||||||
v.map(|engine| engine.lock().style(classes, state))
|
|
||||||
|
v.unwrap_or_else(|| panic!("A style should be set for {:?}", std::any::type_name::<S>()))
|
||||||
|
.lock()
|
||||||
|
.style(self, classes, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,16 @@ use std::{
|
|||||||
fmt::{self, Debug},
|
fmt::{self, Debug},
|
||||||
};
|
};
|
||||||
|
|
||||||
use emath::Vec2;
|
use epaint::{Color32, FontId, Stroke, text::TextWrapMode};
|
||||||
use epaint::{Color32, FontId, Shadow, Stroke, text::TextWrapMode};
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Frame, Response, Spacing, Style, TextBuffer as _, TextStyle, Visuals,
|
Frame, Response, TextBuffer as _,
|
||||||
style::{WidgetVisuals, Widgets},
|
style::{WidgetVisuals, Widgets},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Each dedicated style must implement this trait to be used in the theme plugin system
|
/// 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 {
|
pub trait WidgetStyle: Debug + Clone + Send + Sync + std::any::Any + 'static {}
|
||||||
/// The default style for this struct based on classes and state of the widget.
|
|
||||||
fn default_style(classes: &Classes, state: WidgetState) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// General text style
|
/// General text style
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -36,7 +30,7 @@ pub struct TextVisuals {
|
|||||||
|
|
||||||
/// General widget style
|
/// General widget style
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct WidgetStyle {
|
pub struct BaseStyle {
|
||||||
pub frame: Frame,
|
pub frame: Frame,
|
||||||
|
|
||||||
pub text: TextVisuals,
|
pub text: TextVisuals,
|
||||||
@@ -44,36 +38,7 @@ pub struct WidgetStyle {
|
|||||||
pub stroke: Stroke,
|
pub stroke: Stroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleStruct for WidgetStyle {
|
impl WidgetStyle for BaseStyle {}
|
||||||
fn default_style(_classes: &Classes, state: WidgetState) -> Self {
|
|
||||||
let visuals = Widgets::dark();
|
|
||||||
let spacing = Spacing::default();
|
|
||||||
|
|
||||||
let visuals = match state {
|
|
||||||
WidgetState::Noninteractive => visuals.noninteractive,
|
|
||||||
WidgetState::Inactive => visuals.inactive,
|
|
||||||
WidgetState::Hovered => visuals.hovered,
|
|
||||||
WidgetState::Active => visuals.active,
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
frame: Frame {
|
|
||||||
fill: visuals.bg_fill,
|
|
||||||
stroke: visuals.bg_stroke,
|
|
||||||
corner_radius: visuals.corner_radius,
|
|
||||||
inner_margin: spacing.button_padding.into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
stroke: visuals.fg_stroke,
|
|
||||||
text: TextVisuals {
|
|
||||||
color: visuals.text_color(),
|
|
||||||
font_id: TextStyle::Button.resolve(&Style::default()),
|
|
||||||
strikethrough: Stroke::NONE,
|
|
||||||
underline: Stroke::NONE,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dedicated button style
|
/// Dedicated button style
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -82,43 +47,7 @@ pub struct ButtonStyle {
|
|||||||
pub text_style: TextVisuals,
|
pub text_style: TextVisuals,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleStruct for ButtonStyle {
|
impl WidgetStyle for ButtonStyle {}
|
||||||
fn default_style(classes: &Classes, state: WidgetState) -> Self {
|
|
||||||
let widget_visuals = Widgets::dark();
|
|
||||||
let spacing = Spacing::default();
|
|
||||||
|
|
||||||
let mut widget_visuals = match state {
|
|
||||||
WidgetState::Noninteractive => widget_visuals.noninteractive,
|
|
||||||
WidgetState::Inactive => widget_visuals.inactive,
|
|
||||||
WidgetState::Hovered => widget_visuals.hovered,
|
|
||||||
WidgetState::Active => widget_visuals.active,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ws = WidgetStyle::default_style(classes, state);
|
|
||||||
|
|
||||||
if classes.has(SELECTED_CLASS) {
|
|
||||||
let visuals = Visuals::default();
|
|
||||||
widget_visuals.weak_bg_fill = visuals.selection.bg_fill;
|
|
||||||
widget_visuals.bg_fill = visuals.selection.bg_fill;
|
|
||||||
widget_visuals.fg_stroke = visuals.selection.stroke;
|
|
||||||
ws.text.color = visuals.selection.stroke.color;
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
frame: Frame {
|
|
||||||
fill: widget_visuals.weak_bg_fill,
|
|
||||||
stroke: widget_visuals.bg_stroke,
|
|
||||||
corner_radius: widget_visuals.corner_radius,
|
|
||||||
outer_margin: (-Vec2::splat(widget_visuals.expansion)).into(),
|
|
||||||
inner_margin: (spacing.button_padding + Vec2::splat(widget_visuals.expansion)
|
|
||||||
- Vec2::splat(widget_visuals.bg_stroke.width))
|
|
||||||
.into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
text_style: ws.text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dedicated checkbox style
|
/// Dedicated checkbox style
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -142,35 +71,7 @@ pub struct CheckboxStyle {
|
|||||||
pub check_stroke: Stroke,
|
pub check_stroke: Stroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleStruct for CheckboxStyle {
|
impl WidgetStyle for CheckboxStyle {}
|
||||||
fn default_style(classes: &Classes, state: WidgetState) -> Self {
|
|
||||||
let widget_visuals = Widgets::dark();
|
|
||||||
let spacing = Spacing::default();
|
|
||||||
|
|
||||||
let widget_visuals = match state {
|
|
||||||
WidgetState::Noninteractive => widget_visuals.noninteractive,
|
|
||||||
WidgetState::Inactive => widget_visuals.inactive,
|
|
||||||
WidgetState::Hovered => widget_visuals.hovered,
|
|
||||||
WidgetState::Active => widget_visuals.active,
|
|
||||||
};
|
|
||||||
|
|
||||||
let ws = WidgetStyle::default_style(classes, state);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
frame: Frame::new(),
|
|
||||||
checkbox_size: spacing.icon_width,
|
|
||||||
check_size: spacing.icon_width_inner,
|
|
||||||
checkbox_frame: Frame {
|
|
||||||
fill: widget_visuals.bg_fill,
|
|
||||||
corner_radius: widget_visuals.corner_radius,
|
|
||||||
stroke: widget_visuals.bg_stroke,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
text_style: ws.text,
|
|
||||||
check_stroke: ws.stroke,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dedicated label style
|
/// Dedicated label style
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -185,6 +86,8 @@ pub struct LabelStyle {
|
|||||||
pub wrap_mode: TextWrapMode,
|
pub wrap_mode: TextWrapMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WidgetStyle for LabelStyle {}
|
||||||
|
|
||||||
/// Dedicated separator style
|
/// Dedicated separator style
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SeparatorStyle {
|
pub struct SeparatorStyle {
|
||||||
@@ -195,6 +98,8 @@ pub struct SeparatorStyle {
|
|||||||
pub stroke: Stroke,
|
pub stroke: Stroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WidgetStyle for SeparatorStyle {}
|
||||||
|
|
||||||
/// The different state of a widget can be
|
/// The different state of a widget can be
|
||||||
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum WidgetState {
|
pub enum WidgetState {
|
||||||
@@ -231,109 +136,6 @@ impl Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Style {
|
|
||||||
/// The general widget style. The style is computed according to the classes and state of the widget.
|
|
||||||
pub fn widget_style(&self, _classes: &Classes, state: WidgetState) -> WidgetStyle {
|
|
||||||
let visuals = self.visuals.widgets.state(state);
|
|
||||||
let font_id = self.override_font_id.clone();
|
|
||||||
WidgetStyle {
|
|
||||||
frame: Frame {
|
|
||||||
fill: visuals.bg_fill,
|
|
||||||
stroke: visuals.bg_stroke,
|
|
||||||
corner_radius: visuals.corner_radius,
|
|
||||||
inner_margin: self.spacing.button_padding.into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
stroke: visuals.fg_stroke,
|
|
||||||
text: TextVisuals {
|
|
||||||
color: self
|
|
||||||
.visuals
|
|
||||||
.override_text_color
|
|
||||||
.unwrap_or_else(|| visuals.text_color()),
|
|
||||||
font_id: font_id.unwrap_or_else(|| TextStyle::Body.resolve(self)),
|
|
||||||
strikethrough: Stroke::NONE,
|
|
||||||
underline: Stroke::NONE,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The dedicated button style. The style is computed according to the classes and state of the widget.
|
|
||||||
/// It depend on the general widget style.
|
|
||||||
pub fn button_style(&self, classes: &Classes, state: WidgetState) -> ButtonStyle {
|
|
||||||
let mut visuals = *self.visuals.widgets.state(state);
|
|
||||||
let mut ws = self.widget_style(classes, state);
|
|
||||||
|
|
||||||
if classes.has(SELECTED_CLASS) {
|
|
||||||
visuals.weak_bg_fill = self.visuals.selection.bg_fill;
|
|
||||||
visuals.bg_fill = self.visuals.selection.bg_fill;
|
|
||||||
visuals.fg_stroke = self.visuals.selection.stroke;
|
|
||||||
ws.text.color = self.visuals.selection.stroke.color;
|
|
||||||
}
|
|
||||||
|
|
||||||
ButtonStyle {
|
|
||||||
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: (self.spacing.button_padding + Vec2::splat(visuals.expansion)
|
|
||||||
- Vec2::splat(visuals.bg_stroke.width))
|
|
||||||
.into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
text_style: ws.text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The dedicated checkbox style. The style is computed according to the classes and state of the widget.
|
|
||||||
/// It depend on the general widget style.
|
|
||||||
pub fn checkbox_style(&self, classes: &Classes, state: WidgetState) -> CheckboxStyle {
|
|
||||||
let visuals = self.visuals.widgets.state(state);
|
|
||||||
let ws = self.widget_style(classes, state);
|
|
||||||
CheckboxStyle {
|
|
||||||
frame: Frame::new(),
|
|
||||||
checkbox_size: self.spacing.icon_width,
|
|
||||||
check_size: self.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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The dedicated label style. The style is computed according to the classes and state of the widget.
|
|
||||||
/// It depend on the general widget style.
|
|
||||||
pub fn label_style(&self, classes: &Classes, state: WidgetState) -> LabelStyle {
|
|
||||||
let ws = self.widget_style(classes, state);
|
|
||||||
LabelStyle {
|
|
||||||
frame: Frame {
|
|
||||||
fill: ws.frame.fill,
|
|
||||||
inner_margin: 0.0.into(),
|
|
||||||
outer_margin: 0.0.into(),
|
|
||||||
stroke: Stroke::NONE,
|
|
||||||
shadow: Shadow::NONE,
|
|
||||||
corner_radius: 0.into(),
|
|
||||||
},
|
|
||||||
text: ws.text,
|
|
||||||
wrap_mode: TextWrapMode::Wrap,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The dedicated separator style. The style is computed according to the classes and state of the widget.
|
|
||||||
/// It depend on the general widget style.
|
|
||||||
pub fn separator_style(&self, _classes: &Classes, _state: WidgetState) -> SeparatorStyle {
|
|
||||||
let visuals = self.visuals.noninteractive();
|
|
||||||
SeparatorStyle {
|
|
||||||
spacing: 6.0,
|
|
||||||
stroke: visuals.bg_stroke,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The root class is a special class present on every top-level [`crate::Ui`].
|
/// The root class is a special class present on every top-level [`crate::Ui`].
|
||||||
pub const ROOT_CLASS: &str = "root";
|
pub const ROOT_CLASS: &str = "root";
|
||||||
|
|
||||||
|
|||||||
@@ -328,7 +328,6 @@ impl<'a> Button<'a> {
|
|||||||
|
|
||||||
classes.add_class_if(SELECTED_CLASS, selected.unwrap_or(false));
|
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.widget_style(id, &classes);
|
let ButtonStyle { frame, text_style } = ui.widget_style(id, &classes);
|
||||||
|
|
||||||
let mut button_padding = if has_frame_margin {
|
let mut button_padding = if has_frame_margin {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ impl Widget for Checkbox<'_> {
|
|||||||
frame,
|
frame,
|
||||||
check_stroke,
|
check_stroke,
|
||||||
text_style,
|
text_style,
|
||||||
} = ui.style().checkbox_style(&classes, state);
|
} = ui.widget_style(id, &classes);
|
||||||
|
|
||||||
let mut min_size = Vec2::splat(ui.spacing().interact_size.y);
|
let mut min_size = Vec2::splat(ui.spacing().interact_size.y);
|
||||||
min_size.y = min_size.y.at_least(checkbox_size);
|
min_size.y = min_size.y.at_least(checkbox_size);
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ impl Widget for Separator {
|
|||||||
let SeparatorStyle {
|
let SeparatorStyle {
|
||||||
spacing: spacing_style,
|
spacing: spacing_style,
|
||||||
stroke,
|
stroke,
|
||||||
} = ui.style().separator_style(&classes, state);
|
} = ui.widget_style(id, &classes);
|
||||||
|
|
||||||
// override the spacing if not set
|
// override the spacing if not set
|
||||||
let spacing = spacing.unwrap_or(spacing_style);
|
let spacing = spacing.unwrap_or(spacing_style);
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use eframe::egui::{
|
use eframe::egui::{
|
||||||
Color32,
|
Color32,
|
||||||
theme_plugin::{ThemeCache, ThemeStyle},
|
theme_plugin::{ThemeCache, ThemeStyle, Themes},
|
||||||
widget_style::{
|
widget_style::{
|
||||||
ButtonStyle, Classes, HasClasses as _, StyleStruct as _, WidgetState, WidgetStyle,
|
BaseStyle, ButtonStyle, Classes, HasClasses as _, WidgetState, WidgetStyle as _,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use logos::Logos;
|
use logos::Logos;
|
||||||
@@ -79,16 +79,16 @@ impl ESSEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// This implementation basically do nothing. This is only the minimum requirement with caching.
|
/// This implementation basically do nothing. This is only the minimum requirement with caching.
|
||||||
impl ThemeStyle<WidgetStyle> for ESSEngine {
|
impl ThemeStyle<BaseStyle> for ESSEngine {
|
||||||
fn style(&mut self, classes: &Classes, state: WidgetState) -> WidgetStyle {
|
fn style(&mut self, themes: &Themes, classes: &Classes, state: WidgetState) -> BaseStyle {
|
||||||
self.cache.get(classes, state, || {
|
let current = themes.get::<BaseStyle>(classes, state);
|
||||||
WidgetStyle::default_style(classes, state)
|
self.cache
|
||||||
})
|
.get(classes, state, || BaseStyle::default_style(classes, state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThemeStyle<ButtonStyle> for ESSEngine {
|
impl ThemeStyle<ButtonStyle> for ESSEngine {
|
||||||
fn style(&mut self, classes: &Classes, state: WidgetState) -> ButtonStyle {
|
fn style(&mut self, themes: &Themes, classes: &Classes, state: WidgetState) -> ButtonStyle {
|
||||||
self.cache.get(classes, state, || {
|
self.cache.get(classes, state, || {
|
||||||
let mut default = ButtonStyle::default_style(classes, state);
|
let mut default = ButtonStyle::default_style(classes, state);
|
||||||
for classe in classes.list() {
|
for classe in classes.list() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
#![expect(rustdoc::missing_crate_level_docs)] // it's an example
|
#![expect(rustdoc::missing_crate_level_docs)] // it's an example
|
||||||
|
|
||||||
use eframe::egui::widget_style::WidgetStyle;
|
use eframe::egui::widget_style::BaseStyle;
|
||||||
use eframe::egui::{
|
use eframe::egui::{
|
||||||
self, Button, Frame, Margin, Panel, UiBuilder,
|
self, Button, Frame, Margin, Panel, UiBuilder,
|
||||||
widget_style::{ButtonStyle, HasClasses as _},
|
widget_style::{ButtonStyle, HasClasses as _},
|
||||||
@@ -36,8 +36,8 @@ 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_theme::<WidgetStyle>(engine.clone());
|
ui.add_widget_theme::<BaseStyle>(engine.clone());
|
||||||
ui.add_theme::<ButtonStyle>(engine);
|
ui.add_widget_theme::<ButtonStyle>(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.scope_builder(UiBuilder::new().with_class("body"), |ui| {
|
ui.scope_builder(UiBuilder::new().with_class("body"), |ui| {
|
||||||
@@ -54,8 +54,8 @@ fn main() -> eframe::Result {
|
|||||||
&& let Ok(engine) = ESSEngine::try_parse(&style_code)
|
&& let Ok(engine) = ESSEngine::try_parse(&style_code)
|
||||||
{
|
{
|
||||||
// Overwrite the current theme with the new one.clear
|
// Overwrite the current theme with the new one.clear
|
||||||
ui.replace_theme::<WidgetStyle>(engine.clone());
|
ui.replace_widget_theme::<BaseStyle>(engine.clone());
|
||||||
ui.replace_theme::<ButtonStyle>(engine);
|
ui.replace_widget_theme::<ButtonStyle>(engine);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user