From e9afd3c52dd575ea5528f7ce1e2c923ac379c01c Mon Sep 17 00:00:00 2001 From: Icekey Date: Tue, 5 Aug 2025 19:26:52 +0200 Subject: [PATCH] Add `UiBuilder::global_scope` and `UiBuilder::id` (#7372) I added a new flag to the UiBuilder so that it is possible to move child widgets around the ui tree without losing state information. Currently there is no way to create child widgets with the same id at different locations in the ui tree since ids change in relation the the parent id. With the new flag a unique global scope can be created which always results in the same ids even at different locations. You still need to ensure that the widgets only get rendered once in frame. This feature can be used to fix a issue i am having with the https://github.com/lucasmerlin/hello_egui crate. * Closes https://github.com/lucasmerlin/hello_egui/issues/75 * [X] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/ui.rs | 12 ++++++++++-- crates/egui/src/ui_builder.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 5586734b..1371f1a7 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -124,6 +124,7 @@ impl Ui { pub fn new(ctx: Context, id: Id, ui_builder: UiBuilder) -> Self { let UiBuilder { id_salt, + global_scope: _, ui_stack_info, layer_id, max_rect, @@ -250,6 +251,7 @@ impl Ui { pub fn new_child(&mut self, ui_builder: UiBuilder) -> Self { let UiBuilder { id_salt, + global_scope, ui_stack_info, layer_id, max_rect, @@ -287,8 +289,14 @@ impl Ui { } debug_assert!(!max_rect.any_nan(), "max_rect is NaN: {max_rect:?}"); - let stable_id = self.id.with(id_salt); - let unique_id = stable_id.with(self.next_auto_id_salt); + let (stable_id, unique_id) = if global_scope { + (id_salt, id_salt) + } else { + let stable_id = self.id.with(id_salt); + let unique_id = stable_id.with(self.next_auto_id_salt); + + (stable_id, unique_id) + }; let next_auto_id_salt = unique_id.value().wrapping_add(1); self.next_auto_id_salt = self.next_auto_id_salt.wrapping_add(1); diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index 54917018..e83148da 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -14,6 +14,7 @@ use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo}; #[derive(Clone, Default)] pub struct UiBuilder { pub id_salt: Option, + pub global_scope: bool, pub ui_stack_info: UiStackInfo, pub layer_id: Option, pub max_rect: Option, @@ -42,6 +43,34 @@ impl UiBuilder { self } + /// Set an id of the new `Ui` that is independent of the parent `Ui`. + /// This way child widgets can be moved in the ui tree without losing state. + /// You have to ensure that in a frame the child widgets do not get rendered in multiple places. + /// + /// You should set the same unique `id` at every place in the ui tree where you want the + /// child widgets to share state. + /// If the child widgets are not moved in the ui tree, use [`UiBuilder::id_salt`] instead. + /// + /// This is a shortcut for `.id_salt(my_id).global_scope(true)`. + #[inline] + pub fn id(mut self, id: impl Hash) -> Self { + self.id_salt = Some(Id::new(id)); + self.global_scope = true; + self + } + + /// Make the new `Ui` child ids independent of the parent `Ui`. + /// This way child widgets can be moved in the ui tree without losing state. + /// You have to ensure that in a frame the child widgets do not get rendered in multiple places. + /// + /// You should set the same globally unique `id_salt` at every place in the ui tree where you want the + /// child widgets to share state. + #[inline] + pub fn global_scope(mut self, global_scope: bool) -> Self { + self.global_scope = global_scope; + self + } + /// Provide some information about the new `Ui` being built. #[inline] pub fn ui_stack_info(mut self, ui_stack_info: UiStackInfo) -> Self {