mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 22:00:03 -04:00
Merge branch 'lucas/experiments/helpful-id-debug' into lucas/better-ui-id-bookkeeping
This commit is contained in:
@@ -16,11 +16,11 @@ use epaint::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport,
|
Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport,
|
||||||
ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory,
|
ImmediateViewportRendererCallback, Key, KeyboardShortcut, LayerId, Memory, ModifierNames,
|
||||||
ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText,
|
Modifiers, NumExt as _, Order, Painter, RawInput, Response, SafeAreaInsets, ScrollArea, Sense,
|
||||||
SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui,
|
Style, TextStyle, TextureHandle, TextureOptions, Ui, UiBuilder, ViewportBuilder,
|
||||||
UiBuilder, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair,
|
ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput,
|
||||||
ViewportIdSet, ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText,
|
Visuals, Widget as _, WidgetRect, WidgetText,
|
||||||
animation_manager::AnimationManager,
|
animation_manager::AnimationManager,
|
||||||
containers::{self, area::AreaState},
|
containers::{self, area::AreaState},
|
||||||
data::output::PlatformOutput,
|
data::output::PlatformOutput,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// TODO(emilk): have separate types `PositionId` and `UniqueId`. ?
|
// TODO(emilk): have separate types `PositionId` and `UniqueId`. ?
|
||||||
|
|
||||||
use crate::id::id_source::IdSource;
|
|
||||||
use crate::CollapsingHeader;
|
|
||||||
use epaint::Color32;
|
use epaint::Color32;
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
@@ -39,6 +37,7 @@ pub struct Id(NonZeroU64);
|
|||||||
impl nohash_hasher::IsEnabled for Id {}
|
impl nohash_hasher::IsEnabled for Id {}
|
||||||
|
|
||||||
pub trait AsId: std::hash::Hash + std::fmt::Debug {}
|
pub trait AsId: std::hash::Hash + std::fmt::Debug {}
|
||||||
|
|
||||||
impl<T: std::hash::Hash + std::fmt::Debug> AsId for T {}
|
impl<T: std::hash::Hash + std::fmt::Debug> AsId for T {}
|
||||||
|
|
||||||
impl Id {
|
impl Id {
|
||||||
@@ -73,7 +72,7 @@ impl Id {
|
|||||||
use std::hash::{BuildHasher as _, Hasher as _};
|
use std::hash::{BuildHasher as _, Hasher as _};
|
||||||
let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
|
let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
|
||||||
hasher.write_u64(self.0.get());
|
hasher.write_u64(self.0.get());
|
||||||
(&child).hash(&mut hasher);
|
child.hash(&mut hasher);
|
||||||
let id = Self::from_hash(hasher.finish());
|
let id = Self::from_hash(hasher.finish());
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
@@ -118,100 +117,6 @@ impl Id {
|
|||||||
Self(NonZeroU64::new(value).expect("Id must be non-zero."))
|
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) {
|
|
||||||
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) {
|
pub fn try_highlight(self, ctx: &crate::Context) {
|
||||||
let response = ctx.read_response(self);
|
let response = ctx.read_response(self);
|
||||||
if let Some(response) = response {
|
if let Some(response) = response {
|
||||||
@@ -229,13 +134,16 @@ impl Id {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ui(self, ui: &mut crate::Ui) -> crate::Response {
|
pub fn ui(self, ui: &mut crate::Ui) -> crate::Response {
|
||||||
let data = self.info();
|
#[cfg(debug_assertions)]
|
||||||
let label = if let Some(data) = &data {
|
let debug_label = self
|
||||||
format!("{} ({})", self.short_debug_format(), data.source)
|
.info()
|
||||||
} else {
|
.map(|info| format!("{} ({})", self.short_debug_format(), info.source));
|
||||||
self.short_debug_format()
|
#[cfg(not(debug_assertions))]
|
||||||
};
|
let debug_label: Option<String> = None;
|
||||||
let response = ui.code(label).on_hover_ui(|ui| {
|
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);
|
Self::tree_ui(ui, self, "", 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -249,7 +157,7 @@ impl Id {
|
|||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
mod id_source {
|
mod id_source {
|
||||||
use crate::{AsId, Id};
|
use crate::{AsId, CollapsingHeader, Id};
|
||||||
use ahash::HashMap;
|
use ahash::HashMap;
|
||||||
use epaint::mutex::RwLock;
|
use epaint::mutex::RwLock;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
@@ -260,6 +168,7 @@ mod id_source {
|
|||||||
pub struct IdInfo {
|
pub struct IdInfo {
|
||||||
/// What was this Id generated from?
|
/// What was this Id generated from?
|
||||||
pub source: IdSource,
|
pub source: IdSource,
|
||||||
|
|
||||||
/// If the Id was crated via [`Id::with`], what was the parent Id?
|
/// If the Id was crated via [`Id::with`], what was the parent Id?
|
||||||
pub parent: Option<Id>,
|
pub parent: Option<Id>,
|
||||||
}
|
}
|
||||||
@@ -273,11 +182,11 @@ mod id_source {
|
|||||||
impl Display for IdSource {
|
impl Display for IdSource {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
IdSource::Id(id) => {
|
Self::Id(id) => {
|
||||||
write!(f, "{}", id.short_debug_format())
|
write!(f, "{}", id.short_debug_format())
|
||||||
}
|
}
|
||||||
IdSource::Other(other) => {
|
Self::Other(other) => {
|
||||||
write!(f, "{}", other)
|
write!(f, "{other}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,7 +228,7 @@ mod id_source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_u64(&mut self, i: u64) {
|
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);
|
self.val = Some(i);
|
||||||
} else {
|
} else {
|
||||||
self.not_id = true;
|
self.not_id = true;
|
||||||
@@ -345,10 +254,10 @@ mod id_source {
|
|||||||
if ID_MAP.read().contains_key(&maybe_source_id) {
|
if ID_MAP.read().contains_key(&maybe_source_id) {
|
||||||
IdSource::Id(maybe_source_id)
|
IdSource::Id(maybe_source_id)
|
||||||
} else {
|
} else {
|
||||||
IdSource::Other(format!("{:?}", t))
|
IdSource::Other(format!("{t:?}"))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
IdSource::Other(format!("{:?}", t))
|
IdSource::Other(format!("{t:?}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,14 +275,48 @@ mod id_source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Id {
|
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<IdInfo> {
|
pub fn info(&self) -> Option<IdInfo> {
|
||||||
ID_MAP.read().get(self).cloned()
|
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]
|
#[test]
|
||||||
fn test_fake_hasher() {
|
fn test_fake_hasher() {
|
||||||
use std::hash::Hash;
|
use std::hash::Hash as _;
|
||||||
let mut hasher = ExtractIdHasher::default();
|
let mut hasher = ExtractIdHasher::default();
|
||||||
|
|
||||||
let id = Id::new("test");
|
let id = Id::new("test");
|
||||||
@@ -391,15 +334,15 @@ impl std::fmt::Debug for Id {
|
|||||||
if let Some(info) = self.info() {
|
if let Some(info) = self.info() {
|
||||||
match info.source {
|
match info.source {
|
||||||
id_source::IdSource::Id(source_id) => {
|
id_source::IdSource::Id(source_id) => {
|
||||||
write!(f, "({:?})", source_id)?;
|
write!(f, "({source_id:?})")?;
|
||||||
}
|
}
|
||||||
id_source::IdSource::Other(label) => {
|
id_source::IdSource::Other(label) => {
|
||||||
write!(f, " ({})", label)?;
|
write!(f, " ({label})")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(parent) = info.parent {
|
if let Some(parent) = info.parent {
|
||||||
// Let's hope there are no cycles!
|
// Let's hope there are no cycles!
|
||||||
write!(f, " <- {:?}", parent)?;
|
write!(f, " <- {parent:?}")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ use emath::GuiRounding as _;
|
|||||||
use epaint::mutex::RwLock;
|
use epaint::mutex::RwLock;
|
||||||
|
|
||||||
use crate::containers::menu;
|
use crate::containers::menu;
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
use crate::Stroke;
|
|
||||||
use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *};
|
use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *};
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -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).");
|
(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:");
|
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");
|
||||||
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.vertical_centered(|ui| {
|
||||||
ui.add(crate::egui_github_link_file!());
|
ui.add(crate::egui_github_link_file!());
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:82878e4150e38fdc4b2e78203c8c661c2d9e716ab32595c298392faf6ba96105
|
oid sha256:75ace10713ebc89c4e70f5c75532b2fc0b05140c72111ae361febba12cb4ef9d
|
||||||
size 113803
|
size 139886
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#![expect(rustdoc::missing_crate_level_docs)] // it's an example
|
#![expect(rustdoc::missing_crate_level_docs)] // it's an example
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use eframe::egui::{Id, Ui};
|
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||||
@@ -29,18 +28,6 @@ fn main() -> eframe::Result {
|
|||||||
age += 1;
|
age += 1;
|
||||||
}
|
}
|
||||||
ui.label(format!("Hello '{name}', age {age}"));
|
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);
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user