From f954abcbdf94b4dbe880266643d26664016b1547 Mon Sep 17 00:00:00 2001 From: adrien <221212@umons.ac.be> Date: Tue, 27 Jan 2026 02:31:29 +0100 Subject: [PATCH] hierarchy & example --- Cargo.lock | 8 ++ crates/egui/src/containers/panel.rs | 4 + crates/egui/src/ui.rs | 54 +++++++++++-- crates/egui/src/ui_stack.rs | 2 +- crates/egui/src/widget_style.rs | 102 ++++++++++++++++++++++--- crates/egui/src/widgets/checkbox.rs | 10 +-- crates/egui/src/widgets/separator.rs | 10 +-- examples/styling_engine/Cargo.toml | 19 +++++ examples/styling_engine/README.md | 7 ++ examples/styling_engine/screenshot.png | 3 + examples/styling_engine/src/main.rs | 44 +++++++++++ 11 files changed, 234 insertions(+), 29 deletions(-) create mode 100644 examples/styling_engine/Cargo.toml create mode 100644 examples/styling_engine/README.md create mode 100644 examples/styling_engine/screenshot.png create mode 100644 examples/styling_engine/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index a99c0d281..99e1d7291 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4315,6 +4315,14 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "styling_engine" +version = "0.1.0" +dependencies = [ + "eframe", + "env_logger", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 6281e6b41..833c51ce9 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -15,6 +15,8 @@ //! //! Add your [`crate::Window`]:s after any top-level panels. +use std::sync::Arc; + use emath::{GuiRounding as _, Pos2}; use crate::{ @@ -1027,10 +1029,12 @@ impl CentralPanel { .max_rect(panel_rect) .layout(Layout::top_down(Align::Min)), ); + let style_stack = ui.style_stack_mut().clone(); panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475) let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style())); let response = frame.show(&mut panel_ui, |ui| { + ui.style_stack_mut().parent = Some(Arc::new(style_stack)); ui.expand_to_include_rect(ui.max_rect()); // Expand frame to include it all add_contents(ui) }); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index da44c61f9..c7c4f1ef9 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -7,7 +7,7 @@ use emath::GuiRounding as _; use epaint::mutex::RwLock; use crate::containers::menu; -use crate::widget_style::{HasModifiers, StyleModifiers}; +use crate::widget_style::{HasModifiers, StyleModifiers, StyleStack}; use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *}; // ---------------------------------------------------------------------------- @@ -91,8 +91,8 @@ pub struct Ui { /// end of a [`Ui::scope`]. min_rect_already_remembered: bool, - /// Modifiers for style purpose - modifiers: StyleModifiers, + /// test + style_stack: Arc, } /// Allow using [`Ui`] like a [`Context`]. @@ -117,7 +117,7 @@ impl Ui { let UiBuilder { id_salt, global_scope: _, - ui_stack_info, + mut ui_stack_info, layer_id, max_rect, layout, @@ -143,6 +143,9 @@ impl Ui { let style = style.unwrap_or_else(|| ctx.global_style()); let sense = sense.unwrap_or_else(Sense::hover); + // Temporary use of user tags as proof of concept + ui_stack_info = ui_stack_info.with_tag("root"); + let placer = Placer::new(max_rect, layout); let ui_stack = UiStack { id, @@ -152,6 +155,12 @@ impl Ui { min_rect: placer.min_rect(), max_rect: placer.max_rect(), }; + + let style_stack = StyleStack { + modifiers: StyleModifiers::default(), + parent: None, + }; + let mut ui = Ui { id, unique_id: id, @@ -165,7 +174,7 @@ impl Ui { stack: Arc::new(ui_stack), sense, min_rect_already_remembered: false, - modifiers: StyleModifiers::default(), + style_stack: Arc::new(style_stack), }; if let Some(accessibility_parent) = accessibility_parent { @@ -315,6 +324,12 @@ impl Ui { min_rect: placer.min_rect(), max_rect: placer.max_rect(), }; + + let style_stack = StyleStack { + modifiers: StyleModifiers::default(), + parent: Some(Arc::clone(&self.style_stack)), + }; + let mut child_ui = Ui { id: stable_id, unique_id, @@ -328,7 +343,7 @@ impl Ui { stack: Arc::new(ui_stack), sense, min_rect_already_remembered: false, - modifiers: StyleModifiers::default(), + style_stack: Arc::new(style_stack), }; if disabled { @@ -3153,11 +3168,34 @@ impl Drop for Ui { impl HasModifiers for Ui { fn modifiers(&self) -> &StyleModifiers { - &self.modifiers + &self.style_stack.modifiers } fn modifiers_mut(&mut self) -> &mut StyleModifiers { - &mut self.modifiers + &mut self.style_stack_mut().modifiers + } +} + +impl Ui { + /// borrow internal [`StyleStack`]. + /// Allow the access to the modifiers of the ui's ancestors + /// + /// Example: + /// ``` + /// # egui::__run_test_ui(|ui| { + /// // Check if the parent has the "test" modifier + /// ui.style_stack().parent.is_some_and(|parent| parent.modifiers.has("test")); + /// + /// // Same but shorter + /// ui.style_stack().parent_has("test"); + /// # }); + /// ``` + pub fn style_stack(&self) -> &StyleStack { + &self.style_stack + } + + pub(crate) fn style_stack_mut(&mut self) -> &mut StyleStack { + Arc::make_mut(&mut self.style_stack) } } diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 07026c45b..0251c80f6 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -201,7 +201,7 @@ impl UiTags { /// Note: since [`UiStack`] contains a reference to its parent, it is both a stack, and a node within /// that stack. Most of its methods are about the specific node, but some methods walk up the /// hierarchy to provide information about the entire stack. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct UiStack { // stuff that `Ui::child_ui` can deal with directly pub id: Id, diff --git a/crates/egui/src/widget_style.rs b/crates/egui/src/widget_style.rs index 317149bd6..876744fc1 100644 --- a/crates/egui/src/widget_style.rs +++ b/crates/egui/src/widget_style.rs @@ -1,3 +1,5 @@ +use std::{iter::FusedIterator, sync::Arc}; + use emath::Vec2; use epaint::{Color32, FontId, Shadow, Stroke, text::TextWrapMode}; @@ -203,7 +205,7 @@ impl Style { pub type WidgetStyleModifier = String; /// For now we use Vec for the modifiers but later we could use `SmallVev` for performance -#[derive(Default)] +#[derive(Debug, Default, Clone)] pub struct StyleModifiers { modifiers: Vec, theme: Option, @@ -213,15 +215,15 @@ pub struct StyleModifiers { impl StyleModifiers { /// Add multiples modifiers in one method and return the list for method chaining #[inline] - pub fn with_modifiers(mut self, classes: &[WidgetStyleModifier]) -> Self { - self.modifiers.append(&mut classes.to_vec()); + pub fn with_modifiers(mut self, modifiers: &[WidgetStyleModifier]) -> Self { + self.modifiers.append(&mut modifiers.to_vec()); self } /// Add a single modifier and return the list for method chaining #[inline] - pub fn with_modifier(mut self, class: impl Into) -> Self { - self.modifiers.push(class.into()); + pub fn with_modifier(mut self, modifier: impl Into) -> Self { + self.modifiers.push(modifier.into()); self } @@ -252,6 +254,10 @@ impl StyleModifiers { pub fn with_state(&mut self, state: WidgetState) { self.state = state; } + + pub fn list(&self) -> Vec { + self.modifiers.clone() + } } /// Any widgets supporting [`StyleModifiers`] must implement this trait @@ -260,11 +266,6 @@ pub trait HasModifiers { fn modifiers_mut(&mut self) -> &mut StyleModifiers; - fn add_class(&mut self, modifier: impl Into) -> &Self { - self.modifiers_mut().add_if(modifier.into(), true); - self - } - #[inline] fn with_modifier(mut self, modifier: impl Into) -> Self where @@ -283,6 +284,28 @@ pub trait HasModifiers { self } + #[inline] + fn add_modifier(&mut self, modifier: impl Into) -> &mut Self + where + Self: Sized, + { + self.modifiers_mut().add_if(modifier.into(), true); + self + } + + #[inline] + fn add_modifier_if( + &mut self, + modifier: impl Into, + condition: bool, + ) -> &mut Self + where + Self: Sized, + { + self.modifiers_mut().add_if(modifier.into(), condition); + self + } + #[inline] fn theme(mut self, theme: Theme) -> Self where @@ -315,3 +338,62 @@ macro_rules! add_modifiers { } }; } + +#[derive(Debug, Clone)] +pub struct StyleStack { + pub modifiers: StyleModifiers, + pub parent: Option>, +} + +// these methods act on the entire stack +impl StyleStack { + /// Return an iterator that walks the stack from this node to the root. + #[expect(clippy::iter_without_into_iter)] + pub fn iter(&self) -> StyleStackIterator<'_> { + StyleStackIterator { next: Some(self) } + } + + /// Check if any of the ancestor has the modifiers + pub fn ancestors_have(&self, modifier: impl Into) -> bool { + let modifier = modifier.into(); + self.iter() + .any(|parent| parent.modifiers.has(modifier.clone())) + } + + /// Check if the direct parent has the modifiers + pub fn parent_has(&self, modifier: impl Into) -> bool { + if let Some(parent) = &self.parent { + parent.modifiers.has(modifier) + } else { + false + } + } + + pub fn parent_modifiers(&self) -> Option<&StyleModifiers> { + self.parent.as_ref().map(|parent| &parent.modifiers) + } + + pub fn ancestors_modifiers(&self) -> Vec<&StyleModifiers> { + self.iter().map(|f| &f.modifiers).collect() + } +} + +/// Iterator that walks up a stack of `StackFrame`s. +/// +/// See [`StyleStack::iter`]. +pub struct StyleStackIterator<'a> { + next: Option<&'a StyleStack>, +} + +impl<'a> Iterator for StyleStackIterator<'a> { + type Item = &'a StyleStack; + + #[inline] + fn next(&mut self) -> Option { + let current = self.next; + self.next = current.and_then(|style| style.parent.as_deref()); + current + } +} + +impl FusedIterator for StyleStackIterator<'_> {} diff --git a/crates/egui/src/widgets/checkbox.rs b/crates/egui/src/widgets/checkbox.rs index 8784dc448..942a8cb68 100644 --- a/crates/egui/src/widgets/checkbox.rs +++ b/crates/egui/src/widgets/checkbox.rs @@ -24,7 +24,7 @@ pub struct Checkbox<'a> { checked: &'a mut bool, atoms: Atoms<'a>, indeterminate: bool, - modifier: StyleModifiers, + modifiers: StyleModifiers, } impl<'a> Checkbox<'a> { @@ -33,7 +33,7 @@ impl<'a> Checkbox<'a> { checked, atoms: atoms.into_atoms(), indeterminate: false, - modifier: StyleModifiers::default(), + modifiers: StyleModifiers::default(), } } @@ -58,7 +58,7 @@ impl Widget for Checkbox<'_> { checked, mut atoms, indeterminate, - mut modifier, + modifiers: mut modifier, } = self; // Get the widget style by reading the response from the previous pass @@ -161,10 +161,10 @@ impl Widget for Checkbox<'_> { impl HasModifiers for Checkbox<'_> { fn modifiers(&self) -> &crate::widget_style::StyleModifiers { - &self.modifier + &self.modifiers } fn modifiers_mut(&mut self) -> &mut crate::widget_style::StyleModifiers { - &mut self.modifier + &mut self.modifiers } } diff --git a/crates/egui/src/widgets/separator.rs b/crates/egui/src/widgets/separator.rs index 8fee562e2..082be07a4 100644 --- a/crates/egui/src/widgets/separator.rs +++ b/crates/egui/src/widgets/separator.rs @@ -19,7 +19,7 @@ pub struct Separator { spacing: Option, grow: f32, is_horizontal_line: Option, - modifier: StyleModifiers, + modifiers: StyleModifiers, } impl Default for Separator { @@ -28,7 +28,7 @@ impl Default for Separator { spacing: None, grow: 0.0, is_horizontal_line: None, - modifier: StyleModifiers::default(), + modifiers: StyleModifiers::default(), } } } @@ -96,7 +96,7 @@ impl Widget for Separator { spacing, grow, is_horizontal_line, - mut modifier, + modifiers: mut modifier, } = self; // Get the widget style by reading the response from the previous pass @@ -152,10 +152,10 @@ impl Widget for Separator { impl HasModifiers for Separator { fn modifiers(&self) -> &crate::widget_style::StyleModifiers { - &self.modifier + &self.modifiers } fn modifiers_mut(&mut self) -> &mut crate::widget_style::StyleModifiers { - &mut self.modifier + &mut self.modifiers } } diff --git a/examples/styling_engine/Cargo.toml b/examples/styling_engine/Cargo.toml new file mode 100644 index 000000000..19cec8ae9 --- /dev/null +++ b/examples/styling_engine/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "styling_engine" +version = "0.1.0" +authors = ["Emil Ernerfeldt "] +license = "MIT OR Apache-2.0" +edition = "2024" +rust-version = "1.92" +publish = false + +[lints] +workspace = true + + +[dependencies] +eframe = { workspace = true, features = [ + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO +] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/styling_engine/README.md b/examples/styling_engine/README.md new file mode 100644 index 000000000..d066d3960 --- /dev/null +++ b/examples/styling_engine/README.md @@ -0,0 +1,7 @@ +Example showing how the style engine work. + +```sh +cargo run -p styling_engine +``` + +![](screenshot.png) diff --git a/examples/styling_engine/screenshot.png b/examples/styling_engine/screenshot.png new file mode 100644 index 000000000..0d27bc056 --- /dev/null +++ b/examples/styling_engine/screenshot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cde6a351fbcf5a5d3b5474353c627a9c93801563880b7ff41dfa025da3c6d37 +size 8587 diff --git a/examples/styling_engine/src/main.rs b/examples/styling_engine/src/main.rs new file mode 100644 index 000000000..2446c3385 --- /dev/null +++ b/examples/styling_engine/src/main.rs @@ -0,0 +1,44 @@ +#![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 + +use eframe::egui::{self, Button, Frame, Grid, Margin, Panel, widget_style::HasModifiers as _}; + +fn main() -> eframe::Result { + env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([600.0, 400.0]), + ..Default::default() + }; + + let mut style_code = "// future style code live editor".to_owned(); + + eframe::run_ui_native("My egui App", options, move |ui, _frame| { + // Add a modifier to this ui + ui.add_modifier("body"); + ui.label("body"); + egui::CentralPanel::default().show_inside(ui, |ui| { + // Add a modifier to this ui + ui.add_modifier("central panel"); + ui.label("central panel"); + + Panel::left("style_code").show_inside(ui, |ui| { + ui.add_modifier("style code editor"); + ui.label("style code editor"); + + ui.text_edit_multiline(&mut style_code); + }); + + Grid::new("grid").show(ui, |ui| { + ui.add_modifier("grid"); + + Frame::new().inner_margin(Margin::same(10)).show(ui, |ui| { + ui.add_modifier("frame1"); + + ui.add(Button::new("button1").with_modifier("button1")); + ui.add(Button::new("button2").with_modifier("button2")); + }) + }); + }); + }) +}