1use std::rc::Rc;
8
9use dioxus::html::MountedData;
10use dioxus::prelude::*;
11
12use super::types::{DropOutcome, Point, Rect, ZoneId};
13
14pub struct ZoneRecord<T: Clone + 'static> {
16 pub id: ZoneId,
17 pub parent: Option<ZoneId>,
20 pub label: Option<String>,
22 pub on_drop: Callback<DropOutcome<T>>,
24 pub accepts: Option<Callback<T, bool>>,
26 pub mounted: Signal<Option<Rc<MountedData>>>,
28 pub rect: Signal<Option<Rect>>,
30}
31
32impl<T: Clone + 'static> Clone for ZoneRecord<T> {
33 fn clone(&self) -> Self {
34 Self {
35 id: self.id,
36 parent: self.parent,
37 label: self.label.clone(),
38 on_drop: self.on_drop,
39 accepts: self.accepts,
40 mounted: self.mounted,
41 rect: self.rect,
42 }
43 }
44}
45
46impl<T: Clone + 'static> ZoneRecord<T> {
47 pub fn accepts_payload(&self, payload: &T) -> bool {
49 match self.accepts {
50 Some(cb) => cb.call(payload.clone()),
51 None => true,
52 }
53 }
54}
55
56pub struct ZoneRegistry<T: Clone + 'static> {
58 zones: Signal<Vec<ZoneRecord<T>>>,
59}
60
61impl<T: Clone + 'static> Copy for ZoneRegistry<T> {}
62impl<T: Clone + 'static> Clone for ZoneRegistry<T> {
63 fn clone(&self) -> Self {
64 *self
65 }
66}
67impl<T: Clone + 'static> PartialEq for ZoneRegistry<T> {
68 fn eq(&self, other: &Self) -> bool {
69 self.zones == other.zones
70 }
71}
72
73impl<T: Clone + 'static> ZoneRegistry<T> {
74 pub fn from_signal(zones: Signal<Vec<ZoneRecord<T>>>) -> Self {
76 Self { zones }
77 }
78
79 pub fn register(&mut self, record: ZoneRecord<T>) {
81 let mut zones = self.zones.write();
82 if let Some(existing) = zones.iter_mut().find(|z| z.id == record.id) {
83 *existing = record;
84 } else {
85 zones.push(record);
86 }
87 }
88
89 pub fn sync_label(&mut self, id: ZoneId, label: Option<String>) {
91 let needs = self
92 .zones
93 .peek()
94 .iter()
95 .any(|z| z.id == id && z.label != label);
96 if needs {
97 if let Some(z) = self.zones.write().iter_mut().find(|z| z.id == id) {
98 z.label = label;
99 }
100 }
101 }
102
103 pub fn unregister(&mut self, id: ZoneId) {
105 self.zones.write().retain(|z| z.id != id);
106 }
107
108 pub fn get(&self, id: ZoneId) -> Option<ZoneRecord<T>> {
110 self.zones.peek().iter().find(|z| z.id == id).cloned()
111 }
112
113 pub fn acceptable(&self, payload: &T) -> Vec<ZoneRecord<T>> {
115 self.zones
116 .peek()
117 .iter()
118 .filter(|z| z.accepts_payload(payload))
119 .cloned()
120 .collect()
121 }
122
123 pub fn step_zone(&self, current: Option<ZoneId>, payload: &T, step: isize) -> Option<ZoneId> {
131 let mut zones = self.acceptable(payload);
132 spatial_sort(&mut zones);
133 let current_ix = current.and_then(|c| zones.iter().position(|z| z.id == c));
134 cycle(zones.len(), current_ix, step).map(|ix| zones[ix].id)
135 }
136
137 pub fn parent_of(&self, id: ZoneId) -> Option<ZoneId> {
139 self.zones.peek().iter().find(|z| z.id == id)?.parent
140 }
141
142 pub fn children_of(&self, parent: Option<ZoneId>, payload: &T) -> Vec<ZoneRecord<T>> {
146 let mut zones: Vec<_> = self
147 .zones
148 .peek()
149 .iter()
150 .filter(|z| z.parent == parent && z.accepts_payload(payload))
151 .cloned()
152 .collect();
153 spatial_sort(&mut zones);
154 zones
155 }
156
157 pub fn step_sibling(
160 &self,
161 current: Option<ZoneId>,
162 payload: &T,
163 step: isize,
164 ) -> Option<ZoneId> {
165 let parent = current.and_then(|c| self.parent_of(c));
166 let siblings = self.children_of(parent, payload);
167 let current_ix = current.and_then(|c| siblings.iter().position(|z| z.id == c));
168 cycle(siblings.len(), current_ix, step).map(|ix| siblings[ix].id)
169 }
170
171 pub fn first_child(&self, id: ZoneId, payload: &T) -> Option<ZoneId> {
173 self.children_of(Some(id), payload).first().map(|z| z.id)
174 }
175
176 pub fn hit_test(&self, point: Point) -> Option<ZoneId> {
180 self.zones
181 .peek()
182 .iter()
183 .rev()
184 .find(|z| (*z.rect.peek()).map(|r| r.contains(point)).unwrap_or(false))
185 .map(|z| z.id)
186 }
187
188 pub fn hit_test_closest(&self, point: Point, payload: &T, max_distance: f64) -> Option<ZoneId> {
196 if let Some(hit) = self
197 .zones
198 .peek()
199 .iter()
200 .rev()
201 .find(|z| {
202 z.accepts_payload(payload)
203 && (*z.rect.peek()).map(|r| r.contains(point)).unwrap_or(false)
204 })
205 .map(|z| z.id)
206 {
207 return Some(hit);
208 }
209 let mut best: Option<(ZoneId, f64)> = None;
210 for z in self.acceptable(payload) {
211 let Some(r) = *z.rect.peek() else { continue };
212 let c = r.center();
213 let (dx, dy) = (c.x - point.x, c.y - point.y);
214 let d = (dx * dx + dy * dy).sqrt();
215 if d <= max_distance && best.map(|(_, bd)| d < bd).unwrap_or(true) {
216 best = Some((z.id, d));
217 }
218 }
219 best.map(|(id, _)| id)
220 }
221
222 pub async fn measure_all(&self) {
227 let zones: Vec<_> = self
228 .zones
229 .peek()
230 .iter()
231 .map(|z| (z.mounted.peek().clone(), z.rect))
232 .collect();
233 for (mounted, mut rect) in zones {
234 if let Some(m) = mounted {
235 if let Ok(r) = m.get_client_rect().await {
236 rect.set(Some(Rect::new(
237 r.origin.x,
238 r.origin.y,
239 r.size.width,
240 r.size.height,
241 )));
242 }
243 }
244 }
245 }
246
247 pub fn refresh_rects(&self) {
249 for zone in self.zones.peek().iter() {
250 let mounted = zone.mounted.peek().clone();
251 let mut rect = zone.rect;
252 if let Some(m) = mounted {
253 spawn(async move {
254 if let Ok(r) = m.get_client_rect().await {
255 rect.set(Some(Rect::new(
256 r.origin.x,
257 r.origin.y,
258 r.size.width,
259 r.size.height,
260 )));
261 }
262 });
263 }
264 }
265 }
266}
267
268fn spatial_sort<T: Clone + 'static>(zones: &mut [ZoneRecord<T>]) {
271 zones.sort_by(|a, b| match (*a.rect.peek(), *b.rect.peek()) {
272 (Some(ra), Some(rb)) => (ra.y, ra.x)
273 .partial_cmp(&(rb.y, rb.x))
274 .unwrap_or(std::cmp::Ordering::Equal),
275 (Some(_), None) => std::cmp::Ordering::Less,
276 (None, Some(_)) => std::cmp::Ordering::Greater,
277 (None, None) => std::cmp::Ordering::Equal,
278 });
279}
280
281pub(crate) fn cycle(len: usize, current: Option<usize>, step: isize) -> Option<usize> {
284 if len == 0 {
285 return None;
286 }
287 Some(match current {
288 None => {
289 if step >= 0 {
290 0
291 } else {
292 len - 1
293 }
294 }
295 Some(ix) => (ix as isize + step).rem_euclid(len as isize) as usize,
296 })
297}
298
299#[cfg(test)]
300mod tests {
301 use super::cycle;
302
303 #[test]
304 fn cycle_steps_and_wraps() {
305 assert_eq!(cycle(0, None, 1), None);
306 assert_eq!(cycle(3, None, 1), Some(0));
307 assert_eq!(cycle(3, None, -1), Some(2));
308 assert_eq!(cycle(3, Some(2), 1), Some(0));
309 assert_eq!(cycle(3, Some(0), -1), Some(2));
310 assert_eq!(cycle(3, Some(1), 1), Some(2));
311 }
312}