1use std::rc::Rc;
8use std::sync::atomic::{AtomicU64, Ordering};
9
10use dioxus::html::MountedData;
11use dioxus::prelude::*;
12
13use super::types::{Direction, DropOutcome, Point, Rect, ZoneId};
14
15static NEXT_ZONE_REGISTRATION: AtomicU64 = AtomicU64::new(1);
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub struct ZoneRegistration {
24 id: ZoneId,
25 generation: u64,
26}
27
28pub struct ZoneRecord<T: Clone + 'static> {
30 pub id: ZoneId,
31 pub parent: Option<ZoneId>,
34 pub label: Option<String>,
36 pub on_drop: Callback<DropOutcome<T>>,
38 pub accepts: Option<Callback<T, bool>>,
40 pub mounted: Option<Rc<MountedData>>,
44 pub rect: Option<Rect>,
48}
49
50impl<T: Clone + 'static> Clone for ZoneRecord<T> {
51 fn clone(&self) -> Self {
52 Self {
53 id: self.id,
54 parent: self.parent,
55 label: self.label.clone(),
56 on_drop: self.on_drop,
57 accepts: self.accepts,
58 mounted: self.mounted.clone(),
59 rect: self.rect,
60 }
61 }
62}
63
64impl<T: Clone + 'static> ZoneRecord<T> {
65 pub fn accepts_payload(&self, payload: &T) -> bool {
67 match self.accepts {
68 Some(cb) => cb.call(payload.clone()),
69 None => true,
70 }
71 }
72
73 pub fn cached_rect(&self) -> Option<Rect> {
75 self.rect
76 }
77
78 pub fn mounted_handle(&self) -> Option<Rc<MountedData>> {
80 self.mounted.clone()
81 }
82}
83
84pub struct ZoneRegistry<T: Clone + 'static> {
86 zones: Signal<Vec<ZoneRecord<T>>>,
87 registrations: Signal<Vec<(ZoneId, u64)>>,
90 mount_revision: Signal<u64>,
93 dir: Signal<Direction>,
95}
96
97impl<T: Clone + 'static> Copy for ZoneRegistry<T> {}
98impl<T: Clone + 'static> Clone for ZoneRegistry<T> {
99 fn clone(&self) -> Self {
100 *self
101 }
102}
103impl<T: Clone + 'static> PartialEq for ZoneRegistry<T> {
104 fn eq(&self, other: &Self) -> bool {
105 self.zones == other.zones && self.dir == other.dir
106 }
107}
108
109impl<T: Clone + 'static> ZoneRegistry<T> {
110 pub fn from_signal(zones: Signal<Vec<ZoneRecord<T>>>) -> Self {
112 Self {
113 zones,
114 registrations: Signal::new(Vec::new()),
115 mount_revision: Signal::new(0),
116 dir: Signal::new(Direction::default()),
117 }
118 }
119
120 pub fn direction(&self) -> Direction {
122 self.dir.try_peek().map(|dir| *dir).unwrap_or_default()
123 }
124
125 pub fn set_direction(&mut self, dir: Direction) {
128 let changed = self.dir.try_peek().map(|current| *current != dir);
129 if changed == Ok(true) {
130 if let Ok(mut current) = self.dir.try_write() {
131 *current = dir;
132 }
133 }
134 }
135
136 pub fn register(&mut self, record: ZoneRecord<T>) -> ZoneRegistration {
138 let registration = ZoneRegistration {
139 id: record.id,
140 generation: NEXT_ZONE_REGISTRATION.fetch_add(1, Ordering::Relaxed),
141 };
142 if let Ok(mut zones) = self.zones.try_write() {
143 if let Some(existing) = zones.iter_mut().find(|z| z.id == record.id) {
144 *existing = record;
145 } else {
146 zones.push(record);
147 }
148 }
149 if let Ok(mut registrations) = self.registrations.try_write() {
150 if let Some(existing) = registrations
151 .iter_mut()
152 .find(|(id, _)| *id == registration.id)
153 {
154 existing.1 = registration.generation;
155 } else {
156 registrations.push((registration.id, registration.generation));
157 }
158 }
159 self.bump_mount_revision();
160 registration
161 }
162
163 pub fn sync_label(&mut self, id: ZoneId, label: Option<String>) {
165 let needs = self
166 .zones
167 .try_peek()
168 .map(|zones| zones.iter().any(|z| z.id == id && z.label != label))
169 .unwrap_or(false);
170 if needs {
171 if let Ok(mut zones) = self.zones.try_write() {
172 if let Some(z) = zones.iter_mut().find(|z| z.id == id) {
173 z.label = label;
174 }
175 }
176 }
177 }
178
179 pub fn unregister(&mut self, id: ZoneId) {
181 let removed = self.zones.try_write().is_ok_and(|mut zones| {
182 let old_len = zones.len();
183 zones.retain(|z| z.id != id);
184 zones.len() != old_len
185 });
186 if let Ok(mut registrations) = self.registrations.try_write() {
187 registrations.retain(|(registered_id, _)| *registered_id != id);
188 }
189 if removed {
190 self.bump_mount_revision();
191 }
192 }
193
194 pub fn set_mounted(&mut self, registration: ZoneRegistration, mounted: Rc<MountedData>) {
197 if !self.is_current(registration) {
198 return;
199 }
200 let mut changed = false;
201 if let Ok(mut zones) = self.zones.try_write() {
202 if let Some(zone) = zones.iter_mut().find(|z| z.id == registration.id) {
203 zone.mounted = Some(mounted);
204 changed = true;
205 }
206 }
207 if changed {
208 self.bump_mount_revision();
209 }
210 }
211
212 pub fn set_rect_if_present(&mut self, registration: ZoneRegistration, rect: Rect) {
216 if !self.is_current(registration) {
217 return;
218 }
219 if let Ok(mut zones) = self.zones.try_write() {
220 if let Some(zone) = zones.iter_mut().find(|z| z.id == registration.id) {
221 zone.rect = Some(rect);
222 }
223 }
224 }
225
226 pub fn set_rect(&mut self, id: ZoneId, rect: Rect) {
230 if let Some(registration) = self.current_registration(id) {
231 self.set_rect_if_present(registration, rect);
232 }
233 }
234
235 pub fn get(&self, id: ZoneId) -> Option<ZoneRecord<T>> {
237 self.zones
238 .try_peek()
239 .ok()?
240 .iter()
241 .find(|z| z.id == id)
242 .cloned()
243 }
244
245 pub fn cached_rect(&self, id: ZoneId) -> Option<Rect> {
248 self.zones
249 .try_peek()
250 .ok()?
251 .iter()
252 .find(|z| z.id == id)
253 .and_then(ZoneRecord::cached_rect)
254 }
255
256 pub fn mounted_handle(&self, id: ZoneId) -> Option<Rc<MountedData>> {
259 self.zones
260 .try_peek()
261 .ok()?
262 .iter()
263 .find(|z| z.id == id)
264 .and_then(ZoneRecord::mounted_handle)
265 }
266
267 pub fn records(&self) -> Vec<ZoneRecord<T>> {
272 self.zones
273 .try_read()
274 .map(|zones| zones.to_vec())
275 .unwrap_or_default()
276 }
277
278 pub fn contains(&self, id: ZoneId) -> bool {
282 self.zones
283 .try_peek()
284 .is_ok_and(|zones| zones.iter().any(|z| z.id == id))
285 }
286
287 pub fn ascend(&self, current: ZoneId) -> Option<ZoneId> {
293 self.parent_of(current).filter(|pid| self.contains(*pid))
294 }
295
296 pub fn acceptable(&self, payload: &T) -> Vec<ZoneRecord<T>> {
298 self.zones
299 .try_peek()
300 .map(|zones| {
301 zones
302 .iter()
303 .filter(|z| z.accepts_payload(payload))
304 .cloned()
305 .collect()
306 })
307 .unwrap_or_default()
308 }
309
310 pub fn step_zone(&self, current: Option<ZoneId>, payload: &T, step: isize) -> Option<ZoneId> {
318 let mut zones = self.acceptable(payload);
319 spatial_sort(&mut zones, self.direction());
320 let current_ix = current.and_then(|c| zones.iter().position(|z| z.id == c));
321 cycle(zones.len(), current_ix, step).map(|ix| zones[ix].id)
322 }
323
324 pub fn parent_of(&self, id: ZoneId) -> Option<ZoneId> {
326 self.zones
327 .try_peek()
328 .ok()?
329 .iter()
330 .find(|z| z.id == id)?
331 .parent
332 }
333
334 pub fn children_of(&self, parent: Option<ZoneId>, payload: &T) -> Vec<ZoneRecord<T>> {
338 let mut zones: Vec<_> = self
339 .zones
340 .try_peek()
341 .map(|zones| {
342 zones
343 .iter()
344 .filter(|z| z.parent == parent && z.accepts_payload(payload))
345 .cloned()
346 .collect()
347 })
348 .unwrap_or_default();
349 spatial_sort(&mut zones, self.direction());
350 zones
351 }
352
353 pub fn step_sibling(
356 &self,
357 current: Option<ZoneId>,
358 payload: &T,
359 step: isize,
360 ) -> Option<ZoneId> {
361 let parent = current.and_then(|c| self.parent_of(c));
362 let siblings = self.children_of(parent, payload);
363 let current_ix = current.and_then(|c| siblings.iter().position(|z| z.id == c));
364 cycle(siblings.len(), current_ix, step).map(|ix| siblings[ix].id)
365 }
366
367 pub fn first_child(&self, id: ZoneId, payload: &T) -> Option<ZoneId> {
369 self.children_of(Some(id), payload).first().map(|z| z.id)
370 }
371
372 pub fn hit_test(&self, point: Point) -> Option<ZoneId> {
376 self.zones
377 .try_peek()
378 .ok()?
379 .iter()
380 .rev()
381 .find(|z| z.cached_rect().map(|r| r.contains(point)).unwrap_or(false))
382 .map(|z| z.id)
383 }
384
385 pub fn hit_test_closest(&self, point: Point, payload: &T, max_distance: f64) -> Option<ZoneId> {
395 if let Some(hit) = self
396 .zones
397 .try_peek()
398 .ok()?
399 .iter()
400 .rev()
401 .find(|z| {
402 z.accepts_payload(payload)
403 && z.cached_rect().map(|r| r.contains(point)).unwrap_or(false)
404 })
405 .map(|z| z.id)
406 {
407 return Some(hit);
408 }
409 let mut best: Option<(ZoneId, f64)> = None;
410 for z in self.acceptable(payload) {
411 let Some(r) = z.cached_rect() else { continue };
412 let dx = (r.x - point.x).max(point.x - (r.x + r.width)).max(0.0);
415 let dy = (r.y - point.y).max(point.y - (r.y + r.height)).max(0.0);
416 let d = (dx * dx + dy * dy).sqrt();
417 if d <= max_distance && best.map(|(_, bd)| d < bd).unwrap_or(true) {
418 best = Some((z.id, d));
419 }
420 }
421 best.map(|(id, _)| id)
422 }
423
424 pub async fn measure_all(&self) {
429 let zones = self.measurement_targets();
430 for (registration, mounted) in zones {
431 if let Ok(r) = mounted.get_client_rect().await {
432 let mut registry = *self;
436 registry.set_rect_if_present(
437 registration,
438 Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height),
439 );
440 }
441 }
442 }
443
444 pub fn refresh_rects(&self) {
446 for (registration, mounted) in self.measurement_targets() {
447 let mut registry = *self;
448 spawn(async move {
449 if let Ok(r) = mounted.get_client_rect().await {
450 registry.set_rect_if_present(
453 registration,
454 Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height),
455 );
456 }
457 });
458 }
459 }
460
461 pub(crate) fn track_mounts(&self) {
464 let _ = self.mount_revision.try_read();
465 }
466
467 fn measurement_targets(&self) -> Vec<(ZoneRegistration, Rc<MountedData>)> {
468 let registrations = self
469 .registrations
470 .try_peek()
471 .map(|registrations| registrations.clone())
472 .unwrap_or_default();
473 self.zones
474 .try_peek()
475 .map(|zones| {
476 zones
477 .iter()
478 .filter_map(|zone| {
479 let mounted = zone.mounted_handle()?;
480 let generation = registrations
481 .iter()
482 .find(|(id, _)| *id == zone.id)
483 .map(|(_, generation)| *generation)?;
484 Some((
485 ZoneRegistration {
486 id: zone.id,
487 generation,
488 },
489 mounted,
490 ))
491 })
492 .collect()
493 })
494 .unwrap_or_default()
495 }
496
497 fn is_current(&self, registration: ZoneRegistration) -> bool {
498 self.registrations.try_peek().is_ok_and(|registrations| {
499 registrations.iter().any(|(id, generation)| {
500 *id == registration.id && *generation == registration.generation
501 })
502 })
503 }
504
505 fn current_registration(&self, id: ZoneId) -> Option<ZoneRegistration> {
506 self.registrations
507 .try_peek()
508 .ok()?
509 .iter()
510 .find(|(registered_id, _)| *registered_id == id)
511 .map(|(_, generation)| ZoneRegistration {
512 id,
513 generation: *generation,
514 })
515 }
516
517 fn bump_mount_revision(&mut self) {
518 if let Ok(mut revision) = self.mount_revision.try_write() {
519 *revision = revision.wrapping_add(1);
520 }
521 }
522}
523
524pub struct RectRefresh {
541 thunks: Signal<Vec<(u64, Callback<()>)>>,
542}
543
544impl Copy for RectRefresh {}
545impl Clone for RectRefresh {
546 fn clone(&self) -> Self {
547 *self
548 }
549}
550impl PartialEq for RectRefresh {
551 fn eq(&self, other: &Self) -> bool {
552 self.thunks == other.thunks
553 }
554}
555
556impl RectRefresh {
557 pub fn from_signal(thunks: Signal<Vec<(u64, Callback<()>)>>) -> Self {
561 Self { thunks }
562 }
563
564 pub fn refresh_all(&self) {
568 for (_, thunk) in self.thunks.peek().iter() {
569 thunk.call(());
570 }
571 }
572
573 pub fn len(&self) -> usize {
575 self.thunks.peek().len()
576 }
577
578 pub fn is_empty(&self) -> bool {
580 self.len() == 0
581 }
582
583 pub(crate) fn register(&mut self, key: u64, thunk: Callback<()>) {
585 let mut thunks = self.thunks.write();
586 if let Some(existing) = thunks.iter_mut().find(|(k, _)| *k == key) {
587 existing.1 = thunk;
588 } else {
589 thunks.push((key, thunk));
590 }
591 }
592
593 pub(crate) fn unregister(&mut self, key: u64) {
595 self.thunks.write().retain(|(k, _)| *k != key);
596 }
597}
598
599fn spatial_sort<T: Clone + 'static>(zones: &mut [ZoneRecord<T>], dir: Direction) {
604 let reading_x = move |x: f64| match dir {
605 Direction::Ltr => x,
606 Direction::Rtl => -x,
607 };
608 zones.sort_by(|a, b| match (a.cached_rect(), b.cached_rect()) {
609 (Some(ra), Some(rb)) => (ra.y, reading_x(ra.x))
610 .partial_cmp(&(rb.y, reading_x(rb.x)))
611 .unwrap_or(std::cmp::Ordering::Equal),
612 (Some(_), None) => std::cmp::Ordering::Less,
613 (None, Some(_)) => std::cmp::Ordering::Greater,
614 (None, None) => std::cmp::Ordering::Equal,
615 });
616}
617
618pub(crate) fn cycle(len: usize, current: Option<usize>, step: isize) -> Option<usize> {
621 if len == 0 {
622 return None;
623 }
624 Some(match current {
625 None => {
626 if step >= 0 {
627 0
628 } else {
629 len - 1
630 }
631 }
632 Some(ix) => (ix as isize + step).rem_euclid(len as isize) as usize,
633 })
634}
635
636#[cfg(test)]
637mod tests {
638 use super::cycle;
639
640 #[test]
641 fn cycle_steps_and_wraps() {
642 assert_eq!(cycle(0, None, 1), None);
643 assert_eq!(cycle(3, None, 1), Some(0));
644 assert_eq!(cycle(3, None, -1), Some(2));
645 assert_eq!(cycle(3, Some(2), 1), Some(0));
646 assert_eq!(cycle(3, Some(0), -1), Some(2));
647 assert_eq!(cycle(3, Some(1), 1), Some(2));
648 }
649}