egui/
widget_rect.rs

1use ahash::HashMap;
2
3use crate::{Id, IdMap, LayerId, Rect, Sense, WidgetInfo};
4
5/// Used to store each widget's [Id], [Rect] and [Sense] each frame.
6///
7/// Used to check which widget gets input when a user clicks somewhere.
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct WidgetRect {
10    /// The globally unique widget id.
11    ///
12    /// For interactive widgets, this better be globally unique.
13    /// If not there will be weird bugs,
14    /// and also big red warning test on the screen in debug builds
15    /// (see [`crate::Options::warn_on_id_clash`]).
16    ///
17    /// You can ensure globally unique ids using [`crate::Ui::push_id`].
18    pub id: Id,
19
20    /// The [`Id`] of the parent [`crate::Ui`] that hosts this widget.
21    ///
22    /// Used by debug checks to distinguish true id-instability from
23    /// cascading id shifts caused by a parent Ui's auto-id changing.
24    pub parent_id: Id,
25
26    /// What layer the widget is on.
27    pub layer_id: LayerId,
28
29    /// The full widget rectangle, in local layer coordinates.
30    pub rect: Rect,
31
32    /// Where the widget is, in local layer coordinates.
33    ///
34    /// This is after clipping with the parent ui clip rect.
35    pub interact_rect: Rect,
36
37    /// How the widget responds to interaction.
38    ///
39    /// Note: if [`Self::enabled`] is `false`, then
40    /// the widget _effectively_ doesn't sense anything,
41    /// but can still have the same `Sense`.
42    /// This is because the sense informs the styling of the widget,
43    /// but we don't want to change the style when a widget is disabled
44    /// (that is handled by the `Painter` directly).
45    pub sense: Sense,
46
47    /// Is the widget enabled?
48    pub enabled: bool,
49}
50
51impl WidgetRect {
52    pub fn transform(self, transform: emath::TSTransform) -> Self {
53        let Self {
54            id,
55            parent_id,
56            layer_id,
57            rect,
58            interact_rect,
59            sense,
60            enabled,
61        } = self;
62        Self {
63            id,
64            parent_id,
65            layer_id,
66            rect: transform * rect,
67            interact_rect: transform * interact_rect,
68            sense,
69            enabled,
70        }
71    }
72}
73
74/// How to handle multiple calls to [`crate::Response::interact`] and [`crate::Ui::interact_opt`].
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct InteractOptions {
77    /// If we call interact on the same widget multiple times,
78    /// should we move it to the top on subsequent calls?
79    pub move_to_top: bool,
80}
81
82#[expect(clippy::derivable_impls)] // Nice to be explicit
83impl Default for InteractOptions {
84    fn default() -> Self {
85        Self { move_to_top: false }
86    }
87}
88
89/// Stores the [`WidgetRect`]s of all widgets generated during a single egui update/frame.
90///
91/// All [`crate::Ui`]s have a [`WidgetRect`]. It is created in [`crate::Ui::new`] with [`Rect::NOTHING`]
92/// and updated with the correct [`Rect`] when the [`crate::Ui`] is dropped.
93#[derive(Default, Clone)]
94pub struct WidgetRects {
95    /// All widgets, in painting order.
96    by_layer: HashMap<LayerId, Vec<WidgetRect>>,
97
98    /// All widgets, by id, and their order in their respective layer
99    by_id: IdMap<(usize, WidgetRect)>,
100
101    /// Info about some widgets.
102    ///
103    /// Only filled in if the widget is interacted with,
104    /// or if this is a debug build.
105    infos: IdMap<WidgetInfo>,
106}
107
108impl PartialEq for WidgetRects {
109    fn eq(&self, other: &Self) -> bool {
110        self.by_layer == other.by_layer
111    }
112}
113
114impl WidgetRects {
115    /// All known layers with widgets.
116    pub fn layer_ids(&self) -> impl ExactSizeIterator<Item = LayerId> + '_ {
117        self.by_layer.keys().copied()
118    }
119
120    pub fn layers(&self) -> impl Iterator<Item = (&LayerId, &[WidgetRect])> + '_ {
121        self.by_layer
122            .iter()
123            .map(|(layer_id, rects)| (layer_id, &rects[..]))
124    }
125
126    #[inline]
127    pub fn get(&self, id: Id) -> Option<&WidgetRect> {
128        self.by_id.get(&id).map(|(_, w)| w)
129    }
130
131    /// In which layer, and in which order in that layer?
132    pub fn order(&self, id: Id) -> Option<(LayerId, usize)> {
133        self.by_id.get(&id).map(|(idx, w)| (w.layer_id, *idx))
134    }
135
136    #[inline]
137    pub fn contains(&self, id: Id) -> bool {
138        self.by_id.contains_key(&id)
139    }
140
141    /// All widgets in this layer, sorted back-to-front.
142    #[inline]
143    pub fn get_layer(&self, layer_id: LayerId) -> impl Iterator<Item = &WidgetRect> + '_ {
144        self.by_layer.get(&layer_id).into_iter().flatten()
145    }
146
147    /// Clear the contents while retaining allocated memory.
148    pub fn clear(&mut self) {
149        let Self {
150            by_layer,
151            by_id,
152            infos,
153        } = self;
154
155        #[expect(clippy::iter_over_hash_type)]
156        for rects in by_layer.values_mut() {
157            rects.clear();
158        }
159
160        by_id.clear();
161
162        infos.clear();
163    }
164
165    /// Insert the given widget rect in the given layer.
166    pub fn insert(&mut self, layer_id: LayerId, widget_rect: WidgetRect, options: InteractOptions) {
167        let Self {
168            by_layer,
169            by_id,
170            infos: _,
171        } = self;
172
173        let InteractOptions { move_to_top } = options;
174
175        let mut shift_layer_index_after = None;
176
177        let layer_widgets = by_layer.entry(layer_id).or_default();
178
179        match by_id.entry(widget_rect.id) {
180            std::collections::hash_map::Entry::Vacant(entry) => {
181                // A new widget
182                let idx_in_layer = layer_widgets.len();
183                entry.insert((idx_in_layer, widget_rect));
184                layer_widgets.push(widget_rect);
185            }
186            std::collections::hash_map::Entry::Occupied(mut entry) => {
187                // This is a known widget, but we might need to update it!
188                // e.g. calling `response.interact(…)` to add more interaction.
189                let (idx_in_layer, existing) = entry.get_mut();
190
191                // Update it:
192                existing.rect = widget_rect.rect; // last wins
193                existing.interact_rect = widget_rect.interact_rect; // last wins
194                existing.sense |= widget_rect.sense;
195                existing.enabled |= widget_rect.enabled;
196
197                if existing.layer_id == widget_rect.layer_id {
198                    if move_to_top {
199                        layer_widgets.remove(*idx_in_layer);
200                        shift_layer_index_after = Some(*idx_in_layer);
201                        *idx_in_layer = layer_widgets.len();
202                        layer_widgets.push(*existing);
203                    } else {
204                        layer_widgets[*idx_in_layer] = *existing;
205                    }
206                } else if cfg!(debug_assertions) {
207                    panic!(
208                        "DEBUG ASSERT: Widget {:?} changed layer_id during the frame from {:?} to {:?}",
209                        widget_rect.id, existing.layer_id, widget_rect.layer_id
210                    );
211                }
212            }
213        }
214
215        if let Some(shift_start) = shift_layer_index_after {
216            #[expect(clippy::needless_range_loop)]
217            for i in shift_start..layer_widgets.len() {
218                let w = &layer_widgets[i];
219                if let Some((idx_in_by_id, _)) = by_id.get_mut(&w.id) {
220                    *idx_in_by_id = i;
221                }
222            }
223        }
224    }
225
226    pub fn set_info(&mut self, id: Id, info: WidgetInfo) {
227        self.infos.insert(id, info);
228    }
229
230    pub fn info(&self, id: Id) -> Option<&WidgetInfo> {
231        self.infos.get(&id)
232    }
233}