diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index daabd10b0..da44c61f9 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -7,6 +7,7 @@ use emath::GuiRounding as _; use epaint::mutex::RwLock; use crate::containers::menu; +use crate::widget_style::{HasModifiers, StyleModifiers}; use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *}; // ---------------------------------------------------------------------------- @@ -89,6 +90,9 @@ pub struct Ui { /// This is an optimization, so we don't call [`Ui::remember_min_rect`] multiple times at the /// end of a [`Ui::scope`]. min_rect_already_remembered: bool, + + /// Modifiers for style purpose + modifiers: StyleModifiers, } /// Allow using [`Ui`] like a [`Context`]. @@ -161,6 +165,7 @@ impl Ui { stack: Arc::new(ui_stack), sense, min_rect_already_remembered: false, + modifiers: StyleModifiers::default(), }; if let Some(accessibility_parent) = accessibility_parent { @@ -323,6 +328,7 @@ impl Ui { stack: Arc::new(ui_stack), sense, min_rect_already_remembered: false, + modifiers: StyleModifiers::default(), }; if disabled { @@ -3145,6 +3151,16 @@ impl Drop for Ui { } } +impl HasModifiers for Ui { + fn modifiers(&self) -> &StyleModifiers { + &self.modifiers + } + + fn modifiers_mut(&mut self) -> &mut StyleModifiers { + &mut self.modifiers + } +} + /// Show this rectangle to the user if certain debug options are set. #[cfg(debug_assertions)] fn register_rect(ui: &Ui, rect: Rect) { diff --git a/crates/egui/src/widget_style.rs b/crates/egui/src/widget_style.rs index 793b43ed6..d67ee715b 100644 --- a/crates/egui/src/widget_style.rs +++ b/crates/egui/src/widget_style.rs @@ -2,7 +2,7 @@ use emath::Vec2; use epaint::{Color32, FontId, Shadow, Stroke, text::TextWrapMode}; use crate::{ - Frame, Id, Response, Style, TextStyle, Theme, Ui, + Frame, Response, Style, TextStyle, Theme, style::{WidgetVisuals, Widgets}, }; @@ -107,8 +107,8 @@ impl Response { } impl Style { - pub fn widget_style(&self, state: WidgetState) -> WidgetStyle { - let visuals = self.visuals.widgets.state(state); + pub fn widget_style(&self, modifier: &StyleModifiers) -> WidgetStyle { + let visuals = self.visuals.widgets.state(modifier.state); let font_id = self.override_font_id.clone(); WidgetStyle { frame: Frame { @@ -123,19 +123,19 @@ impl Style { color: self .visuals .override_text_color - .unwrap_or(visuals.text_color()), - font_id: font_id.unwrap_or(TextStyle::Body.resolve(self)), + .unwrap_or_else(|| visuals.text_color()), + font_id: font_id.unwrap_or_else(|| TextStyle::Body.resolve(self)), strikethrough: Stroke::NONE, underline: Stroke::NONE, }, } } - pub fn button_style(&self, state: WidgetState, selected: bool) -> ButtonStyle { - let mut visuals = *self.visuals.widgets.state(state); - let mut ws = self.widget_style(state); + pub fn button_style(&self, modifier: &StyleModifiers) -> ButtonStyle { + let mut visuals = *self.visuals.widgets.state(modifier.state); + let mut ws = self.widget_style(modifier); - if selected { + if modifier.has("selected") { visuals.weak_bg_fill = self.visuals.selection.bg_fill; visuals.bg_fill = self.visuals.selection.bg_fill; visuals.fg_stroke = self.visuals.selection.stroke; @@ -157,9 +157,9 @@ impl Style { } } - pub fn checkbox_style(&self, state: WidgetState) -> CheckboxStyle { - let visuals = self.visuals.widgets.state(state); - let ws = self.widget_style(state); + pub fn checkbox_style(&self, modifier: &StyleModifiers) -> CheckboxStyle { + let visuals = self.visuals.widgets.state(modifier.state); + let ws = self.widget_style(modifier); CheckboxStyle { frame: Frame::new(), checkbox_size: self.spacing.icon_width, @@ -175,8 +175,8 @@ impl Style { } } - pub fn label_style(&self, state: WidgetState) -> LabelStyle { - let ws = self.widget_style(state); + pub fn label_style(&self, modifier: &StyleModifiers) -> LabelStyle { + let ws = self.widget_style(modifier); LabelStyle { frame: Frame { fill: ws.frame.fill, @@ -191,7 +191,7 @@ impl Style { } } - pub fn separator_style(&self, _state: WidgetState) -> SeparatorStyle { + pub fn separator_style(&self, _modifier: &StyleModifiers) -> SeparatorStyle { let visuals = self.visuals.noninteractive(); SeparatorStyle { spacing: 6.0, @@ -200,116 +200,109 @@ impl Style { } } -#[derive(PartialEq, Eq, Clone)] -pub enum StyleModifier { - /// Widget type should be a modifier or a separate information ? - /// Could have the trait [`HasClasses`] force to implement a method "name" - /// or "`widget_type`" - Button, - Label, - Separator, - Checkbox, - /// Classes and Id are string - Class(String), - Id(String), - /// Theme can be useful - Theme(Theme), -} +pub type WidgetStyleModifier = String; -/// Text modifiers affect only the text of the widgets -pub enum TextModifier { - Header, - Small, - Weak, - Strong, - Code, -} - -impl From<&str> for StyleModifier { - fn from(class: &str) -> Self { - match class { - "dark" => Self::Theme(Theme::Dark), - "light" => Self::Theme(Theme::Light), - "button" => Self::Button, - "label" => Self::Label, - "separator" => Self::Separator, - "checkbox" => Self::Checkbox, - // Maybe add a prefix for class and ID ? - _ => Self::Class(class.to_owned()), - } - } -} - -pub(crate) const CLASSES_SMALL_VEC_SIZE: usize = 5; - -/// Small vec for performance +/// For now we use [`Vec`] but later we could use [`SmallVec`] for performance #[derive(Default)] -pub struct Modifiers { - pub modifiers: Vec, - text: Option, - parent: Option, +pub struct StyleModifiers { + modifiers: Vec, + theme: Option, + state: WidgetState, } -impl Modifiers { - pub fn with_classes(mut self, classes: &[StyleModifier]) -> Self { - // debug_assert!( - // classes.len() <= CLASSES_SMALL_VEC_SIZE - self.modifiers.len(), - // "Too many modifiers !" - // ); +impl StyleModifiers { + /// Add multiples modifiers in one method and return the list for method chaining + pub fn with_modifiers(mut self, classes: &[WidgetStyleModifier]) -> Self { self.modifiers.append(&mut classes.to_vec()); self } - pub fn with_class(mut self, class: impl Into) -> Self { + /// Add a single modifier and return the list for method chaining + pub fn with_modifier(mut self, class: impl Into) -> Self { self.modifiers.push(class.into()); self } - pub fn with_parent(mut self, parent: &Ui) -> Self { - self.parent = Some(parent.id()); - self - } - - /// Add a class to the list - pub fn add_if(&mut self, class: impl Into, condition: bool) { + /// Add a class to the list if the condition is true + pub fn add_if(&mut self, modifier: impl Into, condition: bool) { if condition { - self.modifiers.push(class.into()); + self.modifiers.push(modifier.into()); } } - /// Add a class to the list and return the list, for method chaining - pub fn with_if(mut self, class: impl Into, condition: bool) -> Self { - self.add_if(class.into(), condition); + /// Add a class to the list and return the list for method chaining + pub fn with_if(mut self, modifier: impl Into, condition: bool) -> Self { + self.add_if(modifier.into(), condition); self } - pub fn has(&self, class: impl Into) -> bool { - self.modifiers.contains(&class.into()) + + /// Return true if the modifier is present in the list + pub fn has(&self, modifier: impl Into) -> bool { + self.modifiers.contains(&modifier.into()) + } + + pub fn with_theme(&mut self, theme: Theme) { + self.theme = Some(theme); + } + + pub fn with_state(&mut self, state: WidgetState) { + self.state = state; } } -/// Any widgets supporting classes must implement this trait -pub trait HasModifier { - fn classes(&self) -> &Modifiers; +/// Any widgets supporting [`StyleModifiers`] must implement this trait +pub trait HasModifiers { + fn modifiers(&self) -> &StyleModifiers; - fn classes_mut(&mut self) -> &mut Modifiers; + fn modifiers_mut(&mut self) -> &mut StyleModifiers; - fn add_class(&mut self, class: impl Into) -> &Self { - self.classes_mut().add_if(class.into(), true); + fn add_class(&mut self, modifier: impl Into) -> &Self { + self.modifiers_mut().add_if(modifier.into(), true); self } - fn with_class(mut self, class: impl Into) -> Self + fn with_modifier(mut self, modifier: impl Into) -> Self where Self: Sized, { - self.classes_mut().add_if(class.into(), true); + self.modifiers_mut().add_if(modifier.into(), true); self } - fn with_class_if(mut self, class: impl Into, condition: bool) -> Self + fn with_modifier_if(mut self, modifier: impl Into, condition: bool) -> Self where Self: Sized, { - self.classes_mut().add_if(class.into(), condition); + self.modifiers_mut().add_if(modifier.into(), condition); + self + } + + fn theme(mut self, theme: Theme) -> Self + where + Self: Sized, + { + self.modifiers_mut().with_theme(theme); self } } + +/// Add a shortcut to add modifiers. The syntax is add_modifiers!([Name]: (modifier1, modifier2, modifier3,)) for any number of modifiers +#[macro_export] +macro_rules! add_modifiers { + ($trait_name:ident: ($( $name:ident )+),?) => { + + pub trait $trait_name { + $( + fn $name(self) -> Self; + )* + } + + impl $trait_name for T + where + T: HasModifiers, + { + $(fn $name(mut self) -> Self { + self.with_modifier(stringify!($name)) + })? + } + }; +} diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 4b4c2fe32..6c6f3e582 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -4,7 +4,7 @@ use crate::{ Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame, Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType, - widget_style::{ButtonStyle, WidgetState}, + widget_style::{ButtonStyle, HasModifiers, StyleModifiers, WidgetState}, }; /// Clickable button with text. @@ -38,6 +38,7 @@ pub struct Button<'a> { selected: bool, image_tint_follows_text_color: bool, limit_image_size: bool, + modifiers: StyleModifiers, } impl<'a> Button<'a> { @@ -56,6 +57,7 @@ impl<'a> Button<'a> { selected: false, image_tint_follows_text_color: false, limit_image_size: false, + modifiers: StyleModifiers::default(), } } @@ -273,6 +275,7 @@ impl<'a> Button<'a> { selected, image_tint_follows_text_color, limit_image_size, + mut modifiers, } = self; // Min size height always equal or greater than interact size if not small @@ -297,8 +300,11 @@ impl<'a> Button<'a> { let id = ui.next_auto_id(); let response: Option = ui.ctx().read_response(id); let state = response.map(|r| r.widget_state()).unwrap_or_default(); + modifiers.with_state(state); - let ButtonStyle { frame, text_style } = ui.style().button_style(state, selected); + modifiers.add_if("selected", selected); + + let ButtonStyle { frame, text_style } = ui.style().button_style(&modifiers); let mut button_padding = if has_frame_margin { frame.inner_margin @@ -369,3 +375,13 @@ impl Widget for Button<'_> { self.atom_ui(ui).response } } + +impl HasModifiers for Button<'_> { + fn modifiers(&self) -> &StyleModifiers { + &self.modifiers + } + + fn modifiers_mut(&mut self) -> &mut StyleModifiers { + &mut self.modifiers + } +} diff --git a/crates/egui/src/widgets/checkbox.rs b/crates/egui/src/widgets/checkbox.rs index 3d0dbc367..8784dc448 100644 --- a/crates/egui/src/widgets/checkbox.rs +++ b/crates/egui/src/widgets/checkbox.rs @@ -2,7 +2,8 @@ use emath::Rect; use crate::{ Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, Vec2, Widget, - WidgetInfo, WidgetType, epaint, pos2, widget_style::CheckboxStyle, + WidgetInfo, WidgetType, epaint, pos2, + widget_style::{CheckboxStyle, HasModifiers, StyleModifiers}, }; // TODO(emilk): allow checkbox without a text label @@ -23,6 +24,7 @@ pub struct Checkbox<'a> { checked: &'a mut bool, atoms: Atoms<'a>, indeterminate: bool, + modifier: StyleModifiers, } impl<'a> Checkbox<'a> { @@ -31,6 +33,7 @@ impl<'a> Checkbox<'a> { checked, atoms: atoms.into_atoms(), indeterminate: false, + modifier: StyleModifiers::default(), } } @@ -55,12 +58,14 @@ impl Widget for Checkbox<'_> { checked, mut atoms, indeterminate, + mut modifier, } = self; // Get the widget style by reading the response from the previous pass let id = ui.next_auto_id(); let response: Option = ui.ctx().read_response(id); let state = response.map(|r| r.widget_state()).unwrap_or_default(); + modifier.with_state(state); let CheckboxStyle { check_size, @@ -69,7 +74,7 @@ impl Widget for Checkbox<'_> { frame, check_stroke, text_style, - } = ui.style().checkbox_style(state); + } = ui.style().checkbox_style(&modifier); let mut min_size = Vec2::splat(ui.spacing().interact_size.y); min_size.y = min_size.y.at_least(checkbox_size); @@ -153,3 +158,13 @@ impl Widget for Checkbox<'_> { } } } + +impl HasModifiers for Checkbox<'_> { + fn modifiers(&self) -> &crate::widget_style::StyleModifiers { + &self.modifier + } + + fn modifiers_mut(&mut self) -> &mut crate::widget_style::StyleModifiers { + &mut self.modifier + } +} diff --git a/crates/egui/src/widgets/separator.rs b/crates/egui/src/widgets/separator.rs index ab211773e..8fee562e2 100644 --- a/crates/egui/src/widgets/separator.rs +++ b/crates/egui/src/widgets/separator.rs @@ -1,4 +1,7 @@ -use crate::{Response, Sense, Ui, Vec2, Widget, vec2, widget_style::SeparatorStyle}; +use crate::{ + Response, Sense, Ui, Vec2, Widget, vec2, + widget_style::{HasModifiers, SeparatorStyle, StyleModifiers}, +}; /// A visual separator. A horizontal or vertical line (depending on [`crate::Layout`]). /// @@ -16,6 +19,7 @@ pub struct Separator { spacing: Option, grow: f32, is_horizontal_line: Option, + modifier: StyleModifiers, } impl Default for Separator { @@ -24,6 +28,7 @@ impl Default for Separator { spacing: None, grow: 0.0, is_horizontal_line: None, + modifier: StyleModifiers::default(), } } } @@ -91,16 +96,18 @@ impl Widget for Separator { spacing, grow, is_horizontal_line, + mut modifier, } = self; // Get the widget style by reading the response from the previous pass let id = ui.next_auto_id(); let response: Option = ui.ctx().read_response(id); let state = response.map(|r| r.widget_state()).unwrap_or_default(); + modifier.with_state(state); let SeparatorStyle { spacing: spacing_style, stroke, - } = ui.style().separator_style(state); + } = ui.style().separator_style(&modifier); // override the spacing if not set let spacing = spacing.unwrap_or(spacing_style); @@ -142,3 +149,13 @@ impl Widget for Separator { response } } + +impl HasModifiers for Separator { + fn modifiers(&self) -> &crate::widget_style::StyleModifiers { + &self.modifier + } + + fn modifiers_mut(&mut self) -> &mut crate::widget_style::StyleModifiers { + &mut self.modifier + } +}