use std::rc::Rc;
use dioxus::html::MountedData;
use dioxus::prelude::*;
use super::types::{Direction, DropOutcome, Point, Rect, ZoneId};
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: Signal<Option<Rc<MountedData>>>,
pub rect: Signal<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,
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 struct ZoneRegistry<T: Clone + 'static> {
zones: Signal<Vec<ZoneRecord<T>>>,
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,
dir: Signal::new(Direction::default()),
}
}
pub fn direction(&self) -> Direction {
*self.dir.peek()
}
pub fn set_direction(&mut self, dir: Direction) {
if *self.dir.peek() != dir {
self.dir.set(dir);
}
}
pub fn register(&mut self, record: ZoneRecord<T>) {
let mut zones = self.zones.write();
if let Some(existing) = zones.iter_mut().find(|z| z.id == record.id) {
*existing = record;
} else {
zones.push(record);
}
}
pub fn sync_label(&mut self, id: ZoneId, label: Option<String>) {
let needs = self
.zones
.peek()
.iter()
.any(|z| z.id == id && z.label != label);
if needs {
if let Some(z) = self.zones.write().iter_mut().find(|z| z.id == id) {
z.label = label;
}
}
}
pub fn unregister(&mut self, id: ZoneId) {
self.zones.write().retain(|z| z.id != id);
}
pub fn get(&self, id: ZoneId) -> Option<ZoneRecord<T>> {
self.zones.peek().iter().find(|z| z.id == id).cloned()
}
pub fn contains(&self, id: ZoneId) -> bool {
self.zones.peek().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
.peek()
.iter()
.filter(|z| z.accepts_payload(payload))
.cloned()
.collect()
}
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.peek().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
.peek()
.iter()
.filter(|z| z.parent == parent && z.accepts_payload(payload))
.cloned()
.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 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
.peek()
.iter()
.rev()
.find(|z| (*z.rect.peek()).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
.peek()
.iter()
.rev()
.find(|z| {
z.accepts_payload(payload)
&& (*z.rect.peek()).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.rect.peek() else { continue };
let c = r.center();
let (dx, dy) = (c.x - point.x, c.y - point.y);
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: Vec<_> = self
.zones
.peek()
.iter()
.map(|z| (z.mounted.peek().clone(), z.rect))
.collect();
for (mounted, mut rect) in zones {
if let Some(m) = mounted {
if let Ok(r) = m.get_client_rect().await {
rect.set(Some(Rect::new(
r.origin.x,
r.origin.y,
r.size.width,
r.size.height,
)));
}
}
}
}
pub fn refresh_rects(&self) {
for zone in self.zones.peek().iter() {
let mounted = zone.mounted.peek().clone();
let mut rect = zone.rect;
if let Some(m) = mounted {
spawn(async move {
if let Ok(r) = m.get_client_rect().await {
rect.set(Some(Rect::new(
r.origin.x,
r.origin.y,
r.size.width,
r.size.height,
)));
}
});
}
}
}
}
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.rect.peek(), *b.rect.peek()) {
(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));
}
}