From a7dffbcaf14f7924ccf7007b151d3603fd3672f1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 5 Jun 2024 14:55:46 +0200 Subject: [PATCH 1/6] Store the `UiStackInfo` inside of `UiStack`, without unrolling it --- crates/egui/src/ui.rs | 11 ++++++----- crates/egui/src/ui_stack.rs | 19 ++++++++++++++----- .../src/demo/misc_demo_window.rs | 2 +- tests/test_ui_stack/src/main.rs | 17 +++++++++-------- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index d510e4ee7b8..425f0ad8951 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -94,8 +94,7 @@ impl Ui { let ui_stack = UiStack { id, layout_direction: layout.main_dir, - kind: ui_stack_info.kind, - frame: ui_stack_info.frame, + info: ui_stack_info, parent: None, min_rect: placer.min_rect(), max_rect: placer.max_rect(), @@ -130,6 +129,8 @@ impl Ui { /// /// Note: calling this function twice from the same [`Ui`] will create a conflict of id. Use /// [`Self::scope`] if needed. + /// + /// When in doubt, use `None` for the `UiStackInfo` argument. pub fn child_ui( &mut self, max_rect: Rect, @@ -140,6 +141,8 @@ impl Ui { } /// Create a new [`Ui`] at a specific region with a specific id. + /// + /// When in doubt, use `None` for the `UiStackInfo` argument. pub fn child_ui_with_id_source( &mut self, max_rect: Rect, @@ -162,12 +165,10 @@ impl Ui { let new_id = self.id.with(id_source); let placer = Placer::new(max_rect, layout); - let ui_stack_info = ui_stack_info.unwrap_or_default(); let ui_stack = UiStack { id: new_id, layout_direction: layout.main_dir, - kind: ui_stack_info.kind, - frame: ui_stack_info.frame, + info: ui_stack_info.unwrap_or_default(), parent: Some(self.stack.clone()), min_rect: placer.min_rect(), max_rect: placer.max_rect(), diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index bc190ed7faf..2c746b60763 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -100,8 +100,7 @@ impl UiStackInfo { pub struct UiStack { // stuff that `Ui::child_ui` can deal with directly pub id: Id, - pub kind: Option, - pub frame: Frame, + pub info: UiStackInfo, pub layout_direction: Direction, pub min_rect: Rect, pub max_rect: Rect, @@ -110,10 +109,20 @@ pub struct UiStack { // these methods act on this specific node impl UiStack { + #[inline] + pub fn kind(&self) -> Option { + self.info.kind + } + + #[inline] + pub fn frame(&self) -> &Frame { + &self.info.frame + } + /// Is this [`crate::Ui`] a panel? #[inline] pub fn is_panel_ui(&self) -> bool { - self.kind.map_or(false, |kind| kind.is_panel()) + self.kind().map_or(false, |kind| kind.is_panel()) } /// Is this a root [`crate::Ui`], i.e. created with [`crate::Ui::new()`]? @@ -125,7 +134,7 @@ impl UiStack { /// This this [`crate::Ui`] a [`crate::Frame`] with a visible stroke? #[inline] pub fn has_visible_frame(&self) -> bool { - !self.frame.stroke.is_empty() + !self.info.frame.stroke.is_empty() } } @@ -139,7 +148,7 @@ impl UiStack { /// Check if this node is or is contained in a [`crate::Ui`] of a specific kind. pub fn contained_in(&self, kind: UiKind) -> bool { - self.iter().any(|frame| frame.kind == Some(kind)) + self.iter().any(|frame| frame.kind() == Some(kind)) } } diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index d59058f869c..c0f98198e43 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -547,7 +547,7 @@ fn ui_stack_demo(ui: &mut Ui) { }); row.col(|ui| { - ui.label(if let Some(kind) = node.kind { + ui.label(if let Some(kind) = node.kind() { format!("{kind:?}") } else { "-".to_owned() diff --git a/tests/test_ui_stack/src/main.rs b/tests/test_ui_stack/src/main.rs index a5cffd5ce57..3afb659366d 100644 --- a/tests/test_ui_stack/src/main.rs +++ b/tests/test_ui_stack/src/main.rs @@ -209,9 +209,9 @@ fn full_span_horizontal_range(ui_stack: &egui::UiStack) -> Rangef { if node.has_visible_frame() || node.is_panel_ui() || node.is_root_ui() - || node.kind == Some(UiKind::TableCell) + || node.kind() == Some(UiKind::TableCell) { - return (node.max_rect + node.frame.inner_margin).x_range(); + return (node.max_rect + node.frame().inner_margin).x_range(); } } @@ -280,7 +280,7 @@ fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) { } }); row.col(|ui| { - let s = if let Some(kind) = node.kind { + let s = if let Some(kind) = node.kind() { format!("{kind:?}") } else { "-".to_owned() @@ -289,7 +289,8 @@ fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) { ui.label(s); }); row.col(|ui| { - if node.frame.stroke == egui::Stroke::NONE { + let frame = node.frame(); + if frame.stroke == egui::Stroke::NONE { ui.label("-"); } else { let mut layout_job = egui::text::LayoutJob::default(); @@ -298,11 +299,11 @@ fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) { 0.0, egui::TextFormat::simple( egui::TextStyle::Body.resolve(ui.style()), - node.frame.stroke.color, + frame.stroke.color, ), ); layout_job.append( - format!("{}px", node.frame.stroke.width).as_str(), + format!("{}px", frame.stroke.width).as_str(), 0.0, egui::TextFormat::simple( egui::TextStyle::Body.resolve(ui.style()), @@ -314,10 +315,10 @@ fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) { } }); row.col(|ui| { - ui.label(print_margin(&node.frame.inner_margin)); + ui.label(print_margin(&node.frame().inner_margin)); }); row.col(|ui| { - ui.label(print_margin(&node.frame.outer_margin)); + ui.label(print_margin(&node.frame().outer_margin)); }); row.col(|ui| { ui.label(format!("{:?}", node.layout_direction)); From 882baf5769baf4358f94d2c4c672d8ea1bf2652b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 5 Jun 2024 15:01:33 +0200 Subject: [PATCH 2/6] Add tags to `UiStack` --- crates/egui/src/containers/frame.rs | 5 +- crates/egui/src/containers/panel.rs | 15 +---- crates/egui/src/ui_stack.rs | 85 +++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 20 deletions(-) diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 04c2d246d29..07cd679acea 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -253,10 +253,7 @@ impl Frame { let content_ui = ui.child_ui( inner_rect, *ui.layout(), - Some(UiStackInfo { - frame: self, - kind: Some(UiKind::Frame), - }), + Some(UiStackInfo::new(UiKind::Frame).with_frame(self)), ); // content_ui.set_clip_rect(outer_rect_bounds.shrink(self.stroke.width * 0.5)); // Can't do this since we don't know final size yet diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 658b9541d13..09ff67fbf39 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -362,10 +362,7 @@ impl SidePanel { self.id, available_rect, clip_rect, - UiStackInfo { - kind: None, // set by show_inside_dyn - frame: Frame::default(), - }, + UiStackInfo::default(), ); let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); @@ -848,10 +845,7 @@ impl TopBottomPanel { self.id, available_rect, clip_rect, - UiStackInfo { - kind: None, // set by show_inside_dyn - frame: Frame::default(), - }, + UiStackInfo::default(), // set by show_inside_dyn ); let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); @@ -1120,10 +1114,7 @@ impl CentralPanel { id, available_rect, clip_rect, - UiStackInfo { - kind: None, // set by show_inside_dyn - frame: Frame::default(), - }, + UiStackInfo::default(), // set by show_inside_dyn ); let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 2c746b60763..542b54642a0 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -1,5 +1,5 @@ -use std::iter::FusedIterator; use std::sync::Arc; +use std::{any::Any, iter::FusedIterator}; use crate::{Direction, Frame, Id, Rect}; @@ -69,20 +69,91 @@ impl UiKind { // ---------------------------------------------------------------------------- /// Information about a [`crate::Ui`] to be included in the corresponding [`UiStack`]. -#[derive(Default, Copy, Clone, Debug)] +#[derive(Clone, Default, Debug)] pub struct UiStackInfo { pub kind: Option, pub frame: Frame, + pub tags: UiTags, } impl UiStackInfo { /// Create a new [`UiStackInfo`] with the given kind and an empty frame. + #[inline] pub fn new(kind: UiKind) -> Self { Self { kind: Some(kind), - frame: Default::default(), + ..Default::default() } } + + #[inline] + pub fn with_frame(mut self, frame: Frame) -> Self { + self.frame = frame; + self + } + + /// Insert a tag with no value. + #[inline] + pub fn with_tag(mut self, key: impl Into) -> Self { + self.tags.insert(key, None); + self + } + + /// Insert a tag with some value. + #[inline] + pub fn with_tag_value( + mut self, + key: impl Into, + value: impl Any + Send + Sync + 'static, + ) -> Self { + self.tags.insert(key, Some(Arc::new(value))); + self + } +} + +// ---------------------------------------------------------------------------- + +/// User-chosen tags. +/// +/// You can use this in any way you want, +/// i.e. to set some tag on a [`Ui`] and then in your own widget check +/// for the existence of this tag up the [`UiStack`]. +/// +/// Note that egui never sets any tags itself, so this is purely for user code. +#[derive(Clone, Default, Debug)] +pub struct UiTags(pub ahash::HashMap>>); + +impl UiTags { + #[inline] + pub fn insert( + &mut self, + key: impl Into, + value: Option>, + ) { + self.0.insert(key.into(), value); + } + + #[inline] + pub fn contains(&self, key: &str) -> bool { + self.0.contains_key(key) + } + + /// Get the value of a tag. + /// + /// Note that `None` is returned both if the key is set to the value `None`, + /// and if the key is not set at all. + #[inline] + pub fn get_any(&self, key: &str) -> Option<&Arc> { + self.0.get(key)?.as_ref() + } + + /// Get the value of a tag. + /// + /// Note that `None` is returned both if the key is set to the value `None`, + /// and if the key is not set at all. + pub fn get_downcast(&self, key: &str) -> Option<&T> { + self.0.get(key)?.as_ref().and_then(|any| any.downcast_ref()) + } } // ---------------------------------------------------------------------------- @@ -96,7 +167,7 @@ impl UiStackInfo { /// 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(Clone, Debug)] +#[derive(Debug)] pub struct UiStack { // stuff that `Ui::child_ui` can deal with directly pub id: Id, @@ -119,6 +190,12 @@ impl UiStack { &self.info.frame } + /// User tags. + #[inline] + pub fn tags(&self) -> &UiTags { + &self.info.tags + } + /// Is this [`crate::Ui`] a panel? #[inline] pub fn is_panel_ui(&self) -> bool { From 820645c8dc2a1187ab0fe53a57d36364c2f00ccd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 5 Jun 2024 15:09:42 +0200 Subject: [PATCH 3/6] Fix doclink --- crates/egui/src/ui_stack.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 542b54642a0..b679649576c 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -116,7 +116,7 @@ impl UiStackInfo { /// User-chosen tags. /// /// You can use this in any way you want, -/// i.e. to set some tag on a [`Ui`] and then in your own widget check +/// i.e. to set some tag on a [`crate::Ui`] and then in your own widget check /// for the existence of this tag up the [`UiStack`]. /// /// Note that egui never sets any tags itself, so this is purely for user code. From e7f335e509ec12d1146451cc9b4a7a5a7271dd6b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 5 Jun 2024 16:00:22 +0200 Subject: [PATCH 4/6] Note that tags are transient --- crates/egui/src/ui_stack.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index b679649576c..2aadd7d0a20 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -120,6 +120,8 @@ impl UiStackInfo { /// for the existence of this tag up the [`UiStack`]. /// /// Note that egui never sets any tags itself, so this is purely for user code. +/// +/// All tagging is transient, and will only live as long as the parent [`crate::Ui`], i.e. within a single render frame. #[derive(Clone, Default, Debug)] pub struct UiTags(pub ahash::HashMap>>); From 50dc48b9e3346d8c12f3de61ef5d5226e6f33d70 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 5 Jun 2024 16:02:53 +0200 Subject: [PATCH 5/6] Add `Ui::push_stack_info` --- crates/egui/src/ui.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 425f0ad8951..84cc5684da3 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1957,7 +1957,22 @@ impl Ui { id_source: impl Hash, add_contents: impl FnOnce(&mut Ui) -> R, ) -> InnerResponse { - self.scope_dyn(Box::new(add_contents), Id::new(id_source)) + self.scope_dyn(Box::new(add_contents), Id::new(id_source), None) + } + + /// Push another level onto the [`UiStack`]. + /// + /// You can use this, for instance, to tag a group of widgets. + pub fn push_stack_info( + &mut self, + ui_stack_info: UiStackInfo, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + self.scope_dyn( + Box::new(add_contents), + Id::new("child"), + Some(ui_stack_info), + ) } /// Create a scoped child ui. @@ -1973,18 +1988,19 @@ impl Ui { /// # }); /// ``` pub fn scope(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse { - self.scope_dyn(Box::new(add_contents), Id::new("child")) + self.scope_dyn(Box::new(add_contents), Id::new("child"), None) } fn scope_dyn<'c, R>( &mut self, add_contents: Box R + 'c>, id_source: Id, + ui_stack_info: Option, ) -> InnerResponse { let child_rect = self.available_rect_before_wrap(); let next_auto_id_source = self.next_auto_id_source; let mut child_ui = - self.child_ui_with_id_source(child_rect, *self.layout(), id_source, None); + self.child_ui_with_id_source(child_rect, *self.layout(), id_source, ui_stack_info); self.next_auto_id_source = next_auto_id_source; // HACK: we want `scope` to only increment this once, so that `ui.scope` is equivalent to `ui.allocate_space`. let ret = add_contents(&mut child_ui); let response = self.allocate_rect(child_ui.min_rect(), Sense::hover()); From b5f48ed54d26ab8eb58b0faf2dfc184dd76110dc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 5 Jun 2024 16:11:50 +0200 Subject: [PATCH 6/6] Add `UiKind::is_area` --- crates/egui/src/ui_stack.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 2aadd7d0a20..7aae3f2fbeb 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -54,6 +54,7 @@ pub enum UiKind { impl UiKind { /// Is this any kind of panel? + #[inline] pub fn is_panel(&self) -> bool { matches!( self, @@ -64,6 +65,29 @@ impl UiKind { | Self::BottomPanel ) } + + /// Is this any kind of [`crate::Area`]? + #[inline] + pub fn is_area(&self) -> bool { + match self { + Self::CentralPanel + | Self::LeftPanel + | Self::RightPanel + | Self::TopPanel + | Self::BottomPanel + | Self::Frame + | Self::ScrollArea + | Self::Resize + | Self::TableCell => false, + + Self::Window + | Self::Menu + | Self::Popup + | Self::Tooltip + | Self::Picker + | Self::GenericArea => true, + } + } } // ----------------------------------------------------------------------------