1use std::sync::atomic::{AtomicU64, Ordering};
5
6use dioxus::prelude::*;
7
8use crate::core::types::Point;
9
10static NEXT_WINDOW_KEY: AtomicU64 = AtomicU64::new(1);
11static NEXT_FOCUS_STAMP: AtomicU64 = AtomicU64::new(1);
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub struct WindowKey(pub u64);
17
18impl WindowKey {
19 pub fn auto() -> Self {
21 Self(NEXT_WINDOW_KEY.fetch_add(1, Ordering::Relaxed))
22 }
23}
24
25pub(crate) fn client_to_global(client: Point, origin: Point, scale: f64) -> Point {
29 Point::new(origin.x + client.x * scale, origin.y + client.y * scale)
30}
31
32pub(crate) fn global_to_client(global: Point, origin: Point, scale: f64) -> Point {
34 let s = if scale > 0.0 { scale } else { 1.0 };
35 Point::new((global.x - origin.x) / s, (global.y - origin.y) / s)
36}
37
38pub(crate) fn window_contains(global: Point, origin: Point, size: (f64, f64)) -> bool {
41 global.x >= origin.x
42 && global.x <= origin.x + size.0
43 && global.y >= origin.y
44 && global.y <= origin.y + size.1
45}
46
47pub struct WindowGeometry {
53 origin: Signal<Option<Point>>,
55 size: Signal<Option<(f64, f64)>>,
57 scale: Signal<f64>,
59 focused: Signal<u64>,
63 eligible: Signal<bool>,
66}
67
68impl Copy for WindowGeometry {}
69impl Clone for WindowGeometry {
70 fn clone(&self) -> Self {
71 *self
72 }
73}
74impl PartialEq for WindowGeometry {
75 fn eq(&self, other: &Self) -> bool {
76 self.origin == other.origin
77 }
78}
79
80impl Default for WindowGeometry {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86impl WindowGeometry {
87 pub fn new() -> Self {
89 Self {
90 origin: Signal::new(None),
91 size: Signal::new(None),
92 scale: Signal::new(1.0),
93 focused: Signal::new(0),
94 eligible: Signal::new(true),
97 }
98 }
99
100 pub fn set(&self, origin: Point, size: (f64, f64), scale: f64) {
105 let (mut o, mut sz, mut sc) = (self.origin, self.size, self.scale);
109 if matches!(o.try_peek().as_deref(), Ok(v) if *v != Some(origin)) {
110 if let Ok(mut w) = o.try_write() {
111 *w = Some(origin);
112 }
113 }
114 if matches!(sz.try_peek().as_deref(), Ok(v) if *v != Some(size)) {
115 if let Ok(mut w) = sz.try_write() {
116 *w = Some(size);
117 }
118 }
119 if matches!(sc.try_peek().as_deref(), Ok(v) if *v != scale) {
120 if let Ok(mut w) = sc.try_write() {
121 *w = scale;
122 }
123 }
124 }
125
126 pub fn clear(&self) {
129 let (mut o, mut sz) = (self.origin, self.size);
130 if matches!(o.try_peek().as_deref(), Ok(Some(_))) {
131 if let Ok(mut w) = o.try_write() {
132 *w = None;
133 }
134 }
135 if matches!(sz.try_peek().as_deref(), Ok(Some(_))) {
136 if let Ok(mut w) = sz.try_write() {
137 *w = None;
138 }
139 }
140 }
141
142 pub fn set_eligible(&self, eligible: bool) {
145 let mut value = self.eligible;
146 if matches!(value.try_peek().as_deref(), Ok(current) if *current != eligible) {
147 if let Ok(mut writer) = value.try_write() {
148 *writer = eligible;
149 }
150 }
151 }
152
153 pub fn eligible(&self) -> bool {
156 self.eligible
157 .try_read()
158 .map(|value| *value)
159 .unwrap_or(false)
160 }
161
162 pub fn mark_focused(&self) {
164 let mut f = self.focused;
165 if let Ok(mut w) = f.try_write() {
166 *w = NEXT_FOCUS_STAMP.fetch_add(1, Ordering::Relaxed);
167 };
168 }
169
170 pub fn live(&self) -> bool {
183 let has_origin = matches!(self.origin.try_read().as_deref(), Ok(Some(_)));
186 let has_size = matches!(self.size.try_read().as_deref(), Ok(Some(_)));
187 let eligible = self
188 .eligible
189 .try_read()
190 .map(|value| *value)
191 .unwrap_or(false);
192 has_origin && has_size && eligible
193 }
194
195 fn origin_scale(&self) -> Option<(Point, f64)> {
196 let origin = (*self.origin.try_peek().ok()?)?;
197 let scale = self.scale.try_peek().map(|s| *s).unwrap_or(1.0);
198 Some((origin, scale))
199 }
200
201 pub fn to_global(&self, client: Point) -> Option<Point> {
204 let (origin, scale) = self.origin_scale()?;
205 Some(client_to_global(client, origin, scale))
206 }
207
208 pub fn to_client(&self, global: Point) -> Option<Point> {
211 let (origin, scale) = self.origin_scale()?;
212 Some(global_to_client(global, origin, scale))
213 }
214
215 pub fn contains_global(&self, global: Point) -> bool {
218 if !self
221 .eligible
222 .try_peek()
223 .map(|eligible| *eligible)
224 .unwrap_or(false)
225 {
226 return false;
227 }
228 let origin = self.origin.try_peek().ok().and_then(|o| *o);
229 let size = self.size.try_peek().ok().and_then(|s| *s);
230 match (origin, size) {
231 (Some(origin), Some(size)) => window_contains(global, origin, size),
232 _ => false,
233 }
234 }
235
236 pub fn scale(&self) -> f64 {
238 self.scale.try_peek().map(|s| *s).unwrap_or(1.0)
239 }
240
241 pub fn focus_stamp(&self) -> u64 {
243 self.focused.try_peek().map(|f| *f).unwrap_or(0)
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn conversions_round_trip_under_mixed_scales() {
253 for scale in [1.0, 1.5, 2.0] {
254 let origin = Point::new(1200.0, 300.0);
255 let client = Point::new(80.0, 40.5);
256 let global = client_to_global(client, origin, scale);
257 assert_eq!(
258 global,
259 Point::new(1200.0 + 80.0 * scale, 300.0 + 40.5 * scale)
260 );
261 let back = global_to_client(global, origin, scale);
262 assert!((back.x - client.x).abs() < 1e-9);
263 assert!((back.y - client.y).abs() < 1e-9);
264 }
265 }
266
267 #[test]
268 fn degenerate_scale_does_not_divide_by_zero() {
269 let p = global_to_client(Point::new(10.0, 10.0), Point::new(0.0, 0.0), 0.0);
270 assert_eq!(p, Point::new(10.0, 10.0));
271 }
272
273 #[test]
274 fn window_containment_is_edge_inclusive() {
275 let origin = Point::new(100.0, 100.0);
276 let size = (800.0, 600.0);
277 assert!(window_contains(Point::new(100.0, 100.0), origin, size));
278 assert!(window_contains(Point::new(900.0, 700.0), origin, size));
279 assert!(!window_contains(Point::new(99.9, 100.0), origin, size));
280 assert!(!window_contains(Point::new(901.0, 300.0), origin, size));
281 }
282
283 #[test]
284 fn window_keys_are_unique() {
285 let a = WindowKey::auto();
286 let b = WindowKey::auto();
287 assert_ne!(a, b);
288 }
289}