1use std::rc::Rc;
8
9use dioxus::html::MountedData;
10use dioxus::prelude::*;
11
12use super::types::{DropOutcome, Point, Rect, ZoneId};
13
14pub struct ZoneRecord<T: Clone + 'static> {
16 pub id: ZoneId,
17 pub parent: Option<ZoneId>,
20 pub label: Option<String>,
22 pub on_drop: Callback<DropOutcome<T>>,
24 pub accepts: Option<Callback<T, bool>>,
26 pub mounted: Signal<Option<Rc<MountedData>>>,
28 pub rect: Signal<Option<Rect>>,
30}
31
32impl<T: Clone + 'static> Clone for ZoneRecord<T> {
33 fn clone(&self) -> Self {
34 Self {
35 id: self.id,
36 parent: self.parent,
37 label: self.label.clone(),
38 on_drop: self.on_drop,
39 accepts: self.accepts,
40 mounted: self.mounted,
41 rect: self.rect,
42 }
43 }
44}
45
46impl<T: Clone + 'static> ZoneRecord<T> {
47 pub fn accepts_payload(&self, payload: &T) -> bool {
49 match self.accepts {
50 Some(cb) => cb.call(payload.clone()),
51 None => true,
52 }
53 }
54}
55
56pub struct ZoneRegistry<T: Clone + 'static> {
58 zones: Signal<Vec<ZoneRecord<T>>>,
59}
60
61impl<T: Clone + 'static> Copy for ZoneRegistry<T> {}
62impl<T: Clone + 'static> Clone for ZoneRegistry<T> {
63 fn clone(&self) -> Self {
64 *self
65 }
66}
67impl<T: Clone + 'static> PartialEq for ZoneRegistry<T> {
68 fn eq(&self, other: &Self) -> bool {
69 self.zones == other.zones
70 }
71}
72
73impl<T: Clone + 'static> ZoneRegistry<T> {
74 pub fn from_signal(zones: Signal<Vec<ZoneRecord<T>>>) -> Self {
76 Self { zones }
77 }
78
79 pub fn register(&mut self, record: ZoneRecord<T>) {
81 let mut zones = self.zones.write();
82 if let Some(existing) = zones.iter_mut().find(|z| z.id == record.id) {
83 *existing = record;
84 } else {
85 zones.push(record);
86 }
87 }
88
89 pub fn sync_label(&mut self, id: ZoneId, label: Option<String>) {
91 let needs = self
92 .zones
93 .peek()
94 .iter()
95 .any(|z| z.id == id && z.label != label);
96 if needs {
97 if let Some(z) = self.zones.write().iter_mut().find(|z| z.id == id) {
98 z.label = label;
99 }
100 }
101 }
102
103 pub fn unregister(&mut self, id: ZoneId) {
105 self.zones.write().retain(|z| z.id != id);
106 }
107
108 pub fn get(&self, id: ZoneId) -> Option<ZoneRecord<T>> {
110 self.zones.peek().iter().find(|z| z.id == id).cloned()
111 }
112
113 pub fn contains(&self, id: ZoneId) -> bool {
117 self.zones.peek().iter().any(|z| z.id == id)
118 }
119
120 pub fn ascend(&self, current: ZoneId) -> Option<ZoneId> {
126 self.parent_of(current).filter(|pid| self.contains(*pid))
127 }
128
129 pub fn acceptable(&self, payload: &T) -> Vec<ZoneRecord<T>> {
131 self.zones
132 .peek()
133 .iter()
134 .filter(|z| z.accepts_payload(payload))
135 .cloned()
136 .collect()
137 }
138
139 pub fn step_zone(&self, current: Option<ZoneId>, payload: &T, step: isize) -> Option<ZoneId> {
147 let mut zones = self.acceptable(payload);
148 spatial_sort(&mut zones);
149 let current_ix = current.and_then(|c| zones.iter().position(|z| z.id == c));
150 cycle(zones.len(), current_ix, step).map(|ix| zones[ix].id)
151 }
152
153 pub fn parent_of(&self, id: ZoneId) -> Option<ZoneId> {
155 self.zones.peek().iter().find(|z| z.id == id)?.parent
156 }
157
158 pub fn children_of(&self, parent: Option<ZoneId>, payload: &T) -> Vec<ZoneRecord<T>> {
162 let mut zones: Vec<_> = self
163 .zones
164 .peek()
165 .iter()
166 .filter(|z| z.parent == parent && z.accepts_payload(payload))
167 .cloned()
168 .collect();
169 spatial_sort(&mut zones);
170 zones
171 }
172
173 pub fn step_sibling(
176 &self,
177 current: Option<ZoneId>,
178 payload: &T,
179 step: isize,
180 ) -> Option<ZoneId> {
181 let parent = current.and_then(|c| self.parent_of(c));
182 let siblings = self.children_of(parent, payload);
183 let current_ix = current.and_then(|c| siblings.iter().position(|z| z.id == c));
184 cycle(siblings.len(), current_ix, step).map(|ix| siblings[ix].id)
185 }
186
187 pub fn first_child(&self, id: ZoneId, payload: &T) -> Option<ZoneId> {
189 self.children_of(Some(id), payload).first().map(|z| z.id)
190 }
191
192 pub fn hit_test(&self, point: Point) -> Option<ZoneId> {
196 self.zones
197 .peek()
198 .iter()
199 .rev()
200 .find(|z| (*z.rect.peek()).map(|r| r.contains(point)).unwrap_or(false))
201 .map(|z| z.id)
202 }
203
204 pub fn hit_test_closest(&self, point: Point, payload: &T, max_distance: f64) -> Option<ZoneId> {
212 if let Some(hit) = self
213 .zones
214 .peek()
215 .iter()
216 .rev()
217 .find(|z| {
218 z.accepts_payload(payload)
219 && (*z.rect.peek()).map(|r| r.contains(point)).unwrap_or(false)
220 })
221 .map(|z| z.id)
222 {
223 return Some(hit);
224 }
225 let mut best: Option<(ZoneId, f64)> = None;
226 for z in self.acceptable(payload) {
227 let Some(r) = *z.rect.peek() else { continue };
228 let c = r.center();
229 let (dx, dy) = (c.x - point.x, c.y - point.y);
230 let d = (dx * dx + dy * dy).sqrt();
231 if d <= max_distance && best.map(|(_, bd)| d < bd).unwrap_or(true) {
232 best = Some((z.id, d));
233 }
234 }
235 best.map(|(id, _)| id)
236 }
237
238 pub async fn measure_all(&self) {
243 let zones: Vec<_> = self
244 .zones
245 .peek()
246 .iter()
247 .map(|z| (z.mounted.peek().clone(), z.rect))
248 .collect();
249 for (mounted, mut rect) in zones {
250 if let Some(m) = mounted {
251 if let Ok(r) = m.get_client_rect().await {
252 rect.set(Some(Rect::new(
253 r.origin.x,
254 r.origin.y,
255 r.size.width,
256 r.size.height,
257 )));
258 }
259 }
260 }
261 }
262
263 pub fn refresh_rects(&self) {
265 for zone in self.zones.peek().iter() {
266 let mounted = zone.mounted.peek().clone();
267 let mut rect = zone.rect;
268 if let Some(m) = mounted {
269 spawn(async move {
270 if let Ok(r) = m.get_client_rect().await {
271 rect.set(Some(Rect::new(
272 r.origin.x,
273 r.origin.y,
274 r.size.width,
275 r.size.height,
276 )));
277 }
278 });
279 }
280 }
281 }
282}
283
284fn spatial_sort<T: Clone + 'static>(zones: &mut [ZoneRecord<T>]) {
287 zones.sort_by(|a, b| match (*a.rect.peek(), *b.rect.peek()) {
288 (Some(ra), Some(rb)) => (ra.y, ra.x)
289 .partial_cmp(&(rb.y, rb.x))
290 .unwrap_or(std::cmp::Ordering::Equal),
291 (Some(_), None) => std::cmp::Ordering::Less,
292 (None, Some(_)) => std::cmp::Ordering::Greater,
293 (None, None) => std::cmp::Ordering::Equal,
294 });
295}
296
297pub(crate) fn cycle(len: usize, current: Option<usize>, step: isize) -> Option<usize> {
300 if len == 0 {
301 return None;
302 }
303 Some(match current {
304 None => {
305 if step >= 0 {
306 0
307 } else {
308 len - 1
309 }
310 }
311 Some(ix) => (ix as isize + step).rem_euclid(len as isize) as usize,
312 })
313}
314
315#[cfg(test)]
316mod tests {
317 use super::cycle;
318
319 #[test]
320 fn cycle_steps_and_wraps() {
321 assert_eq!(cycle(0, None, 1), None);
322 assert_eq!(cycle(3, None, 1), Some(0));
323 assert_eq!(cycle(3, None, -1), Some(2));
324 assert_eq!(cycle(3, Some(2), 1), Some(0));
325 assert_eq!(cycle(3, Some(0), -1), Some(2));
326 assert_eq!(cycle(3, Some(1), 1), Some(2));
327 }
328}