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);
14static NEXT_FOCUS_STAMP: AtomicU64 = AtomicU64::new(1);
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
19pub struct WindowKey(pub u64);
20
21impl WindowKey {
22 pub fn auto() -> Self {
24 Self(NEXT_WINDOW_KEY.fetch_add(1, Ordering::Relaxed))
25 }
26}
27
28pub(crate) fn client_to_global(client: Point, origin: Point, scale: f64) -> Point {
32 Point::new(origin.x + client.x * scale, origin.y + client.y * scale)
33}
34
35pub(crate) fn global_to_client(global: Point, origin: Point, scale: f64) -> Point {
37 let s = if scale > 0.0 { scale } else { 1.0 };
38 Point::new((global.x - origin.x) / s, (global.y - origin.y) / s)
39}
40
41pub(crate) fn window_contains(global: Point, origin: Point, size: (f64, f64)) -> bool {
44 global.x >= origin.x
45 && global.x <= origin.x + size.0
46 && global.y >= origin.y
47 && global.y <= origin.y + size.1
48}
49
50pub struct WindowGeometry {
56 origin: Signal<Option<Point>>,
58 size: Signal<Option<(f64, f64)>>,
60 scale: Signal<f64>,
62 focused: Signal<u64>,
66 eligible: Signal<bool>,
69}
70
71impl Copy for WindowGeometry {}
72impl Clone for WindowGeometry {
73 fn clone(&self) -> Self {
74 *self
75 }
76}
77impl PartialEq for WindowGeometry {
78 fn eq(&self, other: &Self) -> bool {
79 self.origin == other.origin
80 }
81}
82
83impl Default for WindowGeometry {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89impl WindowGeometry {
90 pub fn new() -> Self {
92 Self {
93 origin: Signal::new(None),
94 size: Signal::new(None),
95 scale: Signal::new(1.0),
96 focused: Signal::new(0),
97 eligible: Signal::new(true),
100 }
101 }
102
103 pub fn set(&self, origin: Point, size: (f64, f64), scale: f64) {
108 let (mut o, mut sz, mut sc) = (self.origin, self.size, self.scale);
112 if matches!(o.try_peek().as_deref(), Ok(v) if *v != Some(origin)) {
113 if let Ok(mut w) = o.try_write() {
114 *w = Some(origin);
115 }
116 }
117 if matches!(sz.try_peek().as_deref(), Ok(v) if *v != Some(size)) {
118 if let Ok(mut w) = sz.try_write() {
119 *w = Some(size);
120 }
121 }
122 if matches!(sc.try_peek().as_deref(), Ok(v) if *v != scale) {
123 if let Ok(mut w) = sc.try_write() {
124 *w = scale;
125 }
126 }
127 }
128
129 pub fn clear(&self) {
132 let (mut o, mut sz) = (self.origin, self.size);
133 if matches!(o.try_peek().as_deref(), Ok(Some(_))) {
134 if let Ok(mut w) = o.try_write() {
135 *w = None;
136 }
137 }
138 if matches!(sz.try_peek().as_deref(), Ok(Some(_))) {
139 if let Ok(mut w) = sz.try_write() {
140 *w = None;
141 }
142 }
143 }
144
145 pub fn set_eligible(&self, eligible: bool) {
148 let mut value = self.eligible;
149 if matches!(value.try_peek().as_deref(), Ok(current) if *current != eligible) {
150 if let Ok(mut writer) = value.try_write() {
151 *writer = eligible;
152 }
153 }
154 }
155
156 pub fn eligible(&self) -> bool {
159 self.eligible
160 .try_read()
161 .map(|value| *value)
162 .unwrap_or(false)
163 }
164
165 pub fn mark_focused(&self) {
167 let mut f = self.focused;
168 if let Ok(mut w) = f.try_write() {
169 *w = NEXT_FOCUS_STAMP.fetch_add(1, Ordering::Relaxed);
170 };
171 }
172
173 pub fn live(&self) -> bool {
186 let has_origin = matches!(self.origin.try_read().as_deref(), Ok(Some(_)));
189 let has_size = matches!(self.size.try_read().as_deref(), Ok(Some(_)));
190 let eligible = self
191 .eligible
192 .try_read()
193 .map(|value| *value)
194 .unwrap_or(false);
195 has_origin && has_size && eligible
196 }
197
198 fn origin_scale(&self) -> Option<(Point, f64)> {
199 let origin = (*self.origin.try_peek().ok()?)?;
200 let scale = self.scale.try_peek().map(|s| *s).unwrap_or(1.0);
201 Some((origin, scale))
202 }
203
204 pub fn to_global(&self, client: Point) -> Option<Point> {
207 let (origin, scale) = self.origin_scale()?;
208 Some(client_to_global(client, origin, scale))
209 }
210
211 pub fn to_client(&self, global: Point) -> Option<Point> {
214 let (origin, scale) = self.origin_scale()?;
215 Some(global_to_client(global, origin, scale))
216 }
217
218 pub fn contains_global(&self, global: Point) -> bool {
221 if !self
224 .eligible
225 .try_peek()
226 .map(|eligible| *eligible)
227 .unwrap_or(false)
228 {
229 return false;
230 }
231 let origin = self.origin.try_peek().ok().and_then(|o| *o);
232 let size = self.size.try_peek().ok().and_then(|s| *s);
233 match (origin, size) {
234 (Some(origin), Some(size)) => window_contains(global, origin, size),
235 _ => false,
236 }
237 }
238
239 pub fn scale(&self) -> f64 {
241 self.scale.try_peek().map(|s| *s).unwrap_or(1.0)
242 }
243
244 pub fn focus_stamp(&self) -> u64 {
246 self.focused.try_peek().map(|f| *f).unwrap_or(0)
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn conversions_round_trip_under_mixed_scales() {
256 for scale in [1.0, 1.5, 2.0] {
257 let origin = Point::new(1200.0, 300.0);
258 let client = Point::new(80.0, 40.5);
259 let global = client_to_global(client, origin, scale);
260 assert_eq!(
261 global,
262 Point::new(1200.0 + 80.0 * scale, 300.0 + 40.5 * scale)
263 );
264 let back = global_to_client(global, origin, scale);
265 assert!((back.x - client.x).abs() < 1e-9);
266 assert!((back.y - client.y).abs() < 1e-9);
267 }
268 }
269
270 #[test]
271 fn degenerate_scale_does_not_divide_by_zero() {
272 let p = global_to_client(Point::new(10.0, 10.0), Point::new(0.0, 0.0), 0.0);
273 assert_eq!(p, Point::new(10.0, 10.0));
274 }
275
276 #[test]
277 fn window_containment_is_edge_inclusive() {
278 let origin = Point::new(100.0, 100.0);
279 let size = (800.0, 600.0);
280 assert!(window_contains(Point::new(100.0, 100.0), origin, size));
281 assert!(window_contains(Point::new(900.0, 700.0), origin, size));
282 assert!(!window_contains(Point::new(99.9, 100.0), origin, size));
283 assert!(!window_contains(Point::new(901.0, 300.0), origin, size));
284 }
285
286 #[test]
287 fn window_keys_are_unique() {
288 let a = WindowKey::auto();
289 let b = WindowKey::auto();
290 assert_ne!(a, b);
291 }
292}