egui/
id.rs

1// TODO(emilk): have separate types `PositionId` and `UniqueId`. ?
2
3use std::num::NonZeroU64;
4
5/// egui tracks widgets frame-to-frame using [`Id`]s.
6///
7/// For instance, if you start dragging a slider one frame, egui stores
8/// the sliders [`Id`] as the current active id so that next frame when
9/// you move the mouse the same slider changes, even if the mouse has
10/// moved outside the slider.
11///
12/// For some widgets [`Id`]s are also used to persist some state about the
13/// widgets, such as Window position or whether not a collapsing header region is open.
14///
15/// This implies that the [`Id`]s must be unique.
16///
17/// For simple things like sliders and buttons that don't have any memory and
18/// doesn't move we can use the location of the widget as a source of identity.
19/// For instance, a slider only needs a unique and persistent ID while you are
20/// dragging the slider. As long as it is still while moving, that is fine.
21///
22/// For things that need to persist state even after moving (windows, collapsing headers)
23/// the location of the widgets is obviously not good enough. For instance,
24/// a collapsing region needs to remember whether or not it is open even
25/// if the layout next frame is different and the collapsing is not lower down
26/// on the screen.
27///
28/// Then there are widgets that need no identifiers at all, like labels,
29/// because they have no state nor are interacted with.
30///
31/// This is niche-optimized to that `Option<Id>` is the same size as `Id`.
32#[derive(Clone, Copy, Hash, Eq, PartialEq)]
33#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
34pub struct Id(NonZeroU64);
35
36impl Id {
37    /// A special [`Id`], in particular as a key to [`crate::Memory::data`]
38    /// for when there is no particular widget to attach the data.
39    ///
40    /// The null [`Id`] is still a valid id to use in all circumstances,
41    /// though obviously it will lead to a lot of collisions if you do use it!
42    pub const NULL: Self = Self(NonZeroU64::MAX);
43
44    #[inline]
45    const fn from_hash(hash: u64) -> Self {
46        if let Some(nonzero) = NonZeroU64::new(hash) {
47            Self(nonzero)
48        } else {
49            Self(NonZeroU64::MIN) // The hash was exactly zero (very bad luck)
50        }
51    }
52
53    /// Generate a new [`Id`] by hashing some source (e.g. a string or integer).
54    pub fn new(source: impl std::hash::Hash) -> Self {
55        Self::from_hash(ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source))
56    }
57
58    /// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument.
59    pub fn with(self, child: impl std::hash::Hash) -> Self {
60        use std::hash::{BuildHasher, Hasher};
61        let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
62        hasher.write_u64(self.0.get());
63        child.hash(&mut hasher);
64        Self::from_hash(hasher.finish())
65    }
66
67    /// Short and readable summary
68    pub fn short_debug_format(&self) -> String {
69        format!("{:04X}", self.value() as u16)
70    }
71
72    /// The inner value of the [`Id`].
73    ///
74    /// This is a high-entropy hash, or [`Self::NULL`].
75    #[inline(always)]
76    pub fn value(&self) -> u64 {
77        self.0.get()
78    }
79
80    #[cfg(feature = "accesskit")]
81    pub(crate) fn accesskit_id(&self) -> accesskit::NodeId {
82        self.value().into()
83    }
84}
85
86impl std::fmt::Debug for Id {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        write!(f, "{:04X}", self.value() as u16)
89    }
90}
91
92/// Convenience
93impl From<&'static str> for Id {
94    #[inline]
95    fn from(string: &'static str) -> Self {
96        Self::new(string)
97    }
98}
99
100impl From<String> for Id {
101    #[inline]
102    fn from(string: String) -> Self {
103        Self::new(string)
104    }
105}
106
107#[test]
108fn id_size() {
109    assert_eq!(std::mem::size_of::<Id>(), 8);
110    assert_eq!(std::mem::size_of::<Option<Id>>(), 8);
111}
112
113// ----------------------------------------------------------------------------
114
115// Idea taken from the `nohash_hasher` crate.
116#[derive(Default)]
117pub struct IdHasher(u64);
118
119impl std::hash::Hasher for IdHasher {
120    fn write(&mut self, _: &[u8]) {
121        unreachable!("Invalid use of IdHasher");
122    }
123
124    fn write_u8(&mut self, _n: u8) {
125        unreachable!("Invalid use of IdHasher");
126    }
127
128    fn write_u16(&mut self, _n: u16) {
129        unreachable!("Invalid use of IdHasher");
130    }
131
132    fn write_u32(&mut self, _n: u32) {
133        unreachable!("Invalid use of IdHasher");
134    }
135
136    #[inline(always)]
137    fn write_u64(&mut self, n: u64) {
138        self.0 = n;
139    }
140
141    fn write_usize(&mut self, _n: usize) {
142        unreachable!("Invalid use of IdHasher");
143    }
144
145    fn write_i8(&mut self, _n: i8) {
146        unreachable!("Invalid use of IdHasher");
147    }
148
149    fn write_i16(&mut self, _n: i16) {
150        unreachable!("Invalid use of IdHasher");
151    }
152
153    fn write_i32(&mut self, _n: i32) {
154        unreachable!("Invalid use of IdHasher");
155    }
156
157    fn write_i64(&mut self, _n: i64) {
158        unreachable!("Invalid use of IdHasher");
159    }
160
161    fn write_isize(&mut self, _n: isize) {
162        unreachable!("Invalid use of IdHasher");
163    }
164
165    #[inline(always)]
166    fn finish(&self) -> u64 {
167        self.0
168    }
169}
170
171#[derive(Copy, Clone, Debug, Default)]
172#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
173pub struct BuildIdHasher {}
174
175impl std::hash::BuildHasher for BuildIdHasher {
176    type Hasher = IdHasher;
177
178    #[inline(always)]
179    fn build_hasher(&self) -> IdHasher {
180        IdHasher::default()
181    }
182}
183
184/// `IdSet` is a `HashSet<Id>` optimized by knowing that [`Id`] has good entropy, and doesn't need more hashing.
185pub type IdSet = std::collections::HashSet<Id, BuildIdHasher>;
186
187/// `IdMap<V>` is a `HashMap<Id, V>` optimized by knowing that [`Id`] has good entropy, and doesn't need more hashing.
188pub type IdMap<V> = std::collections::HashMap<Id, V, BuildIdHasher>;