use std::cell::Cell;
use std::rc::Rc;
use std::sync::atomic::{AtomicU64, Ordering};
use dioxus::html::MountedData;
use dioxus::prelude::*;
use super::collision::{
rank_builtin_candidates, rank_collisions, CollisionDetector, CollisionRequest, ReleasePolicy,
ZoneCandidate,
};
use super::effects::{DropEffects, DropQuery};
use super::types::{Direction, DropEffect, DropOutcome, EdgeSet, Point, Rect, ZoneId};
static NEXT_ZONE_REGISTRATION: AtomicU64 = AtomicU64::new(1);
fn trace_registry_failure(
operation: &'static str,
storage: &'static str,
zone: Option<ZoneId>,
generation: Option<u64>,
error: &impl std::fmt::Display,
) {
tracing::trace!(
target: "dioxus_dnd::registry",
operation,
storage,
zone_id = ?zone,
registration_generation = ?generation,
error = %error,
"zone registry operation skipped"
);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ZoneRegistration {
id: ZoneId,
generation: u64,
}
pub struct ZoneRecord<T: Clone + 'static> {
pub id: ZoneId,
pub parent: Option<ZoneId>,
pub label: Option<String>,
pub on_drop: Callback<DropOutcome<T>>,
pub accepts: Option<Callback<T, bool>>,
pub mounted: Option<Rc<MountedData>>,
pub rect: Option<Rect>,
}
impl<T: Clone + 'static> Clone for ZoneRecord<T> {
fn clone(&self) -> Self {
Self {
id: self.id,
parent: self.parent,
label: self.label.clone(),
on_drop: self.on_drop,
accepts: self.accepts,
mounted: self.mounted.clone(),
rect: self.rect,
}
}
}
impl<T: Clone + 'static> ZoneRecord<T> {
pub fn new(id: ZoneId, on_drop: Callback<DropOutcome<T>>) -> Self {
Self {
id,
parent: None,
label: None,
on_drop,
accepts: None,
mounted: None,
rect: None,
}
}
pub fn accepts_payload(&self, payload: &T) -> bool {
match self.accepts {
Some(cb) => cb.call(payload.clone()),
None => true,
}
}
pub fn cached_rect(&self) -> Option<Rect> {
self.rect
}
pub fn mounted_handle(&self) -> Option<Rc<MountedData>> {
self.mounted.clone()
}
}
#[derive(Clone, PartialEq)]
pub(crate) struct ZonePolicy<T: Clone + 'static> {
pub(crate) accepts_query: Option<Callback<DropQuery<T>, bool>>,
pub(crate) allowed_effects: DropEffects,
pub(crate) edge: Option<EdgeSet>,
}
impl<T: Clone + 'static> Default for ZonePolicy<T> {
fn default() -> Self {
Self {
accepts_query: None,
allowed_effects: DropEffects::default(),
edge: None,
}
}
}
#[derive(Clone)]
struct RegisteredZone<T: Clone + 'static> {
record: ZoneRecord<T>,
policy: ZonePolicy<T>,
}
impl<T: Clone + 'static> RegisteredZone<T> {
fn negotiate(&self, query: &DropQuery<T>) -> Option<DropEffect> {
if query.proposed_effect == DropEffect::None || !self.record.accepts_payload(&query.payload)
{
return None;
}
if self
.policy
.accepts_query
.is_some_and(|callback| !callback.call(query.clone()))
{
return None;
}
self.policy.allowed_effects.negotiate(query.proposed_effect)
}
}
pub(crate) struct NegotiatedZone<T: Clone + 'static> {
pub(crate) record: ZoneRecord<T>,
pub(crate) effect: DropEffect,
pub(crate) edge: Option<EdgeSet>,
}
pub struct ZoneRegistry<T: Clone + 'static> {
zones: Signal<Vec<ZoneRecord<T>>>,
registrations: Signal<Vec<(ZoneId, u64)>>,
policies: Signal<Vec<(ZoneRegistration, ZonePolicy<T>)>>,
mount_revision: Signal<u64>,
dir: Signal<Direction>,
release: Signal<ReleasePolicy<T>>,
}
impl<T: Clone + 'static> Copy for ZoneRegistry<T> {}
impl<T: Clone + 'static> Clone for ZoneRegistry<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Clone + 'static> PartialEq for ZoneRegistry<T> {
fn eq(&self, other: &Self) -> bool {
self.zones == other.zones
&& self.registrations == other.registrations
&& self.policies == other.policies
&& self.mount_revision == other.mount_revision
&& self.dir == other.dir
&& self.release == other.release
}
}
impl<T: Clone + 'static> ZoneRegistry<T> {
pub fn from_signal(zones: Signal<Vec<ZoneRecord<T>>>) -> Self {
Self {
zones,
registrations: Signal::new(Vec::new()),
policies: Signal::new(Vec::new()),
mount_revision: Signal::new(0),
dir: Signal::new(Direction::default()),
release: Signal::new(ReleasePolicy::default()),
}
}
pub fn release_policy(&self) -> ReleasePolicy<T> {
self.release
.try_peek()
.map(|policy| *policy)
.unwrap_or_default()
}
pub fn set_release_policy(&mut self, policy: ReleasePolicy<T>) {
if self
.release
.try_peek()
.is_ok_and(|current| *current == policy)
{
return;
}
if let Ok(mut current) = self.release.try_write() {
*current = policy;
}
}
pub fn direction(&self) -> Direction {
self.dir.try_peek().map(|dir| *dir).unwrap_or_default()
}
pub fn set_direction(&mut self, dir: Direction) {
let changed = match self.dir.try_peek() {
Ok(current) => *current != dir,
Err(error) => {
trace_registry_failure("set_direction", "dir", None, None, &error);
return;
}
};
if changed {
match self.dir.try_write() {
Ok(mut current) => *current = dir,
Err(error) => trace_registry_failure("set_direction", "dir", None, None, &error),
}
}
}
pub fn register(&mut self, record: ZoneRecord<T>) -> ZoneRegistration {
self.register_with_policy(record, ZonePolicy::default())
}
pub(crate) fn register_with_policy(
&mut self,
record: ZoneRecord<T>,
policy: ZonePolicy<T>,
) -> ZoneRegistration {
let registration = ZoneRegistration {
id: record.id,
generation: NEXT_ZONE_REGISTRATION.fetch_add(1, Ordering::Relaxed),
};
let mut zones = match self.zones.try_write() {
Ok(zones) => zones,
Err(error) => {
trace_registry_failure(
"register",
"zones",
Some(registration.id),
Some(registration.generation),
&error,
);
return registration;
}
};
let mut registrations = match self.registrations.try_write() {
Ok(registrations) => registrations,
Err(error) => {
trace_registry_failure(
"register",
"registrations",
Some(registration.id),
Some(registration.generation),
&error,
);
return registration;
}
};
let mut policies = match self.policies.try_write() {
Ok(policies) => policies,
Err(error) => {
trace_registry_failure(
"register",
"policies",
Some(registration.id),
Some(registration.generation),
&error,
);
return registration;
}
};
if let Some(existing) = zones.iter_mut().find(|z| z.id == record.id) {
*existing = record;
} else {
zones.push(record);
}
if let Some(existing) = registrations
.iter_mut()
.find(|(id, _)| *id == registration.id)
{
existing.1 = registration.generation;
} else {
registrations.push((registration.id, registration.generation));
}
policies.retain(|(candidate, _)| candidate.id != registration.id);
policies.push((registration, policy));
drop(policies);
drop(registrations);
drop(zones);
self.bump_mount_revision();
registration
}
pub fn sync_label(&mut self, id: ZoneId, label: Option<String>) {
let needs = match self.zones.try_peek() {
Ok(zones) => zones.iter().any(|z| z.id == id && z.label != label),
Err(error) => {
trace_registry_failure("sync_label", "zones", Some(id), None, &error);
return;
}
};
if needs {
match self.zones.try_write() {
Ok(mut zones) => {
if let Some(z) = zones.iter_mut().find(|z| z.id == id) {
z.label = label;
}
}
Err(error) => trace_registry_failure("sync_label", "zones", Some(id), None, &error),
}
}
}
pub(crate) fn sync_parent(&mut self, registration: ZoneRegistration, parent: Option<ZoneId>) {
if !self.is_current(registration, "sync_parent") {
return;
}
let needs = self.zones.try_peek().is_ok_and(|zones| {
zones
.iter()
.any(|zone| zone.id == registration.id && zone.parent != parent)
});
if !needs {
return;
}
match self.zones.try_write() {
Ok(mut zones) => {
if let Some(zone) = zones.iter_mut().find(|zone| zone.id == registration.id) {
zone.parent = parent;
}
}
Err(error) => trace_registry_failure(
"sync_parent",
"zones",
Some(registration.id),
Some(registration.generation),
&error,
),
}
}
pub(crate) fn sync_policy(
&mut self,
registration: ZoneRegistration,
accepts: Option<Callback<T, bool>>,
policy: ZonePolicy<T>,
) {
if !self.is_current(registration, "sync_policy") {
return;
}
let mut zones = match self.zones.try_write() {
Ok(zones) => zones,
Err(error) => {
trace_registry_failure(
"sync_policy",
"zones",
Some(registration.id),
Some(registration.generation),
&error,
);
return;
}
};
let mut policies = match self.policies.try_write() {
Ok(policies) => policies,
Err(error) => {
trace_registry_failure(
"sync_policy",
"policies",
Some(registration.id),
Some(registration.generation),
&error,
);
return;
}
};
if let Some(zone) = zones.iter_mut().find(|zone| zone.id == registration.id) {
zone.accepts = accepts;
}
if let Some((_, current)) = policies
.iter_mut()
.find(|(candidate, _)| *candidate == registration)
{
*current = policy;
}
}
pub fn unregister(&mut self, id: ZoneId) {
let mut zones = match self.zones.try_write() {
Ok(zones) => zones,
Err(error) => {
trace_registry_failure("unregister", "zones", Some(id), None, &error);
return;
}
};
let mut registrations = match self.registrations.try_write() {
Ok(registrations) => registrations,
Err(error) => {
trace_registry_failure("unregister", "registrations", Some(id), None, &error);
return;
}
};
let mut policies = match self.policies.try_write() {
Ok(policies) => policies,
Err(error) => {
trace_registry_failure("unregister", "policies", Some(id), None, &error);
return;
}
};
let old_len = zones.len();
zones.retain(|z| z.id != id);
let removed = zones.len() != old_len;
registrations.retain(|(registered_id, _)| *registered_id != id);
policies.retain(|(registration, _)| registration.id != id);
drop(policies);
drop(registrations);
drop(zones);
if removed {
self.bump_mount_revision();
}
}
pub fn unregister_registration(&mut self, registration: ZoneRegistration) {
let current = self
.registrations
.try_read()
.ok()
.and_then(|registrations| {
registrations
.iter()
.find(|(id, _)| *id == registration.id)
.copied()
});
if current == Some((registration.id, registration.generation)) {
self.unregister(registration.id);
}
}
pub fn set_mounted(&mut self, registration: ZoneRegistration, mounted: Rc<MountedData>) {
if !self.is_current(registration, "set_mounted") {
return;
}
let mut changed = false;
match self.zones.try_write() {
Ok(mut zones) => {
if let Some(zone) = zones.iter_mut().find(|z| z.id == registration.id) {
zone.mounted = Some(mounted);
changed = true;
}
}
Err(error) => {
trace_registry_failure(
"set_mounted",
"zones",
Some(registration.id),
Some(registration.generation),
&error,
);
}
}
if changed {
self.bump_mount_revision();
}
}
pub fn set_rect_if_present(&mut self, registration: ZoneRegistration, rect: Rect) {
if !self.is_current(registration, "set_rect_if_present") {
return;
}
match self.zones.try_write() {
Ok(mut zones) => {
if let Some(zone) = zones.iter_mut().find(|z| z.id == registration.id) {
zone.rect = Some(rect);
}
}
Err(error) => {
trace_registry_failure(
"set_rect_if_present",
"zones",
Some(registration.id),
Some(registration.generation),
&error,
);
}
}
}
pub fn set_rect(&mut self, id: ZoneId, rect: Rect) {
if let Some(registration) = self.current_registration(id, "set_rect") {
self.set_rect_if_present(registration, rect);
}
}
pub fn get(&self, id: ZoneId) -> Option<ZoneRecord<T>> {
self.zones
.try_peek()
.ok()?
.iter()
.find(|z| z.id == id)
.cloned()
}
pub fn cached_rect(&self, id: ZoneId) -> Option<Rect> {
self.zones
.try_peek()
.ok()?
.iter()
.find(|z| z.id == id)
.and_then(ZoneRecord::cached_rect)
}
pub fn mounted_handle(&self, id: ZoneId) -> Option<Rc<MountedData>> {
self.zones
.try_peek()
.ok()?
.iter()
.find(|z| z.id == id)
.and_then(ZoneRecord::mounted_handle)
}
pub fn records(&self) -> Vec<ZoneRecord<T>> {
let records = self
.zones
.try_read()
.map(|zones| zones.to_vec())
.unwrap_or_default();
records
}
fn snapshot(&self) -> Vec<RegisteredZone<T>> {
let records = self
.zones
.try_peek()
.map(|zones| zones.to_vec())
.unwrap_or_default();
let registrations = self
.registrations
.try_peek()
.map(|registrations| registrations.to_vec())
.unwrap_or_default();
let policies = self
.policies
.try_peek()
.map(|policies| policies.to_vec())
.unwrap_or_default();
records
.into_iter()
.map(|record| {
let registration = registrations.iter().find(|(id, _)| *id == record.id).map(
|(id, generation)| ZoneRegistration {
id: *id,
generation: *generation,
},
);
let policy = registration
.and_then(|registration| {
policies
.iter()
.find(|(candidate, _)| *candidate == registration)
.map(|(_, policy)| policy.clone())
})
.unwrap_or_default();
RegisteredZone { record, policy }
})
.collect()
}
pub fn contains(&self, id: ZoneId) -> bool {
self.zones
.try_peek()
.is_ok_and(|zones| zones.iter().any(|z| z.id == id))
}
pub fn ascend(&self, current: ZoneId) -> Option<ZoneId> {
self.parent_of(current).filter(|pid| self.contains(*pid))
}
pub fn acceptable(&self, payload: &T) -> Vec<ZoneRecord<T>> {
self.snapshot()
.into_iter()
.filter(|zone| zone.record.accepts_payload(payload))
.map(|zone| zone.record)
.collect()
}
pub fn acceptable_query(&self, query: &DropQuery<T>) -> Vec<ZoneRecord<T>> {
self.snapshot()
.into_iter()
.filter(|zone| zone.negotiate(query).is_some())
.map(|zone| zone.record)
.collect()
}
pub(crate) fn negotiate_zone(
&self,
id: ZoneId,
query: &DropQuery<T>,
) -> Option<NegotiatedZone<T>> {
let zone = self
.snapshot()
.into_iter()
.find(|zone| zone.record.id == id)?;
let effect = zone.negotiate(query)?;
Some(NegotiatedZone {
record: zone.record,
effect,
edge: zone.policy.edge,
})
}
pub fn step_zone(&self, current: Option<ZoneId>, payload: &T, step: isize) -> Option<ZoneId> {
let mut zones = self.acceptable(payload);
spatial_sort(&mut zones, self.direction());
let current_ix = current.and_then(|c| zones.iter().position(|z| z.id == c));
cycle(zones.len(), current_ix, step).map(|ix| zones[ix].id)
}
pub fn step_zone_query(
&self,
current: Option<ZoneId>,
query: &DropQuery<T>,
step: isize,
) -> Option<ZoneId> {
let mut zones = self.acceptable_query(query);
spatial_sort(&mut zones, self.direction());
let current_ix = current.and_then(|candidate| zones.iter().position(|z| z.id == candidate));
cycle(zones.len(), current_ix, step).map(|index| zones[index].id)
}
pub fn parent_of(&self, id: ZoneId) -> Option<ZoneId> {
self.zones
.try_peek()
.ok()?
.iter()
.find(|z| z.id == id)?
.parent
}
pub fn children_of(&self, parent: Option<ZoneId>, payload: &T) -> Vec<ZoneRecord<T>> {
let mut zones: Vec<_> = self
.snapshot()
.into_iter()
.filter(|zone| zone.record.parent == parent && zone.record.accepts_payload(payload))
.map(|zone| zone.record)
.collect();
spatial_sort(&mut zones, self.direction());
zones
}
pub fn children_of_query(
&self,
parent: Option<ZoneId>,
query: &DropQuery<T>,
) -> Vec<ZoneRecord<T>> {
let mut zones: Vec<_> = self
.snapshot()
.into_iter()
.filter(|zone| zone.record.parent == parent && zone.negotiate(query).is_some())
.map(|zone| zone.record)
.collect();
spatial_sort(&mut zones, self.direction());
zones
}
pub fn step_sibling(
&self,
current: Option<ZoneId>,
payload: &T,
step: isize,
) -> Option<ZoneId> {
let parent = current.and_then(|c| self.parent_of(c));
let siblings = self.children_of(parent, payload);
let current_ix = current.and_then(|c| siblings.iter().position(|z| z.id == c));
cycle(siblings.len(), current_ix, step).map(|ix| siblings[ix].id)
}
pub fn step_sibling_query(
&self,
current: Option<ZoneId>,
query: &DropQuery<T>,
step: isize,
) -> Option<ZoneId> {
let parent = current.and_then(|candidate| self.parent_of(candidate));
let siblings = self.children_of_query(parent, query);
let current_ix =
current.and_then(|candidate| siblings.iter().position(|zone| zone.id == candidate));
cycle(siblings.len(), current_ix, step).map(|index| siblings[index].id)
}
pub fn first_child(&self, id: ZoneId, payload: &T) -> Option<ZoneId> {
self.children_of(Some(id), payload).first().map(|z| z.id)
}
pub fn first_child_query(&self, id: ZoneId, query: &DropQuery<T>) -> Option<ZoneId> {
self.children_of_query(Some(id), query)
.first()
.map(|zone| zone.id)
}
pub fn hit_test(&self, point: Point) -> Option<ZoneId> {
self.zones
.try_peek()
.ok()?
.iter()
.rev()
.find(|z| z.cached_rect().map(|r| r.contains(point)).unwrap_or(false))
.map(|z| z.id)
}
pub fn hit_test_closest(&self, point: Point, payload: &T, max_distance: f64) -> Option<ZoneId> {
let zones = self.snapshot();
let mut best: Option<(ZoneId, f64)> = None;
for z in zones.iter().rev() {
if !z.record.accepts_payload(payload) {
continue;
}
let Some(r) = z.record.cached_rect() else {
continue;
};
if r.contains(point) {
return Some(z.record.id);
}
let dx = (r.x - point.x).max(point.x - (r.x + r.width)).max(0.0);
let dy = (r.y - point.y).max(point.y - (r.y + r.height)).max(0.0);
let d = (dx * dx + dy * dy).sqrt();
if d <= max_distance && best.map(|(_, bd)| d <= bd).unwrap_or(true) {
best = Some((z.record.id, d));
}
}
best.map(|(id, _)| id)
}
pub fn resolve(
&self,
query: &DropQuery<T>,
point: Point,
active_rect: Option<Rect>,
max_distance: f64,
) -> Option<(ZoneId, DropEffect)> {
let zones = self.snapshot();
let accepted: Vec<_> = zones
.iter()
.enumerate()
.filter_map(|(order, zone)| {
let effect = zone.negotiate(query)?;
Some((
ZoneCandidate {
id: zone.record.id,
rect: zone.record.cached_rect()?,
order,
},
effect,
))
})
.collect();
let candidates = accepted.iter().map(|(candidate, _)| *candidate).collect();
let policy = self.release_policy();
let max_distance = max_distance.max(0.0);
let ranked = match policy.collision {
CollisionDetector::BuiltIn(strategy) => {
rank_builtin_candidates(strategy, point, active_rect, candidates, max_distance)
}
CollisionDetector::Custom(callback) => rank_collisions(
CollisionDetector::Custom(callback),
CollisionRequest {
pointer: point,
active_rect,
payload: query.payload.clone(),
candidates,
max_distance,
},
),
};
for collision in ranked {
if let Some((candidate, effect)) = accepted
.iter()
.find(|(candidate, _)| candidate.id == collision.zone)
{
return Some((candidate.id, *effect));
}
}
None
}
pub fn resolve_hover(
&self,
query: &DropQuery<T>,
point: Point,
active_rect: Option<Rect>,
current: Option<ZoneId>,
) -> Option<(ZoneId, DropEffect)> {
if let Some(hit) = self.resolve(query, point, active_rect, 0.0) {
return Some(hit);
}
let policy = self.release_policy();
if !policy.sticky {
return None;
}
let current = current?;
let zone = self.negotiate_zone(current, query)?;
let rect = zone.record.cached_rect()?;
(crate::core::collision::point_rect_distance(point, rect) <= policy.recovery_radius)
.then_some((current, zone.effect))
}
pub async fn measure_all(&self) {
let zones = self.measurement_targets();
for (registration, mounted) in zones {
if let Ok(r) = mounted.get_client_rect().await {
let mut registry = *self;
registry.set_rect_if_present(
registration,
Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height),
);
}
}
}
pub fn refresh_rects(&self) {
self.spawn_rect_refresh(None);
}
pub(crate) fn refresh_rects_then(&self, on_complete: impl Fn() + 'static) {
self.spawn_rect_refresh(Some(Rc::new(on_complete)));
}
fn spawn_rect_refresh(&self, on_complete: Option<Rc<dyn Fn()>>) {
let targets = self.measurement_targets();
if targets.is_empty() {
if let Some(on_complete) = on_complete {
on_complete();
}
return;
}
let remaining = on_complete.map(|callback| (Rc::new(Cell::new(targets.len())), callback));
for (registration, mounted) in targets {
let mut registry = *self;
let remaining = remaining.clone();
spawn(async move {
if let Ok(r) = mounted.get_client_rect().await {
registry.set_rect_if_present(
registration,
Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height),
);
}
if let Some((remaining, on_complete)) = remaining {
let pending = remaining.get();
debug_assert!(pending > 0, "rect refresh completion counted twice");
remaining.set(pending.saturating_sub(1));
if pending == 1 {
on_complete();
}
}
});
}
}
pub(crate) fn track_mounts(&self) {
let _ = self.mount_revision.try_read();
}
fn measurement_targets(&self) -> Vec<(ZoneRegistration, Rc<MountedData>)> {
let registrations = self
.registrations
.try_peek()
.map(|registrations| registrations.clone())
.unwrap_or_default();
self.zones
.try_peek()
.map(|zones| {
zones
.iter()
.filter_map(|zone| {
let mounted = zone.mounted_handle()?;
let generation = registrations
.iter()
.find(|(id, _)| *id == zone.id)
.map(|(_, generation)| *generation)?;
Some((
ZoneRegistration {
id: zone.id,
generation,
},
mounted,
))
})
.collect()
})
.unwrap_or_default()
}
fn is_current(&self, registration: ZoneRegistration, operation: &'static str) -> bool {
match self.registrations.try_peek() {
Ok(registrations) => registrations.iter().any(|(id, generation)| {
*id == registration.id && *generation == registration.generation
}),
Err(error) => {
trace_registry_failure(
operation,
"registrations",
Some(registration.id),
Some(registration.generation),
&error,
);
false
}
}
}
fn current_registration(
&self,
id: ZoneId,
operation: &'static str,
) -> Option<ZoneRegistration> {
match self.registrations.try_peek() {
Ok(registrations) => registrations
.iter()
.find(|(registered_id, _)| *registered_id == id)
.map(|(_, generation)| ZoneRegistration {
id,
generation: *generation,
}),
Err(error) => {
trace_registry_failure(operation, "registrations", Some(id), None, &error);
None
}
}
}
fn bump_mount_revision(&mut self) {
match self.mount_revision.try_write() {
Ok(mut revision) => *revision = revision.wrapping_add(1),
Err(error) => {
trace_registry_failure("bump_mount_revision", "mount_revision", None, None, &error)
}
}
}
}
pub struct RectRefresh {
thunks: Signal<Vec<(u64, Callback<()>)>>,
}
impl Copy for RectRefresh {}
impl Clone for RectRefresh {
fn clone(&self) -> Self {
*self
}
}
impl PartialEq for RectRefresh {
fn eq(&self, other: &Self) -> bool {
self.thunks == other.thunks
}
}
impl RectRefresh {
pub fn from_signal(thunks: Signal<Vec<(u64, Callback<()>)>>) -> Self {
Self { thunks }
}
pub fn refresh_all(&self) {
for (_, thunk) in self.thunks.peek().iter() {
thunk.call(());
}
}
pub fn len(&self) -> usize {
self.thunks.peek().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub(crate) fn register(&mut self, key: u64, thunk: Callback<()>) {
let mut thunks = self.thunks.write();
if let Some(existing) = thunks.iter_mut().find(|(k, _)| *k == key) {
existing.1 = thunk;
} else {
thunks.push((key, thunk));
}
}
pub(crate) fn unregister(&mut self, key: u64) {
self.thunks.write().retain(|(k, _)| *k != key);
}
}
fn spatial_sort<T: Clone + 'static>(zones: &mut [ZoneRecord<T>], dir: Direction) {
const ROW_TOP_SLOP: f64 = 1.0;
zones.sort_by(|a, b| match (a.cached_rect(), b.cached_rect()) {
(Some(ra), Some(rb)) => ra.y.total_cmp(&rb.y),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
});
let measured = zones
.iter()
.position(|zone| zone.cached_rect().is_none())
.unwrap_or(zones.len());
let mut row_start = 0;
while row_start < measured {
let row_y = zones[row_start].cached_rect().unwrap().y;
let mut row_end = row_start + 1;
while row_end < measured {
let y = zones[row_end].cached_rect().unwrap().y;
if !row_y.is_finite() || !y.is_finite() || (y - row_y).abs() > ROW_TOP_SLOP {
break;
}
row_end += 1;
}
zones[row_start..row_end].sort_by(|a, b| {
let ax = a.cached_rect().unwrap().x;
let bx = b.cached_rect().unwrap().x;
match dir {
Direction::Ltr => ax.total_cmp(&bx),
Direction::Rtl => bx.total_cmp(&ax),
}
});
row_start = row_end;
}
}
pub(crate) fn cycle(len: usize, current: Option<usize>, step: isize) -> Option<usize> {
if len == 0 {
return None;
}
Some(match current {
None => {
if step >= 0 {
0
} else {
len - 1
}
}
Some(ix) => (ix as isize + step).rem_euclid(len as isize) as usize,
})
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use std::rc::Rc;
use dioxus::prelude::*;
use super::{
cycle, Direction, DropQuery, Point, Rect, ReleasePolicy, ZoneId, ZonePolicy, ZoneRecord,
ZoneRegistry,
};
#[test]
fn cycle_steps_and_wraps() {
assert_eq!(cycle(0, None, 1), None);
assert_eq!(cycle(3, None, 1), Some(0));
assert_eq!(cycle(3, None, -1), Some(2));
assert_eq!(cycle(3, Some(2), 1), Some(0));
assert_eq!(cycle(3, Some(0), -1), Some(2));
assert_eq!(cycle(3, Some(1), 1), Some(2));
}
fn equality_probe() -> Element {
let zones = use_signal(Vec::<ZoneRecord<u8>>::new);
let registrations = use_signal(Vec::<(ZoneId, u64)>::new);
let other_registrations = use_signal(Vec::<(ZoneId, u64)>::new);
let policies = use_signal(Vec::new);
let mount_revision = use_signal(|| 0u64);
let other_mount_revision = use_signal(|| 0u64);
let dir = use_signal(Direction::default);
let release = use_signal(ReleasePolicy::default);
let registry = ZoneRegistry {
zones,
registrations,
policies,
mount_revision,
dir,
release,
};
let copy = registry;
assert!(registry == copy, "a copied handle must compare equal");
assert!(
registry
!= ZoneRegistry {
registrations: other_registrations,
..registry
},
"registration identity is part of registry identity"
);
assert!(
registry
!= ZoneRegistry {
mount_revision: other_mount_revision,
..registry
},
"mount-revision identity is part of registry identity"
);
rsx! {}
}
#[test]
fn equality_covers_every_registry_storage_handle() {
let mut dom = VirtualDom::new(equality_probe);
dom.rebuild_in_place();
}
fn single_negotiation_probe() -> Element {
let calls = Rc::new(Cell::new(0));
let observed_calls = calls.clone();
let mut registry = ZoneRegistry::from_signal(Signal::new(Vec::<ZoneRecord<u8>>::new()));
let record = ZoneRecord::new(ZoneId(1), Callback::new(|_| {}));
let registration = registry.register_with_policy(
record,
ZonePolicy {
accepts_query: Some(Callback::new(move |_| {
calls.set(calls.get() + 1);
true
})),
..ZonePolicy::default()
},
);
registry.set_rect_if_present(registration, Rect::new(0.0, 0.0, 20.0, 20.0));
assert_eq!(
registry.resolve(&DropQuery::new(7), Point::new(10.0, 10.0), None, 0.0,),
Some((ZoneId(1), crate::core::DropEffect::Move))
);
assert_eq!(
observed_calls.get(),
1,
"one hit-test must evaluate target policy once"
);
rsx! {}
}
#[test]
fn resolution_negotiates_each_candidate_once() {
let mut dom = VirtualDom::new(single_negotiation_probe);
dom.rebuild_in_place();
}
fn reentrant_acceptance_probe() -> Element {
let mut registry = ZoneRegistry::from_signal(Signal::new(Vec::<ZoneRecord<u8>>::new()));
let mut callback_registry = registry;
let mut record = ZoneRecord::new(ZoneId(1), Callback::new(|_| {}));
record.accepts = Some(Callback::new(move |_| {
callback_registry.register(ZoneRecord::new(ZoneId(2), Callback::new(|_| {})));
true
}));
registry.register(record);
let acceptable = registry.acceptable(&7);
assert_eq!(acceptable.len(), 1);
assert!(
registry.contains(ZoneId(2)),
"acceptance callbacks must be able to mutate the registry"
);
rsx! {}
}
#[test]
fn acceptance_callbacks_run_without_a_registry_borrow() {
let mut dom = VirtualDom::new(reentrant_acceptance_probe);
dom.rebuild_in_place();
}
fn structural_borrow_probe() -> Element {
let zones = use_signal(Vec::<ZoneRecord<u8>>::new);
let mut registry = ZoneRegistry::from_signal(zones);
let record = |id: u64| ZoneRecord {
id: ZoneId(id),
parent: None,
label: None,
on_drop: Callback::new(|_| {}),
accepts: None,
mounted: None,
rect: Some(Rect::new(0.0, 0.0, 10.0, 10.0)),
};
registry.register(record(1));
{
let zones = registry.zones;
let _zones = zones.read();
registry.register(record(2));
}
assert!(registry.get(ZoneId(2)).is_none());
assert!(registry.current_registration(ZoneId(2), "test").is_none());
{
let registrations = registry.registrations;
let _registrations = registrations.read();
registry.register(record(3));
}
assert!(registry.get(ZoneId(3)).is_none());
assert!(registry.current_registration(ZoneId(3), "test").is_none());
{
let registrations = registry.registrations;
let _registrations = registrations.read();
registry.unregister(ZoneId(1));
}
assert!(registry.get(ZoneId(1)).is_some());
assert!(registry.current_registration(ZoneId(1), "test").is_some());
rsx! {}
}
#[test]
fn structural_borrow_failures_cannot_split_registry_state() {
let mut dom = VirtualDom::new(structural_borrow_probe);
dom.rebuild_in_place();
}
}