1use std::rc::Rc;
8
9use dioxus::html::MountedData;
10use dioxus::prelude::*;
11
12use super::types::{Direction, 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 dir: Signal<Direction>,
61}
62
63impl<T: Clone + 'static> Copy for ZoneRegistry<T> {}
64impl<T: Clone + 'static> Clone for ZoneRegistry<T> {
65 fn clone(&self) -> Self {
66 *self
67 }
68}
69impl<T: Clone + 'static> PartialEq for ZoneRegistry<T> {
70 fn eq(&self, other: &Self) -> bool {
71 self.zones == other.zones && self.dir == other.dir
72 }
73}
74
75impl<T: Clone + 'static> ZoneRegistry<T> {
76 pub fn from_signal(zones: Signal<Vec<ZoneRecord<T>>>) -> Self {
78 Self {
79 zones,
80 dir: Signal::new(Direction::default()),
81 }
82 }
83
84 pub fn direction(&self) -> Direction {
86 *self.dir.peek()
87 }
88
89 pub fn set_direction(&mut self, dir: Direction) {
92 if *self.dir.peek() != dir {
93 self.dir.set(dir);
94 }
95 }
96
97 pub fn register(&mut self, record: ZoneRecord<T>) {
99 let mut zones = self.zones.write();
100 if let Some(existing) = zones.iter_mut().find(|z| z.id == record.id) {
101 *existing = record;
102 } else {
103 zones.push(record);
104 }
105 }
106
107 pub fn sync_label(&mut self, id: ZoneId, label: Option<String>) {
109 let needs = self
110 .zones
111 .peek()
112 .iter()
113 .any(|z| z.id == id && z.label != label);
114 if needs {
115 if let Some(z) = self.zones.write().iter_mut().find(|z| z.id == id) {
116 z.label = label;
117 }
118 }
119 }
120
121 pub fn unregister(&mut self, id: ZoneId) {
123 self.zones.write().retain(|z| z.id != id);
124 }
125
126 pub fn get(&self, id: ZoneId) -> Option<ZoneRecord<T>> {
128 self.zones.peek().iter().find(|z| z.id == id).cloned()
129 }
130
131 pub fn contains(&self, id: ZoneId) -> bool {
135 self.zones.peek().iter().any(|z| z.id == id)
136 }
137
138 pub fn ascend(&self, current: ZoneId) -> Option<ZoneId> {
144 self.parent_of(current).filter(|pid| self.contains(*pid))
145 }
146
147 pub fn acceptable(&self, payload: &T) -> Vec<ZoneRecord<T>> {
149 self.zones
150 .peek()
151 .iter()
152 .filter(|z| z.accepts_payload(payload))
153 .cloned()
154 .collect()
155 }
156
157 pub fn step_zone(&self, current: Option<ZoneId>, payload: &T, step: isize) -> Option<ZoneId> {
165 let mut zones = self.acceptable(payload);
166 spatial_sort(&mut zones, self.direction());
167 let current_ix = current.and_then(|c| zones.iter().position(|z| z.id == c));
168 cycle(zones.len(), current_ix, step).map(|ix| zones[ix].id)
169 }
170
171 pub fn parent_of(&self, id: ZoneId) -> Option<ZoneId> {
173 self.zones.peek().iter().find(|z| z.id == id)?.parent
174 }
175
176 pub fn children_of(&self, parent: Option<ZoneId>, payload: &T) -> Vec<ZoneRecord<T>> {
180 let mut zones: Vec<_> = self
181 .zones
182 .peek()
183 .iter()
184 .filter(|z| z.parent == parent && z.accepts_payload(payload))
185 .cloned()
186 .collect();
187 spatial_sort(&mut zones, self.direction());
188 zones
189 }
190
191 pub fn step_sibling(
194 &self,
195 current: Option<ZoneId>,
196 payload: &T,
197 step: isize,
198 ) -> Option<ZoneId> {
199 let parent = current.and_then(|c| self.parent_of(c));
200 let siblings = self.children_of(parent, payload);
201 let current_ix = current.and_then(|c| siblings.iter().position(|z| z.id == c));
202 cycle(siblings.len(), current_ix, step).map(|ix| siblings[ix].id)
203 }
204
205 pub fn first_child(&self, id: ZoneId, payload: &T) -> Option<ZoneId> {
207 self.children_of(Some(id), payload).first().map(|z| z.id)
208 }
209
210 pub fn hit_test(&self, point: Point) -> Option<ZoneId> {
214 self.zones
215 .peek()
216 .iter()
217 .rev()
218 .find(|z| (*z.rect.peek()).map(|r| r.contains(point)).unwrap_or(false))
219 .map(|z| z.id)
220 }
221
222 pub fn hit_test_closest(&self, point: Point, payload: &T, max_distance: f64) -> Option<ZoneId> {
230 if let Some(hit) = self
231 .zones
232 .peek()
233 .iter()
234 .rev()
235 .find(|z| {
236 z.accepts_payload(payload)
237 && (*z.rect.peek()).map(|r| r.contains(point)).unwrap_or(false)
238 })
239 .map(|z| z.id)
240 {
241 return Some(hit);
242 }
243 let mut best: Option<(ZoneId, f64)> = None;
244 for z in self.acceptable(payload) {
245 let Some(r) = *z.rect.peek() else { continue };
246 let c = r.center();
247 let (dx, dy) = (c.x - point.x, c.y - point.y);
248 let d = (dx * dx + dy * dy).sqrt();
249 if d <= max_distance && best.map(|(_, bd)| d < bd).unwrap_or(true) {
250 best = Some((z.id, d));
251 }
252 }
253 best.map(|(id, _)| id)
254 }
255
256 pub async fn measure_all(&self) {
261 let zones: Vec<_> = self
262 .zones
263 .peek()
264 .iter()
265 .map(|z| (z.mounted.peek().clone(), z.rect))
266 .collect();
267 for (mounted, mut rect) in zones {
268 if let Some(m) = mounted {
269 if let Ok(r) = m.get_client_rect().await {
270 rect.set(Some(Rect::new(
271 r.origin.x,
272 r.origin.y,
273 r.size.width,
274 r.size.height,
275 )));
276 }
277 }
278 }
279 }
280
281 pub fn refresh_rects(&self) {
283 for zone in self.zones.peek().iter() {
284 let mounted = zone.mounted.peek().clone();
285 let mut rect = zone.rect;
286 if let Some(m) = mounted {
287 spawn(async move {
288 if let Ok(r) = m.get_client_rect().await {
289 rect.set(Some(Rect::new(
290 r.origin.x,
291 r.origin.y,
292 r.size.width,
293 r.size.height,
294 )));
295 }
296 });
297 }
298 }
299 }
300}
301
302pub struct RectRefresh {
319 thunks: Signal<Vec<(u64, Callback<()>)>>,
320}
321
322impl Copy for RectRefresh {}
323impl Clone for RectRefresh {
324 fn clone(&self) -> Self {
325 *self
326 }
327}
328impl PartialEq for RectRefresh {
329 fn eq(&self, other: &Self) -> bool {
330 self.thunks == other.thunks
331 }
332}
333
334impl RectRefresh {
335 pub fn from_signal(thunks: Signal<Vec<(u64, Callback<()>)>>) -> Self {
339 Self { thunks }
340 }
341
342 pub fn refresh_all(&self) {
346 for (_, thunk) in self.thunks.peek().iter() {
347 thunk.call(());
348 }
349 }
350
351 pub fn len(&self) -> usize {
353 self.thunks.peek().len()
354 }
355
356 pub fn is_empty(&self) -> bool {
358 self.len() == 0
359 }
360
361 pub(crate) fn register(&mut self, key: u64, thunk: Callback<()>) {
363 let mut thunks = self.thunks.write();
364 if let Some(existing) = thunks.iter_mut().find(|(k, _)| *k == key) {
365 existing.1 = thunk;
366 } else {
367 thunks.push((key, thunk));
368 }
369 }
370
371 pub(crate) fn unregister(&mut self, key: u64) {
373 self.thunks.write().retain(|(k, _)| *k != key);
374 }
375}
376
377fn spatial_sort<T: Clone + 'static>(zones: &mut [ZoneRecord<T>], dir: Direction) {
382 let reading_x = move |x: f64| match dir {
383 Direction::Ltr => x,
384 Direction::Rtl => -x,
385 };
386 zones.sort_by(|a, b| match (*a.rect.peek(), *b.rect.peek()) {
387 (Some(ra), Some(rb)) => (ra.y, reading_x(ra.x))
388 .partial_cmp(&(rb.y, reading_x(rb.x)))
389 .unwrap_or(std::cmp::Ordering::Equal),
390 (Some(_), None) => std::cmp::Ordering::Less,
391 (None, Some(_)) => std::cmp::Ordering::Greater,
392 (None, None) => std::cmp::Ordering::Equal,
393 });
394}
395
396pub(crate) fn cycle(len: usize, current: Option<usize>, step: isize) -> Option<usize> {
399 if len == 0 {
400 return None;
401 }
402 Some(match current {
403 None => {
404 if step >= 0 {
405 0
406 } else {
407 len - 1
408 }
409 }
410 Some(ix) => (ix as isize + step).rem_euclid(len as isize) as usize,
411 })
412}
413
414#[cfg(test)]
415mod tests {
416 use super::cycle;
417
418 #[test]
419 fn cycle_steps_and_wraps() {
420 assert_eq!(cycle(0, None, 1), None);
421 assert_eq!(cycle(3, None, 1), Some(0));
422 assert_eq!(cycle(3, None, -1), Some(2));
423 assert_eq!(cycle(3, Some(2), 1), Some(0));
424 assert_eq!(cycle(3, Some(0), -1), Some(2));
425 assert_eq!(cycle(3, Some(1), 1), Some(2));
426 }
427}