From 70435084a98b4e9bdd153c0c716219abd6ee0b9c Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Wed, 18 Mar 2026 16:55:42 +0100 Subject: [PATCH 1/4] Show id bookkeeping in id test demo --- crates/egui_demo_lib/src/demo/tests/id_test.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/src/demo/tests/id_test.rs b/crates/egui_demo_lib/src/demo/tests/id_test.rs index 6c6895b75..73a29d744 100644 --- a/crates/egui_demo_lib/src/demo/tests/id_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/id_test.rs @@ -47,8 +47,14 @@ impl crate::View for IdTest { (and if it is, the window will have a new layout, and the slider will end up somewhere else, and so aborting the interaction probably makes sense)."); ui.label("So these buttons have automatic Id:s, and therefore there is no name clash:"); + let button_response = ui.button("Button"); let _ = ui.button("Button"); - let _ = ui.button("Button"); + + ui.label("Use id.ui() to show a interactive debug ui that explains how a id was derived:"); + button_response.id.ui(ui); + + ui.label("Debug formatting the id will also show the hierarchy (useful when logging ids):"); + ui.code(format!("{:?}", button_response.id)); ui.vertical_centered(|ui| { ui.add(crate::egui_github_link_file!()); From 3ac34b3efb180e8c7e51747d9fde38ce1a4479c6 Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Thu, 19 Mar 2026 12:42:43 +0100 Subject: [PATCH 2/4] Clippy --- crates/egui/src/context.rs | 10 ++--- crates/egui/src/id.rs | 89 ++++++-------------------------------- crates/egui/src/ui.rs | 2 - 3 files changed, 18 insertions(+), 83 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index e487bee47..d69b6a648 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -16,11 +16,11 @@ use epaint::{ use crate::{ Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport, - ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory, - ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText, - SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, - UiBuilder, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, - ViewportIdSet, ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText, + ImmediateViewportRendererCallback, Key, KeyboardShortcut, LayerId, Memory, ModifierNames, + Modifiers, NumExt as _, Order, Painter, RawInput, Response, SafeAreaInsets, ScrollArea, Sense, + Style, TextStyle, TextureHandle, TextureOptions, Ui, UiBuilder, ViewportBuilder, + ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput, + Visuals, Widget as _, WidgetRect, WidgetText, animation_manager::AnimationManager, containers::{self, area::AreaState}, data::output::PlatformOutput, diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 439fa614d..0d2f53cb0 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -1,7 +1,7 @@ // TODO(emilk): have separate types `PositionId` and `UniqueId`. ? -use crate::id::id_source::IdSource; use crate::CollapsingHeader; +use crate::id::id_source::IdSource; use epaint::Color32; use std::num::NonZeroU64; @@ -73,7 +73,7 @@ impl Id { 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); + child.hash(&mut hasher); let id = Self::from_hash(hasher.finish()); #[cfg(debug_assertions)] @@ -118,70 +118,7 @@ impl Id { Self(NonZeroU64::new(value).expect("Id must be non-zero.")) } - fn source_ui(ui: &mut crate::Ui, source: IdSource) { - match source { - IdSource::Id(id) => { - Self::parent_ui(ui, id); - } - IdSource::Other(other) => { - ui.code(other); - } - } - } - - fn parent_ui(ui: &mut crate::Ui, id: Id) { - let data = id.info(); - if let Some(data) = data { - if let Some(parent) = data.parent { - Self::parent_ui(ui, parent); - ui.horizontal(|ui| { - ui.code(".with("); - Self::source_ui(ui, data.source); - ui.code(format!(" /* {} */", id.short_debug_format())); - ui.code(")"); - }); - } else { - ui.horizontal(|ui| { - ui.code("Id::new("); - Self::source_ui(ui, data.source); - ui.code(format!(" /* {} */", id.short_debug_format())); - ui.code(")"); - }); - } - } else { - ui.code(format!("Id::from_hash({})", id.short_debug_format())); - } - } - - fn group_ui(ui: &mut crate::Ui, id: Id) { - ui.group(|ui| { - let info = id.info(); - if let Some(info) = info { - ui.horizontal(|ui| { - ui.label("Id("); - ui.code(format!("{:04X}", id.value() as u16)); - ui.label(")"); - - ui.label("Source:"); - match info.source { - IdSource::Id(id) => { - Self::group_ui(ui, id); - } - IdSource::Other(other) => { - ui.code(other); - } - } - }); - if let Some(parent) = info.parent { - ui.label("^ with"); - Self::group_ui(ui, parent); - } - } else { - } - }); - } - - fn tree_ui(ui: &mut crate::Ui, id: Id, prefix: &str, depth: usize) { + fn tree_ui(ui: &mut crate::Ui, id: Self, prefix: &str, depth: usize) { let info = id.info(); if let Some(info) = info { let response = @@ -273,11 +210,11 @@ mod id_source { impl Display for IdSource { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - IdSource::Id(id) => { + Self::Id(id) => { write!(f, "{}", id.short_debug_format()) } - IdSource::Other(other) => { - write!(f, "{}", other) + Self::Other(other) => { + write!(f, "{other}") } } } @@ -319,7 +256,7 @@ mod id_source { } fn write_u64(&mut self, i: u64) { - if !self.not_id && !self.val.is_some() { + if !self.not_id && self.val.is_none() { self.val = Some(i); } else { self.not_id = true; @@ -345,10 +282,10 @@ mod id_source { if ID_MAP.read().contains_key(&maybe_source_id) { IdSource::Id(maybe_source_id) } else { - IdSource::Other(format!("{:?}", t)) + IdSource::Other(format!("{t:?}")) } } else { - IdSource::Other(format!("{:?}", t)) + IdSource::Other(format!("{t:?}")) } } @@ -373,7 +310,7 @@ mod id_source { #[test] fn test_fake_hasher() { - use std::hash::Hash; + use std::hash::Hash as _; let mut hasher = ExtractIdHasher::default(); let id = Id::new("test"); @@ -391,15 +328,15 @@ impl std::fmt::Debug for Id { if let Some(info) = self.info() { match info.source { id_source::IdSource::Id(source_id) => { - write!(f, "({:?})", source_id)?; + write!(f, "({source_id:?})")?; } id_source::IdSource::Other(label) => { - write!(f, " ({})", label)?; + write!(f, " ({label})")?; } } if let Some(parent) = info.parent { // Let's hope there are no cycles! - write!(f, " <- {:?}", parent)?; + write!(f, " <- {parent:?}")?; } } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 8ff59350d..e58503bb1 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -7,8 +7,6 @@ use emath::GuiRounding as _; use epaint::mutex::RwLock; use crate::containers::menu; -#[cfg(debug_assertions)] -use crate::Stroke; use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *}; // ---------------------------------------------------------------------------- From ac3564a2ee8ce59c5134c108f3314295ebc145c8 Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Thu, 19 Mar 2026 13:01:33 +0100 Subject: [PATCH 3/4] Fix release build --- crates/egui/src/id.rs | 88 +++++++++++++------------ examples/hello_world_simple/src/main.rs | 13 ---- 2 files changed, 47 insertions(+), 54 deletions(-) diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 0d2f53cb0..a02052fba 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -1,7 +1,5 @@ // TODO(emilk): have separate types `PositionId` and `UniqueId`. ? -use crate::CollapsingHeader; -use crate::id::id_source::IdSource; use epaint::Color32; use std::num::NonZeroU64; @@ -39,6 +37,7 @@ pub struct Id(NonZeroU64); impl nohash_hasher::IsEnabled for Id {} pub trait AsId: std::hash::Hash + std::fmt::Debug {} + impl AsId for T {} impl Id { @@ -118,37 +117,6 @@ impl Id { Self(NonZeroU64::new(value).expect("Id must be non-zero.")) } - fn tree_ui(ui: &mut crate::Ui, id: Self, prefix: &str, depth: usize) { - let info = id.info(); - if let Some(info) = info { - let response = - CollapsingHeader::new(format!("{}Id({})", prefix, id.short_debug_format())) - .default_open(depth < 4) - .show(ui, |ui| { - match info.source { - IdSource::Id(id_source) => { - Self::tree_ui(ui, id_source, "Source: ", depth + 1); - } - IdSource::Other(other) => { - ui.horizontal(|ui| { - ui.add_space(ui.spacing().indent); - ui.label("Source:"); - ui.code(other); - }); - } - } - - if let Some(parent) = info.parent { - Self::tree_ui(ui, parent, "Parent: ", depth + 1); - } - }); - - if response.header_response.hovered() { - id.try_highlight(ui.ctx()); - } - } - } - pub fn try_highlight(self, ctx: &crate::Context) { let response = ctx.read_response(self); if let Some(response) = response { @@ -166,13 +134,16 @@ impl Id { } pub fn ui(self, ui: &mut crate::Ui) -> crate::Response { - let data = self.info(); - let label = if let Some(data) = &data { - format!("{} ({})", self.short_debug_format(), data.source) - } else { - self.short_debug_format() - }; - let response = ui.code(label).on_hover_ui(|ui| { + #[cfg(debug_assertions)] + let debug_label = self + .info() + .map(|info| format!("{} ({})", self.short_debug_format(), info.source)); + #[cfg(not(debug_assertions))] + let debug_label: Option = None; + let response = ui.code(debug_label.unwrap_or_else(|| self.short_debug_format())); + + #[cfg(debug_assertions)] + let response = response.on_hover_ui(|ui| { Self::tree_ui(ui, self, "", 0); }); @@ -186,7 +157,7 @@ impl Id { #[cfg(debug_assertions)] mod id_source { - use crate::{AsId, Id}; + use crate::{AsId, CollapsingHeader, Id}; use ahash::HashMap; use epaint::mutex::RwLock; use std::fmt::{Display, Formatter}; @@ -197,6 +168,7 @@ mod id_source { pub struct IdInfo { /// What was this Id generated from? pub source: IdSource, + /// If the Id was crated via [`Id::with`], what was the parent Id? pub parent: Option, } @@ -303,9 +275,43 @@ mod id_source { } impl Id { + /// Get info about this id (what source was it generated from, what parent does it have)? + /// + /// Only available with `#[cfg(debug_assertions)]`. pub fn info(&self) -> Option { ID_MAP.read().get(self).cloned() } + + pub(super) fn tree_ui(ui: &mut crate::Ui, id: Self, prefix: &str, depth: usize) { + let info = id.info(); + if let Some(info) = info { + let response = + CollapsingHeader::new(format!("{}Id({})", prefix, id.short_debug_format())) + .default_open(depth < 4) + .show(ui, |ui| { + match info.source { + IdSource::Id(id_source) => { + Self::tree_ui(ui, id_source, "Source: ", depth + 1); + } + IdSource::Other(other) => { + ui.horizontal(|ui| { + ui.add_space(ui.spacing().indent); + ui.label("Source:"); + ui.code(other); + }); + } + } + + if let Some(parent) = info.parent { + Self::tree_ui(ui, parent, "Parent: ", depth + 1); + } + }); + + if response.header_response.hovered() { + id.try_highlight(ui.ctx()); + } + } + } } #[test] diff --git a/examples/hello_world_simple/src/main.rs b/examples/hello_world_simple/src/main.rs index 959c67484..cbdfcf1f1 100644 --- a/examples/hello_world_simple/src/main.rs +++ b/examples/hello_world_simple/src/main.rs @@ -2,7 +2,6 @@ #![expect(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; -use eframe::egui::{Id, Ui}; fn main() -> eframe::Result { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). @@ -29,18 +28,6 @@ fn main() -> eframe::Result { age += 1; } ui.label(format!("Hello '{name}', age {age}")); - - Id::new(Id::new("Hi").with((123, "456"))) - .with(Id::new("lol")) - .ui(ui); - - ui.id().ui(ui); - Id::ui(Ui::id(ui), ui); - - ui.unique_id().ui(ui); - - let some_button = ui.button("Some button"); - some_button.id.ui(ui); }); }) } From 9b7fc3c18fef0f3bffa043ec4247ddc51e5be685 Mon Sep 17 00:00:00 2001 From: lucasmerlin <8009393+lucasmerlin@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:04:00 +0000 Subject: [PATCH 4/4] Update snapshot images --- crates/egui_demo_lib/tests/snapshots/demos/ID Test.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 abd7c485b..bf9b544a9 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:82878e4150e38fdc4b2e78203c8c661c2d9e716ab32595c298392faf6ba96105 -size 113803 +oid sha256:75ace10713ebc89c4e70f5c75532b2fc0b05140c72111ae361febba12cb4ef9d +size 139886