use std::rc::Rc;
use std::sync::atomic::{AtomicU64, Ordering};
use dioxus::html::MountedData;
use dioxus::prelude::*;
use super::types::{Direction, DropOutcome, Point, Rect, ZoneId};
static NEXT_ZONE_REGISTRATION: AtomicU64 = AtomicU64::new(1);
#[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 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()
}
}
pub struct ZoneRegistry<T: Clone + 'static> {
zones: Signal<Vec<ZoneRecord<T>>>,
registrations: Signal<Vec<(ZoneId, u64)>>,
mount_revision: Signal<u64>,
dir: Signal<Direction>,
}
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.dir == other.dir
}
}
impl<T: Clone + 'static> ZoneRegistry<T> {
pub fn from_signal(zones: Signal<Vec<ZoneRecord<T>>>) -> Self {
Self {
zones,
registrations: Signal::new(Vec::new()),
mount_revision: Signal::new(0),
dir: Signal::new(Direction::default()),
}
}
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 = self.dir.try_peek().map(|current| *current != dir);
if changed == Ok(true) {
if let Ok(mut current) = self.dir.try_write() {
*current = dir;
}
}
}
pub fn register(&mut self, record: ZoneRecord<T>) -> ZoneRegistration {
let registration = ZoneRegistration {
id: record.id,
generation: NEXT_ZONE_REGISTRATION.fetch_add(1, Ordering::Relaxed),
};
if let Ok(mut zones) = self.zones.try_write() {
if let Some(existing) = zones.iter_mut().find(|z| z.id == record.id) {
*existing = record;
} else {
zones.push(record);
}
}
if let Ok(mut registrations) = self.registrations.try_write() {
if let Some(existing) = registrations
.iter_mut()
.find(|(id, _)| *id == registration.id)
{
existing.1 = registration.generation;
} else {
registrations.push((registration.id, registration.generation));
}
}
self.bump_mount_revision();
registration
}
pub fn sync_label(&mut self, id: ZoneId, label: Option<String>) {
let needs = self
.zones
.try_peek()
.map(|zones| zones.iter().any(|z| z.id == id && z.label != label))
.unwrap_or(false);
if needs {
if let Ok(mut zones) = self.zones.try_write() {
if let Some(z) = zones.iter_mut().find(|z| z.id == id) {
z.label = label;
}
}
}
}
pub fn unregister(&mut self, id: ZoneId) {
let removed = self.zones.try_write().is_ok_and(|mut zones| {
let old_len = zones.len();
zones.retain(|z| z.id != id);
zones.len() != old_len
});
if let Ok(mut registrations) = self.registrations.try_write() {
registrations.retain(|(registered_id, _)| *registered_id != id);
}
if removed {
self.bump_mount_revision();
}
}
pub fn set_mounted(&mut self, registration: ZoneRegistration, mounted: Rc<MountedData>) {
if !self.is_current(registration) {
return;
}
let mut changed = false;
if let Ok(mut zones) = self.zones.try_write() {
if let Some(zone) = zones.iter_mut().find(|z| z.id == registration.id) {
zone.mounted = Some(mounted);
changed = true;
}
}
if changed {
self.bump_mount_revision();
}
}
pub fn set_rect_if_present(&mut self, registration: ZoneRegistration, rect: Rect) {
if !self.is_current(registration) {
return;
}
if let Ok(mut zones) = self.zones.try_write() {
if let Some(zone) = zones.iter_mut().find(|z| z.id == registration.id) {
zone.rect = Some(rect);
}
}
}
pub fn set_rect(&mut self, id: ZoneId, rect: Rect) {
if let Some(registration) = self.current_registration(id) {
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>> {
self.zones
.try_read()
.map(|zones| zones.to_vec())
.unwrap_or_default()
}
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.zones
.try_peek()
.map(|zones| {
zones
.iter()
.filter(|z| z.accepts_payload(payload))
.cloned()
.collect()
})
.unwrap_or_default()
}
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 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
.zones
.try_peek()
.map(|zones| {
zones
.iter()
.filter(|z| z.parent == parent && z.accepts_payload(payload))
.cloned()
.collect()
})
.unwrap_or_default();
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 first_child(&self, id: ZoneId, payload: &T) -> Option<ZoneId> {
self.children_of(Some(id), payload).first().map(|z| z.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> {
if let Some(hit) = self
.zones
.try_peek()
.ok()?
.iter()
.rev()
.find(|z| {
z.accepts_payload(payload)
&& z.cached_rect().map(|r| r.contains(point)).unwrap_or(false)
})
.map(|z| z.id)
{
return Some(hit);
}
let mut best: Option<(ZoneId, f64)> = None;
for z in self.acceptable(payload) {
let Some(r) = z.cached_rect() else { continue };
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.id, d));
}
}
best.map(|(id, _)| id)
}
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) {
for (registration, mounted) in self.measurement_targets() {
let mut registry = *self;
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),
);
}
});
}
}
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) -> bool {
self.registrations.try_peek().is_ok_and(|registrations| {
registrations.iter().any(|(id, generation)| {
*id == registration.id && *generation == registration.generation
})
})
}
fn current_registration(&self, id: ZoneId) -> Option<ZoneRegistration> {
self.registrations
.try_peek()
.ok()?
.iter()
.find(|(registered_id, _)| *registered_id == id)
.map(|(_, generation)| ZoneRegistration {
id,
generation: *generation,
})
}
fn bump_mount_revision(&mut self) {
if let Ok(mut revision) = self.mount_revision.try_write() {
*revision = revision.wrapping_add(1);
}
}
}
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) {
let reading_x = move |x: f64| match dir {
Direction::Ltr => x,
Direction::Rtl => -x,
};
zones.sort_by(|a, b| match (a.cached_rect(), b.cached_rect()) {
(Some(ra), Some(rb)) => (ra.y, reading_x(ra.x))
.partial_cmp(&(rb.y, reading_x(rb.x)))
.unwrap_or(std::cmp::Ordering::Equal),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
});
}
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 super::cycle;
#[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));
}
}