1use ahash::HashMap;
2
3use crate::{Id, IdMap, LayerId, Rect, Sense, WidgetInfo};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct WidgetRect {
10 pub id: Id,
19
20 pub parent_id: Id,
25
26 pub layer_id: LayerId,
28
29 pub rect: Rect,
31
32 pub interact_rect: Rect,
36
37 pub sense: Sense,
46
47 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct InteractOptions {
77 pub move_to_top: bool,
80}
81
82#[expect(clippy::derivable_impls)] impl Default for InteractOptions {
84 fn default() -> Self {
85 Self { move_to_top: false }
86 }
87}
88
89#[derive(Default, Clone)]
94pub struct WidgetRects {
95 by_layer: HashMap<LayerId, Vec<WidgetRect>>,
97
98 by_id: IdMap<(usize, WidgetRect)>,
100
101 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 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 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 #[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 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 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 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 let (idx_in_layer, existing) = entry.get_mut();
190
191 existing.rect = widget_rect.rect; existing.interact_rect = widget_rect.interact_rect; 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}