1use std::cell::Cell;
8use std::rc::Rc;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11use dioxus::html::MountedData;
12use dioxus::prelude::*;
13
14use super::collision::{
15 rank_builtin_candidates, rank_collisions, CollisionDetector, CollisionRequest, ReleasePolicy,
16 ZoneCandidate,
17};
18use super::effects::{DropEffects, DropQuery};
19use super::types::{Direction, DropEffect, DropOutcome, EdgeSet, Point, Rect, ZoneId};
20
21static NEXT_ZONE_REGISTRATION: AtomicU64 = AtomicU64::new(1);
25
26fn trace_registry_failure(
27 operation: &'static str,
28 storage: &'static str,
29 zone: Option<ZoneId>,
30 generation: Option<u64>,
31 error: &impl std::fmt::Display,
32) {
33 tracing::trace!(
34 target: "dioxus_dnd::registry",
35 operation,
36 storage,
37 zone_id = ?zone,
38 registration_generation = ?generation,
39 error = %error,
40 "zone registry operation skipped"
41 );
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub struct ZoneRegistration {
51 id: ZoneId,
52 generation: u64,
53}
54
55pub struct ZoneRecord<T: Clone + 'static> {
57 pub id: ZoneId,
58 pub parent: Option<ZoneId>,
61 pub label: Option<String>,
63 pub on_drop: Callback<DropOutcome<T>>,
65 pub accepts: Option<Callback<T, bool>>,
67 pub mounted: Option<Rc<MountedData>>,
71 pub rect: Option<Rect>,
75}
76
77impl<T: Clone + 'static> Clone for ZoneRecord<T> {
78 fn clone(&self) -> Self {
79 Self {
80 id: self.id,
81 parent: self.parent,
82 label: self.label.clone(),
83 on_drop: self.on_drop,
84 accepts: self.accepts,
85 mounted: self.mounted.clone(),
86 rect: self.rect,
87 }
88 }
89}
90
91impl<T: Clone + 'static> ZoneRecord<T> {
92 pub fn new(id: ZoneId, on_drop: Callback<DropOutcome<T>>) -> Self {
94 Self {
95 id,
96 parent: None,
97 label: None,
98 on_drop,
99 accepts: None,
100 mounted: None,
101 rect: None,
102 }
103 }
104
105 pub fn accepts_payload(&self, payload: &T) -> bool {
107 match self.accepts {
108 Some(cb) => cb.call(payload.clone()),
109 None => true,
110 }
111 }
112
113 pub fn cached_rect(&self) -> Option<Rect> {
115 self.rect
116 }
117
118 pub fn mounted_handle(&self) -> Option<Rc<MountedData>> {
120 self.mounted.clone()
121 }
122}
123
124#[derive(Clone, PartialEq)]
130pub(crate) struct ZonePolicy<T: Clone + 'static> {
131 pub(crate) accepts_query: Option<Callback<DropQuery<T>, bool>>,
132 pub(crate) allowed_effects: DropEffects,
133 pub(crate) edge: Option<EdgeSet>,
134}
135
136impl<T: Clone + 'static> Default for ZonePolicy<T> {
137 fn default() -> Self {
138 Self {
139 accepts_query: None,
140 allowed_effects: DropEffects::default(),
141 edge: None,
142 }
143 }
144}
145
146#[derive(Clone)]
147struct RegisteredZone<T: Clone + 'static> {
148 record: ZoneRecord<T>,
149 policy: ZonePolicy<T>,
150}
151
152impl<T: Clone + 'static> RegisteredZone<T> {
153 fn negotiate(&self, query: &DropQuery<T>) -> Option<DropEffect> {
154 if query.proposed_effect == DropEffect::None || !self.record.accepts_payload(&query.payload)
155 {
156 return None;
157 }
158 if self
159 .policy
160 .accepts_query
161 .is_some_and(|callback| !callback.call(query.clone()))
162 {
163 return None;
164 }
165 self.policy.allowed_effects.negotiate(query.proposed_effect)
166 }
167}
168
169pub(crate) struct NegotiatedZone<T: Clone + 'static> {
170 pub(crate) record: ZoneRecord<T>,
171 pub(crate) effect: DropEffect,
172 pub(crate) edge: Option<EdgeSet>,
173}
174
175pub struct ZoneRegistry<T: Clone + 'static> {
177 zones: Signal<Vec<ZoneRecord<T>>>,
178 registrations: Signal<Vec<(ZoneId, u64)>>,
181 policies: Signal<Vec<(ZoneRegistration, ZonePolicy<T>)>>,
183 mount_revision: Signal<u64>,
186 dir: Signal<Direction>,
188 release: Signal<ReleasePolicy<T>>,
190}
191
192impl<T: Clone + 'static> Copy for ZoneRegistry<T> {}
193impl<T: Clone + 'static> Clone for ZoneRegistry<T> {
194 fn clone(&self) -> Self {
195 *self
196 }
197}
198impl<T: Clone + 'static> PartialEq for ZoneRegistry<T> {
199 fn eq(&self, other: &Self) -> bool {
200 self.zones == other.zones
201 && self.registrations == other.registrations
202 && self.policies == other.policies
203 && self.mount_revision == other.mount_revision
204 && self.dir == other.dir
205 && self.release == other.release
206 }
207}
208
209impl<T: Clone + 'static> ZoneRegistry<T> {
210 pub fn from_signal(zones: Signal<Vec<ZoneRecord<T>>>) -> Self {
212 Self {
213 zones,
214 registrations: Signal::new(Vec::new()),
215 policies: Signal::new(Vec::new()),
216 mount_revision: Signal::new(0),
217 dir: Signal::new(Direction::default()),
218 release: Signal::new(ReleasePolicy::default()),
219 }
220 }
221
222 pub fn release_policy(&self) -> ReleasePolicy<T> {
224 self.release
225 .try_peek()
226 .map(|policy| *policy)
227 .unwrap_or_default()
228 }
229
230 pub fn set_release_policy(&mut self, policy: ReleasePolicy<T>) {
232 if self
233 .release
234 .try_peek()
235 .is_ok_and(|current| *current == policy)
236 {
237 return;
238 }
239 if let Ok(mut current) = self.release.try_write() {
240 *current = policy;
241 }
242 }
243
244 pub fn direction(&self) -> Direction {
246 self.dir.try_peek().map(|dir| *dir).unwrap_or_default()
247 }
248
249 pub fn set_direction(&mut self, dir: Direction) {
252 let changed = match self.dir.try_peek() {
253 Ok(current) => *current != dir,
254 Err(error) => {
255 trace_registry_failure("set_direction", "dir", None, None, &error);
256 return;
257 }
258 };
259 if changed {
260 match self.dir.try_write() {
261 Ok(mut current) => *current = dir,
262 Err(error) => trace_registry_failure("set_direction", "dir", None, None, &error),
263 }
264 }
265 }
266
267 pub fn register(&mut self, record: ZoneRecord<T>) -> ZoneRegistration {
269 self.register_with_policy(record, ZonePolicy::default())
270 }
271
272 pub(crate) fn register_with_policy(
275 &mut self,
276 record: ZoneRecord<T>,
277 policy: ZonePolicy<T>,
278 ) -> ZoneRegistration {
279 let registration = ZoneRegistration {
280 id: record.id,
281 generation: NEXT_ZONE_REGISTRATION.fetch_add(1, Ordering::Relaxed),
282 };
283 let mut zones = match self.zones.try_write() {
286 Ok(zones) => zones,
287 Err(error) => {
288 trace_registry_failure(
289 "register",
290 "zones",
291 Some(registration.id),
292 Some(registration.generation),
293 &error,
294 );
295 return registration;
296 }
297 };
298 let mut registrations = match self.registrations.try_write() {
299 Ok(registrations) => registrations,
300 Err(error) => {
301 trace_registry_failure(
302 "register",
303 "registrations",
304 Some(registration.id),
305 Some(registration.generation),
306 &error,
307 );
308 return registration;
309 }
310 };
311 let mut policies = match self.policies.try_write() {
312 Ok(policies) => policies,
313 Err(error) => {
314 trace_registry_failure(
315 "register",
316 "policies",
317 Some(registration.id),
318 Some(registration.generation),
319 &error,
320 );
321 return registration;
322 }
323 };
324 if let Some(existing) = zones.iter_mut().find(|z| z.id == record.id) {
325 *existing = record;
326 } else {
327 zones.push(record);
328 }
329 if let Some(existing) = registrations
330 .iter_mut()
331 .find(|(id, _)| *id == registration.id)
332 {
333 existing.1 = registration.generation;
334 } else {
335 registrations.push((registration.id, registration.generation));
336 }
337 policies.retain(|(candidate, _)| candidate.id != registration.id);
338 policies.push((registration, policy));
339 drop(policies);
340 drop(registrations);
341 drop(zones);
342 self.bump_mount_revision();
343 registration
344 }
345
346 pub fn sync_label(&mut self, id: ZoneId, label: Option<String>) {
348 let needs = match self.zones.try_peek() {
349 Ok(zones) => zones.iter().any(|z| z.id == id && z.label != label),
350 Err(error) => {
351 trace_registry_failure("sync_label", "zones", Some(id), None, &error);
352 return;
353 }
354 };
355 if needs {
356 match self.zones.try_write() {
357 Ok(mut zones) => {
358 if let Some(z) = zones.iter_mut().find(|z| z.id == id) {
359 z.label = label;
360 }
361 }
362 Err(error) => trace_registry_failure("sync_label", "zones", Some(id), None, &error),
363 }
364 }
365 }
366
367 pub(crate) fn sync_parent(&mut self, registration: ZoneRegistration, parent: Option<ZoneId>) {
369 if !self.is_current(registration, "sync_parent") {
370 return;
371 }
372 let needs = self.zones.try_peek().is_ok_and(|zones| {
373 zones
374 .iter()
375 .any(|zone| zone.id == registration.id && zone.parent != parent)
376 });
377 if !needs {
378 return;
379 }
380 match self.zones.try_write() {
381 Ok(mut zones) => {
382 if let Some(zone) = zones.iter_mut().find(|zone| zone.id == registration.id) {
383 zone.parent = parent;
384 }
385 }
386 Err(error) => trace_registry_failure(
387 "sync_parent",
388 "zones",
389 Some(registration.id),
390 Some(registration.generation),
391 &error,
392 ),
393 }
394 }
395
396 pub(crate) fn sync_policy(
400 &mut self,
401 registration: ZoneRegistration,
402 accepts: Option<Callback<T, bool>>,
403 policy: ZonePolicy<T>,
404 ) {
405 if !self.is_current(registration, "sync_policy") {
406 return;
407 }
408 let mut zones = match self.zones.try_write() {
409 Ok(zones) => zones,
410 Err(error) => {
411 trace_registry_failure(
412 "sync_policy",
413 "zones",
414 Some(registration.id),
415 Some(registration.generation),
416 &error,
417 );
418 return;
419 }
420 };
421 let mut policies = match self.policies.try_write() {
422 Ok(policies) => policies,
423 Err(error) => {
424 trace_registry_failure(
425 "sync_policy",
426 "policies",
427 Some(registration.id),
428 Some(registration.generation),
429 &error,
430 );
431 return;
432 }
433 };
434 if let Some(zone) = zones.iter_mut().find(|zone| zone.id == registration.id) {
435 zone.accepts = accepts;
436 }
437 if let Some((_, current)) = policies
438 .iter_mut()
439 .find(|(candidate, _)| *candidate == registration)
440 {
441 *current = policy;
442 }
443 }
444
445 pub fn unregister(&mut self, id: ZoneId) {
447 let mut zones = match self.zones.try_write() {
449 Ok(zones) => zones,
450 Err(error) => {
451 trace_registry_failure("unregister", "zones", Some(id), None, &error);
452 return;
453 }
454 };
455 let mut registrations = match self.registrations.try_write() {
456 Ok(registrations) => registrations,
457 Err(error) => {
458 trace_registry_failure("unregister", "registrations", Some(id), None, &error);
459 return;
460 }
461 };
462 let mut policies = match self.policies.try_write() {
463 Ok(policies) => policies,
464 Err(error) => {
465 trace_registry_failure("unregister", "policies", Some(id), None, &error);
466 return;
467 }
468 };
469 let old_len = zones.len();
470 zones.retain(|z| z.id != id);
471 let removed = zones.len() != old_len;
472 registrations.retain(|(registered_id, _)| *registered_id != id);
473 policies.retain(|(registration, _)| registration.id != id);
474 drop(policies);
475 drop(registrations);
476 drop(zones);
477 if removed {
478 self.bump_mount_revision();
479 }
480 }
481
482 pub fn unregister_registration(&mut self, registration: ZoneRegistration) {
488 let current = self
489 .registrations
490 .try_read()
491 .ok()
492 .and_then(|registrations| {
493 registrations
494 .iter()
495 .find(|(id, _)| *id == registration.id)
496 .copied()
497 });
498 if current == Some((registration.id, registration.generation)) {
499 self.unregister(registration.id);
500 }
501 }
502
503 pub fn set_mounted(&mut self, registration: ZoneRegistration, mounted: Rc<MountedData>) {
506 if !self.is_current(registration, "set_mounted") {
507 return;
508 }
509 let mut changed = false;
510 match self.zones.try_write() {
511 Ok(mut zones) => {
512 if let Some(zone) = zones.iter_mut().find(|z| z.id == registration.id) {
513 zone.mounted = Some(mounted);
514 changed = true;
515 }
516 }
517 Err(error) => {
518 trace_registry_failure(
519 "set_mounted",
520 "zones",
521 Some(registration.id),
522 Some(registration.generation),
523 &error,
524 );
525 }
526 }
527 if changed {
528 self.bump_mount_revision();
529 }
530 }
531
532 pub fn set_rect_if_present(&mut self, registration: ZoneRegistration, rect: Rect) {
536 if !self.is_current(registration, "set_rect_if_present") {
537 return;
538 }
539 match self.zones.try_write() {
540 Ok(mut zones) => {
541 if let Some(zone) = zones.iter_mut().find(|z| z.id == registration.id) {
542 zone.rect = Some(rect);
543 }
544 }
545 Err(error) => {
546 trace_registry_failure(
547 "set_rect_if_present",
548 "zones",
549 Some(registration.id),
550 Some(registration.generation),
551 &error,
552 );
553 }
554 }
555 }
556
557 pub fn set_rect(&mut self, id: ZoneId, rect: Rect) {
561 if let Some(registration) = self.current_registration(id, "set_rect") {
562 self.set_rect_if_present(registration, rect);
563 }
564 }
565
566 pub fn get(&self, id: ZoneId) -> Option<ZoneRecord<T>> {
568 self.zones
569 .try_peek()
570 .ok()?
571 .iter()
572 .find(|z| z.id == id)
573 .cloned()
574 }
575
576 pub fn cached_rect(&self, id: ZoneId) -> Option<Rect> {
579 self.zones
580 .try_peek()
581 .ok()?
582 .iter()
583 .find(|z| z.id == id)
584 .and_then(ZoneRecord::cached_rect)
585 }
586
587 pub fn mounted_handle(&self, id: ZoneId) -> Option<Rc<MountedData>> {
590 self.zones
591 .try_peek()
592 .ok()?
593 .iter()
594 .find(|z| z.id == id)
595 .and_then(ZoneRecord::mounted_handle)
596 }
597
598 pub fn records(&self) -> Vec<ZoneRecord<T>> {
603 let records = self
604 .zones
605 .try_read()
606 .map(|zones| zones.to_vec())
607 .unwrap_or_default();
608 records
609 }
610
611 fn snapshot(&self) -> Vec<RegisteredZone<T>> {
615 let records = self
616 .zones
617 .try_peek()
618 .map(|zones| zones.to_vec())
619 .unwrap_or_default();
620 let registrations = self
621 .registrations
622 .try_peek()
623 .map(|registrations| registrations.to_vec())
624 .unwrap_or_default();
625 let policies = self
626 .policies
627 .try_peek()
628 .map(|policies| policies.to_vec())
629 .unwrap_or_default();
630 records
631 .into_iter()
632 .map(|record| {
633 let registration = registrations.iter().find(|(id, _)| *id == record.id).map(
634 |(id, generation)| ZoneRegistration {
635 id: *id,
636 generation: *generation,
637 },
638 );
639 let policy = registration
640 .and_then(|registration| {
641 policies
642 .iter()
643 .find(|(candidate, _)| *candidate == registration)
644 .map(|(_, policy)| policy.clone())
645 })
646 .unwrap_or_default();
647 RegisteredZone { record, policy }
648 })
649 .collect()
650 }
651
652 pub fn contains(&self, id: ZoneId) -> bool {
656 self.zones
657 .try_peek()
658 .is_ok_and(|zones| zones.iter().any(|z| z.id == id))
659 }
660
661 pub fn ascend(&self, current: ZoneId) -> Option<ZoneId> {
667 self.parent_of(current).filter(|pid| self.contains(*pid))
668 }
669
670 pub fn acceptable(&self, payload: &T) -> Vec<ZoneRecord<T>> {
672 self.snapshot()
673 .into_iter()
674 .filter(|zone| zone.record.accepts_payload(payload))
675 .map(|zone| zone.record)
676 .collect()
677 }
678
679 pub fn acceptable_query(&self, query: &DropQuery<T>) -> Vec<ZoneRecord<T>> {
681 self.snapshot()
682 .into_iter()
683 .filter(|zone| zone.negotiate(query).is_some())
684 .map(|zone| zone.record)
685 .collect()
686 }
687
688 pub(crate) fn negotiate_zone(
690 &self,
691 id: ZoneId,
692 query: &DropQuery<T>,
693 ) -> Option<NegotiatedZone<T>> {
694 let zone = self
695 .snapshot()
696 .into_iter()
697 .find(|zone| zone.record.id == id)?;
698 let effect = zone.negotiate(query)?;
699 Some(NegotiatedZone {
700 record: zone.record,
701 effect,
702 edge: zone.policy.edge,
703 })
704 }
705
706 pub fn step_zone(&self, current: Option<ZoneId>, payload: &T, step: isize) -> Option<ZoneId> {
715 let mut zones = self.acceptable(payload);
716 spatial_sort(&mut zones, self.direction());
717 let current_ix = current.and_then(|c| zones.iter().position(|z| z.id == c));
718 cycle(zones.len(), current_ix, step).map(|ix| zones[ix].id)
719 }
720
721 pub fn step_zone_query(
722 &self,
723 current: Option<ZoneId>,
724 query: &DropQuery<T>,
725 step: isize,
726 ) -> Option<ZoneId> {
727 let mut zones = self.acceptable_query(query);
728 spatial_sort(&mut zones, self.direction());
729 let current_ix = current.and_then(|candidate| zones.iter().position(|z| z.id == candidate));
730 cycle(zones.len(), current_ix, step).map(|index| zones[index].id)
731 }
732
733 pub fn parent_of(&self, id: ZoneId) -> Option<ZoneId> {
735 self.zones
736 .try_peek()
737 .ok()?
738 .iter()
739 .find(|z| z.id == id)?
740 .parent
741 }
742
743 pub fn children_of(&self, parent: Option<ZoneId>, payload: &T) -> Vec<ZoneRecord<T>> {
748 let mut zones: Vec<_> = self
749 .snapshot()
750 .into_iter()
751 .filter(|zone| zone.record.parent == parent && zone.record.accepts_payload(payload))
752 .map(|zone| zone.record)
753 .collect();
754 spatial_sort(&mut zones, self.direction());
755 zones
756 }
757
758 pub fn children_of_query(
759 &self,
760 parent: Option<ZoneId>,
761 query: &DropQuery<T>,
762 ) -> Vec<ZoneRecord<T>> {
763 let mut zones: Vec<_> = self
764 .snapshot()
765 .into_iter()
766 .filter(|zone| zone.record.parent == parent && zone.negotiate(query).is_some())
767 .map(|zone| zone.record)
768 .collect();
769 spatial_sort(&mut zones, self.direction());
770 zones
771 }
772
773 pub fn step_sibling(
776 &self,
777 current: Option<ZoneId>,
778 payload: &T,
779 step: isize,
780 ) -> Option<ZoneId> {
781 let parent = current.and_then(|c| self.parent_of(c));
782 let siblings = self.children_of(parent, payload);
783 let current_ix = current.and_then(|c| siblings.iter().position(|z| z.id == c));
784 cycle(siblings.len(), current_ix, step).map(|ix| siblings[ix].id)
785 }
786
787 pub fn step_sibling_query(
788 &self,
789 current: Option<ZoneId>,
790 query: &DropQuery<T>,
791 step: isize,
792 ) -> Option<ZoneId> {
793 let parent = current.and_then(|candidate| self.parent_of(candidate));
794 let siblings = self.children_of_query(parent, query);
795 let current_ix =
796 current.and_then(|candidate| siblings.iter().position(|zone| zone.id == candidate));
797 cycle(siblings.len(), current_ix, step).map(|index| siblings[index].id)
798 }
799
800 pub fn first_child(&self, id: ZoneId, payload: &T) -> Option<ZoneId> {
802 self.children_of(Some(id), payload).first().map(|z| z.id)
803 }
804
805 pub fn first_child_query(&self, id: ZoneId, query: &DropQuery<T>) -> Option<ZoneId> {
806 self.children_of_query(Some(id), query)
807 .first()
808 .map(|zone| zone.id)
809 }
810
811 pub fn hit_test(&self, point: Point) -> Option<ZoneId> {
816 self.zones
817 .try_peek()
818 .ok()?
819 .iter()
820 .rev()
821 .find(|z| z.cached_rect().map(|r| r.contains(point)).unwrap_or(false))
822 .map(|z| z.id)
823 }
824
825 pub fn hit_test_closest(&self, point: Point, payload: &T, max_distance: f64) -> Option<ZoneId> {
835 let zones = self.snapshot();
836 let mut best: Option<(ZoneId, f64)> = None;
837 for z in zones.iter().rev() {
840 if !z.record.accepts_payload(payload) {
841 continue;
842 }
843 let Some(r) = z.record.cached_rect() else {
844 continue;
845 };
846 if r.contains(point) {
847 return Some(z.record.id);
848 }
849 let dx = (r.x - point.x).max(point.x - (r.x + r.width)).max(0.0);
852 let dy = (r.y - point.y).max(point.y - (r.y + r.height)).max(0.0);
853 let d = (dx * dx + dy * dy).sqrt();
854 if d <= max_distance && best.map(|(_, bd)| d <= bd).unwrap_or(true) {
858 best = Some((z.record.id, d));
859 }
860 }
861 best.map(|(id, _)| id)
862 }
863
864 pub fn resolve(
868 &self,
869 query: &DropQuery<T>,
870 point: Point,
871 active_rect: Option<Rect>,
872 max_distance: f64,
873 ) -> Option<(ZoneId, DropEffect)> {
874 let zones = self.snapshot();
875 let accepted: Vec<_> = zones
876 .iter()
877 .enumerate()
878 .filter_map(|(order, zone)| {
879 let effect = zone.negotiate(query)?;
880 Some((
881 ZoneCandidate {
882 id: zone.record.id,
883 rect: zone.record.cached_rect()?,
884 order,
885 },
886 effect,
887 ))
888 })
889 .collect();
890 let candidates = accepted.iter().map(|(candidate, _)| *candidate).collect();
891 let policy = self.release_policy();
892 let max_distance = max_distance.max(0.0);
893 let ranked = match policy.collision {
894 CollisionDetector::BuiltIn(strategy) => {
895 rank_builtin_candidates(strategy, point, active_rect, candidates, max_distance)
896 }
897 CollisionDetector::Custom(callback) => rank_collisions(
898 CollisionDetector::Custom(callback),
899 CollisionRequest {
900 pointer: point,
901 active_rect,
902 payload: query.payload.clone(),
903 candidates,
904 max_distance,
905 },
906 ),
907 };
908 for collision in ranked {
909 if let Some((candidate, effect)) = accepted
910 .iter()
911 .find(|(candidate, _)| candidate.id == collision.zone)
912 {
913 return Some((candidate.id, *effect));
914 }
915 }
916 None
917 }
918
919 pub fn resolve_hover(
924 &self,
925 query: &DropQuery<T>,
926 point: Point,
927 active_rect: Option<Rect>,
928 current: Option<ZoneId>,
929 ) -> Option<(ZoneId, DropEffect)> {
930 if let Some(hit) = self.resolve(query, point, active_rect, 0.0) {
931 return Some(hit);
932 }
933 let policy = self.release_policy();
934 if !policy.sticky {
935 return None;
936 }
937 let current = current?;
938 let zone = self.negotiate_zone(current, query)?;
939 let rect = zone.record.cached_rect()?;
940 (crate::core::collision::point_rect_distance(point, rect) <= policy.recovery_radius)
941 .then_some((current, zone.effect))
942 }
943
944 pub async fn measure_all(&self) {
949 let zones = self.measurement_targets();
950 for (registration, mounted) in zones {
951 if let Ok(r) = mounted.get_client_rect().await {
952 let mut registry = *self;
956 registry.set_rect_if_present(
957 registration,
958 Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height),
959 );
960 }
961 }
962 }
963
964 pub fn refresh_rects(&self) {
966 self.spawn_rect_refresh(None);
967 }
968
969 pub(crate) fn refresh_rects_then(&self, on_complete: impl Fn() + 'static) {
975 self.spawn_rect_refresh(Some(Rc::new(on_complete)));
976 }
977
978 fn spawn_rect_refresh(&self, on_complete: Option<Rc<dyn Fn()>>) {
979 let targets = self.measurement_targets();
980 if targets.is_empty() {
981 if let Some(on_complete) = on_complete {
982 on_complete();
983 }
984 return;
985 }
986
987 let remaining = on_complete.map(|callback| (Rc::new(Cell::new(targets.len())), callback));
988 for (registration, mounted) in targets {
989 let mut registry = *self;
990 let remaining = remaining.clone();
991 spawn(async move {
992 if let Ok(r) = mounted.get_client_rect().await {
993 registry.set_rect_if_present(
996 registration,
997 Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height),
998 );
999 }
1000 if let Some((remaining, on_complete)) = remaining {
1001 let pending = remaining.get();
1002 debug_assert!(pending > 0, "rect refresh completion counted twice");
1003 remaining.set(pending.saturating_sub(1));
1004 if pending == 1 {
1005 on_complete();
1006 }
1007 }
1008 });
1009 }
1010 }
1011
1012 pub(crate) fn track_mounts(&self) {
1015 let _ = self.mount_revision.try_read();
1016 }
1017
1018 fn measurement_targets(&self) -> Vec<(ZoneRegistration, Rc<MountedData>)> {
1019 let registrations = self
1020 .registrations
1021 .try_peek()
1022 .map(|registrations| registrations.clone())
1023 .unwrap_or_default();
1024 self.zones
1025 .try_peek()
1026 .map(|zones| {
1027 zones
1028 .iter()
1029 .filter_map(|zone| {
1030 let mounted = zone.mounted_handle()?;
1031 let generation = registrations
1032 .iter()
1033 .find(|(id, _)| *id == zone.id)
1034 .map(|(_, generation)| *generation)?;
1035 Some((
1036 ZoneRegistration {
1037 id: zone.id,
1038 generation,
1039 },
1040 mounted,
1041 ))
1042 })
1043 .collect()
1044 })
1045 .unwrap_or_default()
1046 }
1047
1048 fn is_current(&self, registration: ZoneRegistration, operation: &'static str) -> bool {
1049 match self.registrations.try_peek() {
1050 Ok(registrations) => registrations.iter().any(|(id, generation)| {
1051 *id == registration.id && *generation == registration.generation
1052 }),
1053 Err(error) => {
1054 trace_registry_failure(
1055 operation,
1056 "registrations",
1057 Some(registration.id),
1058 Some(registration.generation),
1059 &error,
1060 );
1061 false
1062 }
1063 }
1064 }
1065
1066 fn current_registration(
1067 &self,
1068 id: ZoneId,
1069 operation: &'static str,
1070 ) -> Option<ZoneRegistration> {
1071 match self.registrations.try_peek() {
1072 Ok(registrations) => registrations
1073 .iter()
1074 .find(|(registered_id, _)| *registered_id == id)
1075 .map(|(_, generation)| ZoneRegistration {
1076 id,
1077 generation: *generation,
1078 }),
1079 Err(error) => {
1080 trace_registry_failure(operation, "registrations", Some(id), None, &error);
1081 None
1082 }
1083 }
1084 }
1085
1086 fn bump_mount_revision(&mut self) {
1087 match self.mount_revision.try_write() {
1088 Ok(mut revision) => *revision = revision.wrapping_add(1),
1089 Err(error) => {
1090 trace_registry_failure("bump_mount_revision", "mount_revision", None, None, &error)
1091 }
1092 }
1093 }
1094}
1095
1096pub struct RectRefresh {
1113 thunks: Signal<Vec<(u64, Callback<()>)>>,
1114}
1115
1116impl Copy for RectRefresh {}
1117impl Clone for RectRefresh {
1118 fn clone(&self) -> Self {
1119 *self
1120 }
1121}
1122impl PartialEq for RectRefresh {
1123 fn eq(&self, other: &Self) -> bool {
1124 self.thunks == other.thunks
1125 }
1126}
1127
1128impl RectRefresh {
1129 pub fn from_signal(thunks: Signal<Vec<(u64, Callback<()>)>>) -> Self {
1133 Self { thunks }
1134 }
1135
1136 pub fn refresh_all(&self) {
1140 for (_, thunk) in self.thunks.peek().iter() {
1141 thunk.call(());
1142 }
1143 }
1144
1145 pub fn len(&self) -> usize {
1147 self.thunks.peek().len()
1148 }
1149
1150 pub fn is_empty(&self) -> bool {
1152 self.len() == 0
1153 }
1154
1155 pub(crate) fn register(&mut self, key: u64, thunk: Callback<()>) {
1157 let mut thunks = self.thunks.write();
1158 if let Some(existing) = thunks.iter_mut().find(|(k, _)| *k == key) {
1159 existing.1 = thunk;
1160 } else {
1161 thunks.push((key, thunk));
1162 }
1163 }
1164
1165 pub(crate) fn unregister(&mut self, key: u64) {
1167 self.thunks.write().retain(|(k, _)| *k != key);
1168 }
1169}
1170
1171fn spatial_sort<T: Clone + 'static>(zones: &mut [ZoneRecord<T>], dir: Direction) {
1176 const ROW_TOP_SLOP: f64 = 1.0;
1177
1178 zones.sort_by(|a, b| match (a.cached_rect(), b.cached_rect()) {
1182 (Some(ra), Some(rb)) => ra.y.total_cmp(&rb.y),
1183 (Some(_), None) => std::cmp::Ordering::Less,
1184 (None, Some(_)) => std::cmp::Ordering::Greater,
1185 (None, None) => std::cmp::Ordering::Equal,
1186 });
1187
1188 let measured = zones
1189 .iter()
1190 .position(|zone| zone.cached_rect().is_none())
1191 .unwrap_or(zones.len());
1192 let mut row_start = 0;
1193 while row_start < measured {
1194 let row_y = zones[row_start].cached_rect().unwrap().y;
1195 let mut row_end = row_start + 1;
1196 while row_end < measured {
1197 let y = zones[row_end].cached_rect().unwrap().y;
1198 if !row_y.is_finite() || !y.is_finite() || (y - row_y).abs() > ROW_TOP_SLOP {
1199 break;
1200 }
1201 row_end += 1;
1202 }
1203 zones[row_start..row_end].sort_by(|a, b| {
1204 let ax = a.cached_rect().unwrap().x;
1205 let bx = b.cached_rect().unwrap().x;
1206 match dir {
1207 Direction::Ltr => ax.total_cmp(&bx),
1208 Direction::Rtl => bx.total_cmp(&ax),
1209 }
1210 });
1211 row_start = row_end;
1212 }
1213}
1214
1215pub(crate) fn cycle(len: usize, current: Option<usize>, step: isize) -> Option<usize> {
1218 if len == 0 {
1219 return None;
1220 }
1221 Some(match current {
1222 None => {
1223 if step >= 0 {
1224 0
1225 } else {
1226 len - 1
1227 }
1228 }
1229 Some(ix) => (ix as isize + step).rem_euclid(len as isize) as usize,
1230 })
1231}
1232
1233#[cfg(test)]
1234mod tests {
1235 use std::cell::Cell;
1236 use std::rc::Rc;
1237
1238 use dioxus::prelude::*;
1239
1240 use super::{
1241 cycle, Direction, DropQuery, Point, Rect, ReleasePolicy, ZoneId, ZonePolicy, ZoneRecord,
1242 ZoneRegistry,
1243 };
1244
1245 #[test]
1246 fn cycle_steps_and_wraps() {
1247 assert_eq!(cycle(0, None, 1), None);
1248 assert_eq!(cycle(3, None, 1), Some(0));
1249 assert_eq!(cycle(3, None, -1), Some(2));
1250 assert_eq!(cycle(3, Some(2), 1), Some(0));
1251 assert_eq!(cycle(3, Some(0), -1), Some(2));
1252 assert_eq!(cycle(3, Some(1), 1), Some(2));
1253 }
1254
1255 fn equality_probe() -> Element {
1256 let zones = use_signal(Vec::<ZoneRecord<u8>>::new);
1257 let registrations = use_signal(Vec::<(ZoneId, u64)>::new);
1258 let other_registrations = use_signal(Vec::<(ZoneId, u64)>::new);
1259 let policies = use_signal(Vec::new);
1260 let mount_revision = use_signal(|| 0u64);
1261 let other_mount_revision = use_signal(|| 0u64);
1262 let dir = use_signal(Direction::default);
1263 let release = use_signal(ReleasePolicy::default);
1264 let registry = ZoneRegistry {
1265 zones,
1266 registrations,
1267 policies,
1268 mount_revision,
1269 dir,
1270 release,
1271 };
1272 let copy = registry;
1273
1274 assert!(registry == copy, "a copied handle must compare equal");
1275 assert!(
1276 registry
1277 != ZoneRegistry {
1278 registrations: other_registrations,
1279 ..registry
1280 },
1281 "registration identity is part of registry identity"
1282 );
1283 assert!(
1284 registry
1285 != ZoneRegistry {
1286 mount_revision: other_mount_revision,
1287 ..registry
1288 },
1289 "mount-revision identity is part of registry identity"
1290 );
1291 rsx! {}
1292 }
1293
1294 #[test]
1295 fn equality_covers_every_registry_storage_handle() {
1296 let mut dom = VirtualDom::new(equality_probe);
1297 dom.rebuild_in_place();
1298 }
1299
1300 fn single_negotiation_probe() -> Element {
1301 let calls = Rc::new(Cell::new(0));
1302 let observed_calls = calls.clone();
1303 let mut registry = ZoneRegistry::from_signal(Signal::new(Vec::<ZoneRecord<u8>>::new()));
1304 let record = ZoneRecord::new(ZoneId(1), Callback::new(|_| {}));
1305 let registration = registry.register_with_policy(
1306 record,
1307 ZonePolicy {
1308 accepts_query: Some(Callback::new(move |_| {
1309 calls.set(calls.get() + 1);
1310 true
1311 })),
1312 ..ZonePolicy::default()
1313 },
1314 );
1315 registry.set_rect_if_present(registration, Rect::new(0.0, 0.0, 20.0, 20.0));
1316
1317 assert_eq!(
1318 registry.resolve(&DropQuery::new(7), Point::new(10.0, 10.0), None, 0.0,),
1319 Some((ZoneId(1), crate::core::DropEffect::Move))
1320 );
1321 assert_eq!(
1322 observed_calls.get(),
1323 1,
1324 "one hit-test must evaluate target policy once"
1325 );
1326 rsx! {}
1327 }
1328
1329 #[test]
1330 fn resolution_negotiates_each_candidate_once() {
1331 let mut dom = VirtualDom::new(single_negotiation_probe);
1332 dom.rebuild_in_place();
1333 }
1334
1335 fn reentrant_acceptance_probe() -> Element {
1336 let mut registry = ZoneRegistry::from_signal(Signal::new(Vec::<ZoneRecord<u8>>::new()));
1337 let mut callback_registry = registry;
1338 let mut record = ZoneRecord::new(ZoneId(1), Callback::new(|_| {}));
1339 record.accepts = Some(Callback::new(move |_| {
1340 callback_registry.register(ZoneRecord::new(ZoneId(2), Callback::new(|_| {})));
1341 true
1342 }));
1343 registry.register(record);
1344
1345 let acceptable = registry.acceptable(&7);
1346 assert_eq!(acceptable.len(), 1);
1347 assert!(
1348 registry.contains(ZoneId(2)),
1349 "acceptance callbacks must be able to mutate the registry"
1350 );
1351 rsx! {}
1352 }
1353
1354 #[test]
1355 fn acceptance_callbacks_run_without_a_registry_borrow() {
1356 let mut dom = VirtualDom::new(reentrant_acceptance_probe);
1357 dom.rebuild_in_place();
1358 }
1359
1360 fn structural_borrow_probe() -> Element {
1361 let zones = use_signal(Vec::<ZoneRecord<u8>>::new);
1362 let mut registry = ZoneRegistry::from_signal(zones);
1363 let record = |id: u64| ZoneRecord {
1364 id: ZoneId(id),
1365 parent: None,
1366 label: None,
1367 on_drop: Callback::new(|_| {}),
1368 accepts: None,
1369 mounted: None,
1370 rect: Some(Rect::new(0.0, 0.0, 10.0, 10.0)),
1371 };
1372 registry.register(record(1));
1373
1374 {
1376 let zones = registry.zones;
1377 let _zones = zones.read();
1378 registry.register(record(2));
1379 }
1380 assert!(registry.get(ZoneId(2)).is_none());
1381 assert!(registry.current_registration(ZoneId(2), "test").is_none());
1382
1383 {
1386 let registrations = registry.registrations;
1387 let _registrations = registrations.read();
1388 registry.register(record(3));
1389 }
1390 assert!(registry.get(ZoneId(3)).is_none());
1391 assert!(registry.current_registration(ZoneId(3), "test").is_none());
1392
1393 {
1395 let registrations = registry.registrations;
1396 let _registrations = registrations.read();
1397 registry.unregister(ZoneId(1));
1398 }
1399 assert!(registry.get(ZoneId(1)).is_some());
1400 assert!(registry.current_registration(ZoneId(1), "test").is_some());
1401 rsx! {}
1402 }
1403
1404 #[test]
1405 fn structural_borrow_failures_cannot_split_registry_state() {
1406 let mut dom = VirtualDom::new(structural_borrow_probe);
1407 dom.rebuild_in_place();
1408 }
1409}