From 3888087dc5f471f5b64436c73f9223154ae186d8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 22 May 2026 15:19:55 +0200 Subject: [PATCH] Add `AsId` and `IdSalt` (#8184) ## Related * https://github.com/emilk/egui/pull/5851 * https://github.com/emilk/egui/pull/7988 ## What We want to make it easier to understand the lineage of a `Id` (which is the parent `Id`, and the parent of that, etc?) As a first step of that, we want to clarify the different between a globally unique `Id`, and an `IdSalt` I also introduced the `AsId` and `AsIdSalt` traits, which are implemented of anything that implements `Hash` and `Debug`. The `Debug` half of that is unused here, but will later be used in `Debug` builds to produce a proper tree. --- .../egui/src/containers/collapsing_header.rs | 16 +++-- crates/egui/src/containers/combo_box.rs | 20 ++++--- crates/egui/src/containers/resize.rs | 12 ++-- crates/egui/src/containers/scroll_area.rs | 14 ++--- crates/egui/src/grid.rs | 10 ++-- crates/egui/src/id.rs | 23 ++++++-- crates/egui/src/id_salt.rs | 59 +++++++++++++++++++ crates/egui/src/lib.rs | 6 +- crates/egui/src/ui.rs | 48 +++++++-------- crates/egui/src/ui_builder.rs | 41 ++++++------- crates/egui/src/viewport.rs | 4 +- crates/egui/src/widgets/text_edit/builder.rs | 16 ++--- .../tests/snapshots/demos/ID Test.png | 4 +- crates/egui_extras/src/table.rs | 12 ++-- 14 files changed, 174 insertions(+), 111 deletions(-) create mode 100644 crates/egui/src/id_salt.rs diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index cad9d5b3e..d2312eef0 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -1,9 +1,7 @@ -use std::hash::Hash; - use crate::{ - Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle, - TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, WidgetInfo, WidgetText, WidgetType, emath, - epaint, pos2, remap, remap_clamp, vec2, + AsIdSalt, Context, Id, IdSalt, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, + TextStyle, TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, WidgetInfo, WidgetText, + WidgetType, emath, epaint, pos2, remap, remap_clamp, vec2, }; use emath::GuiRounding as _; use epaint::{Shape, StrokeKind}; @@ -371,7 +369,7 @@ pub struct CollapsingHeader { text: WidgetText, default_open: bool, open: Option, - id_salt: Id, + id_salt: IdSalt, enabled: bool, selectable: bool, selected: bool, @@ -388,7 +386,7 @@ impl CollapsingHeader { /// you need to provide a unique id source with [`Self::id_salt`]. pub fn new(text: impl Into) -> Self { let text = text.into(); - let id_salt = Id::new(text.text()); + let id_salt = IdSalt::new(text.text()); Self { text, default_open: false, @@ -424,8 +422,8 @@ impl CollapsingHeader { /// Explicitly set the source of the [`Id`] of this widget, instead of using title label. /// This is useful if the title label is dynamic or not unique. #[inline] - pub fn id_salt(mut self, id_salt: impl Hash) -> Self { - self.id_salt = Id::new(id_salt); + pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self { + self.id_salt = IdSalt::new(id_salt); self } diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index adbda583c..a5d925827 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -1,9 +1,11 @@ use epaint::Shape; use crate::{ - Align2, Context, Id, InnerResponse, NumExt as _, Painter, Popup, PopupCloseBehavior, Rect, - Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui, UiBuilder, Vec2, WidgetInfo, - WidgetText, WidgetType, epaint, style::StyleModifier, style::WidgetVisuals, vec2, + Align2, AsIdSalt, Context, Id, IdSalt, InnerResponse, NumExt as _, Painter, Popup, + PopupCloseBehavior, Rect, Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui, + UiBuilder, Vec2, WidgetInfo, WidgetText, WidgetType, epaint, + style::{StyleModifier, WidgetVisuals}, + vec2, }; #[expect(unused_imports)] // Documentation @@ -36,7 +38,7 @@ pub type IconPainter = Box; /// ``` #[must_use = "You should call .show*"] pub struct ComboBox { - id_salt: Id, + id_salt: IdSalt, label: Option, selected_text: WidgetText, width: Option, @@ -49,9 +51,9 @@ pub struct ComboBox { impl ComboBox { /// Create new [`ComboBox`] with id and label - pub fn new(id_salt: impl std::hash::Hash, label: impl Into) -> Self { + pub fn new(id_salt: impl AsIdSalt, label: impl Into) -> Self { Self { - id_salt: Id::new(id_salt), + id_salt: IdSalt::new(id_salt), label: Some(label.into()), selected_text: Default::default(), width: None, @@ -67,7 +69,7 @@ impl ComboBox { pub fn from_label(label: impl Into) -> Self { let label = label.into(); Self { - id_salt: Id::new(label.text()), + id_salt: IdSalt::new(label.text()), label: Some(label), selected_text: Default::default(), width: None, @@ -80,9 +82,9 @@ impl ComboBox { } /// Without label. - pub fn from_id_salt(id_salt: impl std::hash::Hash) -> Self { + pub fn from_id_salt(id_salt: impl AsIdSalt) -> Self { Self { - id_salt: Id::new(id_salt), + id_salt: IdSalt::new(id_salt), label: Default::default(), selected_text: Default::default(), width: None, diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 4c21930ae..b6c086aca 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -1,6 +1,6 @@ use crate::{ - Align2, Color32, Context, CursorIcon, Id, NumExt as _, Rect, Response, Sense, Shape, Ui, - UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, pos2, vec2, + Align2, AsIdSalt, Color32, Context, CursorIcon, Id, IdSalt, NumExt as _, Rect, Response, Sense, + Shape, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, pos2, vec2, }; #[derive(Clone, Copy, Debug)] @@ -41,7 +41,7 @@ impl State { #[must_use = "You should call .show()"] pub struct Resize { id: Option, - id_salt: Option, + id_salt: Option, /// If false, we are no enabled resizable: Vec2b, @@ -78,8 +78,8 @@ impl Resize { /// A source for the unique [`Id`], e.g. `.id_salt("second_resize_area")` or `.id_salt(loop_index)`. #[inline] - pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self { - self.id_salt = Some(Id::new(id_salt)); + pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self { + self.id_salt = Some(IdSalt::new(id_salt)); self } @@ -209,7 +209,7 @@ impl Resize { fn begin(&self, ui: &mut Ui) -> Prepared { let position = ui.available_rect_before_wrap().min; let id = self.id.unwrap_or_else(|| { - let id_salt = self.id_salt.unwrap_or_else(|| Id::new("resize")); + let id_salt = self.id_salt.unwrap_or_else(|| IdSalt::new("resize")); ui.make_persistent_id(id_salt) }); diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 0d1187b95..2672683c9 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -8,9 +8,9 @@ use emath::GuiRounding as _; use epaint::{Color32, Direction, Margin, Shape}; use crate::{ - Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder, - UiKind, UiStackInfo, Vec2, Vec2b, WidgetInfo, emath, epaint, lerp, pass_state, pos2, remap, - remap_clamp, + AsIdSalt, Context, CursorIcon, Id, IdSalt, NumExt as _, Pos2, Rangef, Rect, Response, Sense, + Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, WidgetInfo, emath, epaint, lerp, pass_state, + pos2, remap, remap_clamp, }; #[derive(Clone, Copy, Debug)] @@ -344,7 +344,7 @@ pub struct ScrollArea { min_scrolled_size: Vec2, scroll_bar_visibility: ScrollBarVisibility, scroll_bar_rect: Option, - id_salt: Option, + id_salt: Option, offset_x: Option, offset_y: Option, on_hover_cursor: Option, @@ -479,8 +479,8 @@ impl ScrollArea { /// A source for the unique [`Id`], e.g. `.id_salt("second_scroll_area")` or `.id_salt(loop_index)`. #[inline] - pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self { - self.id_salt = Some(Id::new(id_salt)); + pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self { + self.id_salt = Some(IdSalt::new(id_salt)); self } @@ -730,7 +730,7 @@ impl ScrollArea { let ctx = ui.ctx().clone(); - let id_salt = id_salt.unwrap_or_else(|| Id::new("scroll_area")); + let id_salt = id_salt.unwrap_or_else(|| IdSalt::new("scroll_area")); let id = ui.make_persistent_id(id_salt); ctx.check_for_id_clash( id, diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index 0cf5c95df..e5c20f05f 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use emath::GuiRounding as _; use crate::{ - Align2, Color32, Context, Id, InnerResponse, NumExt as _, Painter, Rect, Region, Style, Ui, - UiBuilder, Vec2, vec2, + Align2, AsIdSalt, Color32, Context, Id, IdSalt, InnerResponse, NumExt as _, Painter, Rect, + Region, Style, Ui, UiBuilder, Vec2, vec2, }; #[cfg(debug_assertions)] @@ -312,7 +312,7 @@ impl GridLayout { /// ``` #[must_use = "You should call .show()"] pub struct Grid { - id_salt: Id, + id_salt: IdSalt, num_columns: Option, min_col_width: Option, min_row_height: Option, @@ -324,9 +324,9 @@ pub struct Grid { impl Grid { /// Create a new [`Grid`] with a locally unique identifier. - pub fn new(id_salt: impl std::hash::Hash) -> Self { + pub fn new(id_salt: impl AsIdSalt) -> Self { Self { - id_salt: Id::new(id_salt), + id_salt: IdSalt::new(id_salt), num_columns: None, min_col_width: None, min_row_height: None, diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 661bdf2bf..c0e21fbe2 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -2,6 +2,16 @@ use std::num::NonZeroU64; +use crate::{AsIdSalt, IdSalt}; + +/// Types that can be converted to an [`Id`]. +/// +/// This is all types implementing `Hash` and `Debug`, +/// which includes things like string, integers, tuples of those, etc. +pub trait AsId: std::hash::Hash + std::fmt::Debug {} + +impl AsId for T {} + /// egui tracks widgets frame-to-frame using [`Id`]s. /// /// For instance, if you start dragging a slider one frame, egui stores @@ -43,6 +53,7 @@ impl Id { /// though obviously it will lead to a lot of collisions if you do use it! pub const NULL: Self = Self(NonZeroU64::MAX); + /// Create a new root [`Id`] from a high-entropy hash. #[inline] const fn from_hash(hash: u64) -> Self { if let Some(nonzero) = NonZeroU64::new(hash) { @@ -52,17 +63,17 @@ impl Id { } } - /// Generate a new [`Id`] by hashing some source (e.g. a string or integer). - pub fn new(source: impl std::hash::Hash) -> Self { + /// Generate a new root [`Id`] by hashing some source (e.g. a string or integer). + pub fn new(source: impl AsId) -> Self { Self::from_hash(ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source)) } - /// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument. - pub fn with(self, child: impl std::hash::Hash) -> Self { + /// Generate a child [`Id`] by salting the parent [`Id`] with the given argument. + pub fn with(self, salt: impl AsIdSalt) -> Self { use std::hash::{BuildHasher as _, Hasher as _}; let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher(); - hasher.write_u64(self.0.get()); - child.hash(&mut hasher); + hasher.write_u64(self.value()); + hasher.write_u64(IdSalt::new(salt).value()); Self::from_hash(hasher.finish()) } diff --git a/crates/egui/src/id_salt.rs b/crates/egui/src/id_salt.rs new file mode 100644 index 000000000..0912dcbd3 --- /dev/null +++ b/crates/egui/src/id_salt.rs @@ -0,0 +1,59 @@ +use std::num::NonZeroU64; + +/// Types that can be converted to an [`IdSalt`]. +/// +/// This is all types implementing `Hash` and `Debug`, +/// which includes things like string, integers, tuples of those, etc. +pub trait AsIdSalt: std::hash::Hash + std::fmt::Debug {} + +impl AsIdSalt for T {} + +/// Uniquely identifies a child widget within a parent widget. +/// +/// An [`IdSalt`] is only unique within a parent [`crate::Id`]. +/// An [`IdSalt`] is NOT globally unique. +/// +/// You combine a parent [`crate::Id`] with an [`IdSalt`] to get a child [`crate::Id`], +/// using [`crate::Id::with`]. +/// +/// An [`IdSalt`] is usually a string, an integer, or similar. +/// +/// An [`IdSalt`] should NOT be produced from an [`crate::Id`]. +/// +/// This is niche-optimized to that `Option` is the same size as `IdSalt`. +#[derive(Clone, Copy, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct IdSalt(NonZeroU64); + +impl nohash_hasher::IsEnabled for IdSalt {} + +impl IdSalt { + /// Create a new [`IdSalt`] by hashing some source (e.g. a string or integer). + pub fn new(source: impl AsIdSalt) -> Self { + Self::from_hash(ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source)) + } + + /// Create a new root [`IdSalt`] from a high-entropy hash. + #[inline] + const fn from_hash(hash: u64) -> Self { + if let Some(nonzero) = NonZeroU64::new(hash) { + Self(nonzero) + } else { + Self(NonZeroU64::MIN) // The hash was exactly zero + } + } + + /// The inner value of the [`IdSalt`]. + /// + /// This is a high-entropy hash. + #[inline(always)] + pub fn value(&self) -> u64 { + self.0.get() + } +} + +impl std::fmt::Debug for IdSalt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "salt_{:04X}", self.value() as u16) + } +} diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index f88372efd..681aacdd0 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -400,6 +400,7 @@ pub(crate) mod grid; pub mod gui_zoom; mod hit_test; mod id; +mod id_salt; mod input_state; mod interaction; pub mod introspection; @@ -473,7 +474,8 @@ pub use self::{ drag_and_drop::DragAndDrop, epaint::text::TextWrapMode, grid::Grid, - id::{Id, IdMap, IdSet}, + id::{AsId, Id, IdMap, IdSet}, + id_salt::{AsIdSalt, IdSalt}, input_state::{InputOptions, InputState, MultiTouchInfo, PointerState, SurrenderFocusOn}, layers::{LayerId, Order}, layout::*, @@ -486,7 +488,7 @@ pub use self::{ style::{FontSelection, Spacing, Style, TextStyle, Visuals}, text::{Galley, TextFormat}, ui::Ui, - ui_builder::UiBuilder, + ui_builder::{IdSource, UiBuilder}, ui_stack::*, viewport::*, widget_rect::{InteractOptions, WidgetRect, WidgetRects}, diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 81508e940..840682979 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1,12 +1,13 @@ #![warn(missing_docs)] // Let's keep `Ui` well-documented. #![expect(clippy::use_self)] -use std::{any::Any, hash::Hash, ops::Deref, sync::Arc}; +use std::{any::Any, ops::Deref, sync::Arc}; use crate::containers::menu; use crate::widget_style::{HasClasses as _, ROOT_CLASS}; -use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *}; +use crate::{IdSource, containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *}; use emath::GuiRounding as _; + // ---------------------------------------------------------------------------- /// This is what you use to place widgets. @@ -106,8 +107,7 @@ impl Ui { /// [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. pub fn new(ctx: Context, id: Id, ui_builder: UiBuilder) -> Self { let UiBuilder { - id_salt, - global_scope: _, + id_source, ui_stack_info, layer_id, max_rect, @@ -124,8 +124,8 @@ impl Ui { let layer_id = layer_id.unwrap_or_else(LayerId::background); debug_assert!( - id_salt.is_none(), - "Top-level Ui:s should not have an id_salt" + id_source.is_none(), + "Top-level Ui:s should not have an UiBuilder::id_source" ); let max_rect = max_rect.unwrap_or_else(|| ctx.content_rect()); @@ -207,8 +207,7 @@ impl Ui { /// [`Ui::advance_cursor_after_rect`]. pub fn new_child(&mut self, ui_builder: UiBuilder) -> Self { let UiBuilder { - id_salt, - global_scope, + id_source, ui_stack_info, layer_id, max_rect, @@ -224,7 +223,6 @@ impl Ui { let mut painter = self.painter.clone(); - let id_salt = id_salt.unwrap_or_else(|| Id::from("child")); let max_rect = max_rect.unwrap_or_else(|| self.available_rect_before_wrap()); let mut layout = layout.unwrap_or_else(|| *self.layout()); let enabled = self.enabled && !disabled && !invisible; @@ -248,13 +246,15 @@ impl Ui { } debug_assert!(!max_rect.any_nan(), "max_rect is NaN: {max_rect:?}"); - 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 id_source = id_source.unwrap_or_else(|| IdSource::Child(IdSalt::new("child"))); + let (stable_id, unique_id) = match id_source { + IdSource::Explicit(id) => (id, id), + IdSource::Child(id_salt) => { + 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); @@ -880,11 +880,8 @@ impl Ui { /// # [`Id`] creation impl Ui { /// Use this to generate widget ids for widgets that have persistent state in [`Memory`]. - pub fn make_persistent_id(&self, id_salt: IdSource) -> Id - where - IdSource: Hash, - { - self.id.with(&id_salt) + pub fn make_persistent_id(&self, id_salt: impl AsIdSalt) -> Id { + self.id.with(id_salt) } /// This is the `Id` that will be assigned to the next widget added to this `Ui`. @@ -893,10 +890,7 @@ impl Ui { } /// Same as `ui.next_auto_id().with(id_salt)` - pub fn auto_id_with(&self, id_salt: IdSource) -> Id - where - IdSource: Hash, - { + pub fn auto_id_with(&self, id_salt: impl AsIdSalt) -> Id { Id::new(self.next_auto_id_salt).with(id_salt) } @@ -2168,7 +2162,7 @@ impl Ui { /// ``` pub fn push_id( &mut self, - id_salt: impl Hash, + id_salt: impl AsIdSalt, add_contents: impl FnOnce(&mut Ui) -> R, ) -> InnerResponse { self.scope_dyn(UiBuilder::new().id_salt(id_salt), Box::new(add_contents)) @@ -2233,7 +2227,7 @@ impl Ui { #[inline] pub fn indent( &mut self, - id_salt: impl Hash, + id_salt: impl AsIdSalt, add_contents: impl FnOnce(&mut Ui) -> R, ) -> InnerResponse { self.indent_dyn(id_salt, Box::new(add_contents)) @@ -2241,7 +2235,7 @@ impl Ui { fn indent_dyn<'c, R>( &mut self, - id_salt: impl Hash, + id_salt: impl AsIdSalt, add_contents: Box R + 'c>, ) -> InnerResponse { assert!( diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index a8121e235..893b34468 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -1,10 +1,11 @@ -use std::{hash::Hash, sync::Arc}; +use std::sync::Arc; #[expect(unused_imports)] // Used for doclinks use crate::Ui; -use crate::widget_style::HasClasses; -use crate::{ClosableTag, widget_style::Classes}; -use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo}; +use crate::{ + AsIdSalt, ClosableTag, Id, IdSalt, LayerId, Layout, Rect, Sense, Style, UiStackInfo, + widget_style::{Classes, HasClasses}, +}; /// Build a [`Ui`] as the child of another [`Ui`]. /// @@ -14,8 +15,7 @@ use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo}; #[must_use] #[derive(Clone, Default)] pub struct UiBuilder { - pub id_salt: Option, - pub global_scope: bool, + pub id_source: Option, pub ui_stack_info: UiStackInfo, pub layer_id: Option, pub max_rect: Option, @@ -29,6 +29,16 @@ pub struct UiBuilder { pub classes: Classes, } +/// Is this [`Ui`] a root or a child of another [`Ui`]? +#[derive(Clone)] +pub enum IdSource { + /// Explicitly use this [`Id`] + Explicit(Id), + + /// Salt the parent [`Id`] with this. + Child(IdSalt), +} + impl UiBuilder { #[inline] pub fn new() -> Self { @@ -41,8 +51,8 @@ impl UiBuilder { /// You should give each [`Ui`] an `id_salt` that is unique /// within the parent, or give it none at all. #[inline] - pub fn id_salt(mut self, id_salt: impl Hash) -> Self { - self.id_salt = Some(Id::new(id_salt)); + pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self { + self.id_source = Some(IdSource::Child(IdSalt::new(id_salt))); self } @@ -57,20 +67,7 @@ impl UiBuilder { /// This is a shortcut for `.id_salt(my_id).global_scope(true)`. #[inline] pub fn id(mut self, id: Id) -> Self { - self.id_salt = Some(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.id_source = Some(IdSource::Explicit(id)); self } diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index ac8704961..a94f1f637 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -73,7 +73,7 @@ use std::sync::Arc; use epaint::{Pos2, Vec2}; -use crate::{Context, Id, Ui}; +use crate::{AsId, Context, Id, Ui}; // ---------------------------------------------------------------------------- @@ -150,7 +150,7 @@ impl ViewportId { pub const ROOT: Self = Self(Id::NULL); #[inline] - pub fn from_hash_of(source: impl std::hash::Hash) -> Self { + pub fn from_hash_of(source: impl AsId) -> Self { Self(Id::new(source)) } } diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 7cb28d8f8..1489fc67c 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -4,10 +4,10 @@ use emath::{Rect, TSTransform}; use epaint::text::{Galley, LayoutJob, TextWrapMode, cursor::CCursor}; use crate::{ - Align, Align2, AtomExt as _, AtomKind, AtomLayout, Atoms, Color32, Context, CursorIcon, Event, - EventFilter, FontSelection, Frame, Id, ImeEvent, IntoAtoms, IntoSizedResult, Key, - KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense, SizedAtomKind, TextBuffer, - TextStyle, Ui, Vec2, Widget, WidgetInfo, WidgetWithState, epaint, + Align, Align2, AsIdSalt, AtomExt as _, AtomKind, AtomLayout, Atoms, Color32, Context, + CursorIcon, Event, EventFilter, FontSelection, Frame, Id, IdSalt, ImeEvent, IntoAtoms, + IntoSizedResult, Key, KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense, + SizedAtomKind, TextBuffer, TextStyle, Ui, Vec2, Widget, WidgetInfo, WidgetWithState, epaint, os::OperatingSystem, output::OutputEvent, response, @@ -72,7 +72,7 @@ pub struct TextEdit<'t> { suffix: Atoms<'static>, hint_text: Atoms<'static>, id: Option, - id_salt: Option, + id_salt: Option, font_selection: FontSelection, text_color: Option, layouter: Option>, @@ -171,14 +171,14 @@ impl<'t> TextEdit<'t> { /// A source for the unique [`Id`], e.g. `.id_source("second_text_edit_field")` or `.id_source(loop_index)`. #[inline] - pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self { + pub fn id_source(self, id_salt: impl AsIdSalt) -> Self { self.id_salt(id_salt) } /// A source for the unique [`Id`], e.g. `.id_salt("second_text_edit_field")` or `.id_salt(loop_index)`. #[inline] - pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self { - self.id_salt = Some(Id::new(id_salt)); + pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self { + self.id_salt = Some(IdSalt::new(id_salt)); self } diff --git a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png index cbbec3f61..f26458ea5 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d14571fdd7602ea4924d04725008433e1bb53596d8a279f310ddc6c3231b6d32 -size 114106 +oid sha256:c9b497aa9b2b92843937c84a6ff8901f248501d5d4a89c2fb1237008a44fcad1 +size 114038 diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 4832a313f..9147b3c0d 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -4,7 +4,7 @@ //! Takes all available height, so if you want something below the table, put it in a strip. use egui::{ - Align, Id, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b, + Align, AsIdSalt, IdSalt, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b, scroll_area::{DragScroll, ScrollAreaOutput, ScrollBarVisibility, ScrollSource}, }; @@ -246,7 +246,7 @@ impl Default for TableScrollOptions { /// ``` pub struct TableBuilder<'a> { ui: &'a mut Ui, - id_salt: Id, + id_salt: IdSalt, columns: Vec, striped: Option, resizable: bool, @@ -260,7 +260,7 @@ impl<'a> TableBuilder<'a> { let cell_layout = *ui.layout(); Self { ui, - id_salt: Id::new("__table_state"), + id_salt: IdSalt::new("__table_state"), columns: Default::default(), striped: None, resizable: false, @@ -270,12 +270,12 @@ impl<'a> TableBuilder<'a> { } } - /// Give this table a unique id within the parent [`Ui`]. + /// Give this table a unique salt within the parent [`Ui`]. /// /// This is required if you have multiple tables in the same [`Ui`]. #[inline] - pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self { - self.id_salt = Id::new(id_salt); + pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self { + self.id_salt = IdSalt::new(id_salt); self }