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