1use crate::schema::{WindowHeightClass, WindowSizeClass};
10use std::cell::{Cell, RefCell};
11use std::sync::Arc;
12
13#[derive(Clone, Copy, Debug, Default, PartialEq)]
15pub struct WindowSafeArea {
16 pub top: f32,
17 pub leading: f32,
18 pub trailing: f32,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq)]
28pub struct WindowSize {
29 pub width_dp: f32,
30 pub height_dp: f32,
31 pub class: WindowSizeClass,
32 pub height_class: WindowHeightClass,
33 pub safe_area: WindowSafeArea,
34}
35
36impl WindowSize {
37 pub const fn new(
38 width_dp: f32,
39 height_dp: f32,
40 class: WindowSizeClass,
41 height_class: WindowHeightClass,
42 ) -> Self {
43 Self {
44 width_dp,
45 height_dp,
46 class,
47 height_class,
48 safe_area: WindowSafeArea {
49 top: 0.0,
50 leading: 0.0,
51 trailing: 0.0,
52 },
53 }
54 }
55
56 pub const fn with_safe_area(mut self, safe_area: WindowSafeArea) -> Self {
57 self.safe_area = safe_area;
58 self
59 }
60
61 pub fn is_compact(&self) -> bool {
63 matches!(self.class, WindowSizeClass::Compact)
64 }
65
66 pub fn is_medium(&self) -> bool {
68 matches!(self.class, WindowSizeClass::Medium)
69 }
70
71 pub fn is_expanded(&self) -> bool {
73 matches!(self.class, WindowSizeClass::Expanded)
74 }
75
76 pub fn is_short(&self) -> bool {
79 matches!(self.height_class, WindowHeightClass::Compact)
80 }
81
82 pub fn is_medium_height(&self) -> bool {
84 matches!(self.height_class, WindowHeightClass::Medium)
85 }
86
87 pub fn is_tall(&self) -> bool {
89 matches!(self.height_class, WindowHeightClass::Expanded)
90 }
91}
92
93impl Default for WindowSize {
94 fn default() -> Self {
100 Self::new(
101 0.0,
102 0.0,
103 WindowSizeClass::Compact,
104 WindowHeightClass::Compact,
105 )
106 }
107}
108
109struct Subscriber {
110 id: u64,
111 notify: Arc<dyn Fn() + Send + Sync>,
112}
113
114thread_local! {
115 static NODES: RefCell<Vec<(u32, WindowSize)>> = const { RefCell::new(Vec::new()) };
120 static NODE_SUBSCRIBERS: RefCell<Vec<(u32, Subscriber)>> = const { RefCell::new(Vec::new()) };
121 static CURRENT: Cell<WindowSize> = const { Cell::new(WindowSize {
122 width_dp: 0.0,
123 height_dp: 0.0,
124 class: WindowSizeClass::Compact,
125 height_class: WindowHeightClass::Compact,
126 safe_area: WindowSafeArea { top: 0.0, leading: 0.0, trailing: 0.0 },
127 }) };
128 static SUBSCRIBERS: RefCell<Vec<Subscriber>> = const { RefCell::new(Vec::new()) };
129 static WINDOWS: RefCell<Vec<(u32, WindowSize)>> = const { RefCell::new(Vec::new()) };
133 static WINDOW_SUBSCRIBERS: RefCell<Vec<(u32, Subscriber)>> = const { RefCell::new(Vec::new()) };
134 static NEXT_SUBSCRIBER_ID: Cell<u64> = const { Cell::new(1) };
135}
136
137pub fn window_size() -> WindowSize {
144 match crate::windows::current_window_id() {
145 0 => CURRENT.with(Cell::get),
146 window => window_size_of(window),
147 }
148}
149
150pub fn window_size_of(window: u32) -> WindowSize {
153 WINDOWS.with_borrow(|windows| {
154 windows
155 .iter()
156 .find(|(id, _)| *id == window)
157 .map(|(_, size)| *size)
158 .unwrap_or_default()
159 })
160}
161
162pub(crate) fn publish_window(window: u32, size: WindowSize) -> bool {
165 let changed =
166 WINDOWS.with_borrow_mut(
167 |windows| match windows.iter_mut().find(|(id, _)| *id == window) {
168 Some(entry) => {
169 if entry.1 == size {
170 return false;
171 }
172 entry.1 = size;
173 true
174 }
175 None => {
176 windows.push((window, size));
177 true
178 }
179 },
180 );
181 if !changed {
182 return false;
183 }
184 WINDOW_SUBSCRIBERS.with_borrow(|subscribers| {
185 let mut woke = false;
186 for (id, subscriber) in subscribers {
187 if *id == window {
188 (subscriber.notify)();
189 woke = true;
190 }
191 }
192 woke
193 })
194}
195
196pub(crate) fn window_state_len() -> usize {
198 WINDOWS.with_borrow(Vec::len) + WINDOW_SUBSCRIBERS.with_borrow(Vec::len)
199}
200
201pub(crate) fn forget_window(window: u32) {
203 WINDOWS.with_borrow_mut(|windows| windows.retain(|(id, _)| *id != window));
204}
205
206pub struct WindowSubscription {
208 id: u64,
209}
210
211pub fn subscribe_window(window: u32, notify: Arc<dyn Fn() + Send + Sync>) -> WindowSubscription {
214 let id = next_subscriber_id();
215 WINDOW_SUBSCRIBERS.with_borrow_mut(|subscribers| {
216 subscribers.push((window, Subscriber { id, notify }));
217 });
218 WindowSubscription { id }
219}
220
221impl Drop for WindowSubscription {
222 fn drop(&mut self) {
223 WINDOW_SUBSCRIBERS.with_borrow_mut(|subscribers| {
224 subscribers.retain(|(_, subscriber)| subscriber.id != self.id);
225 });
226 }
227}
228
229pub(crate) fn publish(size: WindowSize) -> bool {
234 if CURRENT.with(Cell::get) == size {
235 return false;
236 }
237 CURRENT.with(|current| current.set(size));
238 SUBSCRIBERS.with_borrow(|subscribers| {
239 for subscriber in subscribers {
240 (subscriber.notify)();
241 }
242 });
243 true
244}
245
246pub fn node_size(token: u32) -> WindowSize {
248 NODES.with_borrow(|nodes| {
249 nodes
250 .iter()
251 .find(|(name, _)| *name == token)
252 .map(|(_, size)| *size)
253 .unwrap_or_default()
254 })
255}
256
257pub(crate) fn publish_node(token: u32, size: WindowSize) -> bool {
262 let changed =
263 NODES.with_borrow_mut(
264 |nodes| match nodes.iter_mut().find(|(name, _)| *name == token) {
265 Some(entry) => {
266 if entry.1 == size {
267 return false;
268 }
269 entry.1 = size;
270 true
271 }
272 None => {
273 nodes.push((token, size));
274 true
275 }
276 },
277 );
278 if !changed {
279 return false;
280 }
281 NODE_SUBSCRIBERS.with_borrow(|subscribers| {
282 let mut woke = false;
283 for (name, subscriber) in subscribers {
284 if *name == token {
285 (subscriber.notify)();
286 woke = true;
287 }
288 }
289 woke
290 })
291}
292
293#[doc(hidden)]
295pub fn reset_window_size() {
296 CURRENT.with(|current| current.set(WindowSize::default()));
297 SUBSCRIBERS.with_borrow_mut(Vec::clear);
298 NODES.with_borrow_mut(Vec::clear);
299 NODE_SUBSCRIBERS.with_borrow_mut(Vec::clear);
300 WINDOWS.with_borrow_mut(Vec::clear);
301 WINDOW_SUBSCRIBERS.with_borrow_mut(Vec::clear);
302}
303
304pub struct WindowSizeSubscription {
307 id: u64,
308}
309
310pub fn subscribe(notify: Arc<dyn Fn() + Send + Sync>) -> WindowSizeSubscription {
313 let id = next_subscriber_id();
314 SUBSCRIBERS.with_borrow_mut(|subscribers| subscribers.push(Subscriber { id, notify }));
315 WindowSizeSubscription { id }
316}
317
318fn next_subscriber_id() -> u64 {
319 NEXT_SUBSCRIBER_ID.with(|next| {
320 let id = next.get();
321 next.set(id + 1);
322 id
323 })
324}
325
326impl Drop for WindowSizeSubscription {
327 fn drop(&mut self) {
328 SUBSCRIBERS.with_borrow_mut(|subscribers| {
329 subscribers.retain(|subscriber| subscriber.id != self.id);
330 });
331 }
332}
333
334#[derive(Clone, Copy, Debug, PartialEq)]
354pub struct NodeSize {
355 token: u32,
356 size: WindowSize,
357}
358
359impl NodeSize {
360 pub fn of(token: u32) -> Self {
362 Self {
363 token,
364 size: node_size(token),
365 }
366 }
367
368 pub const fn token(&self) -> i64 {
370 self.token as i64
371 }
372
373 pub fn is_compact(&self) -> bool {
375 self.size.is_compact()
376 }
377
378 pub fn is_medium(&self) -> bool {
380 self.size.is_medium()
381 }
382
383 pub fn is_expanded(&self) -> bool {
385 self.size.is_expanded()
386 }
387
388 pub const fn measured(&self) -> WindowSize {
390 self.size
391 }
392}
393
394thread_local! {
395 static NEXT_TOKEN: Cell<u32> = const { Cell::new(1) };
396}
397
398pub fn next_node_token() -> u32 {
400 NEXT_TOKEN.with(|next| {
401 let token = next.get();
402 next.set(token + 1);
403 token
404 })
405}
406
407pub struct NodeSizeSubscription {
412 token: u32,
413 id: u64,
414}
415
416impl NodeSizeSubscription {
417 pub fn token(&self) -> u32 {
419 self.token
420 }
421}
422
423pub fn subscribe_node(token: u32, notify: Arc<dyn Fn() + Send + Sync>) -> NodeSizeSubscription {
426 let id = next_subscriber_id();
427 NODE_SUBSCRIBERS.with_borrow_mut(|subscribers| {
428 subscribers.push((token, Subscriber { id, notify }));
429 });
430 NodeSizeSubscription { token, id }
431}
432
433impl Drop for NodeSizeSubscription {
434 fn drop(&mut self) {
435 NODE_SUBSCRIBERS.with_borrow_mut(|subscribers| {
438 subscribers.retain(|(_, subscriber)| subscriber.id != self.id);
439 });
440 NODES.with_borrow_mut(|nodes| nodes.retain(|(token, _)| *token != self.token));
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 #[test]
449 fn fr19_9_safe_area_only_change_republishes() {
450 use std::sync::atomic::{AtomicUsize, Ordering};
451 reset_window_size();
452 let calls = Arc::new(AtomicUsize::new(0));
453 let counter = Arc::clone(&calls);
454 let _subscription = subscribe(Arc::new(move || {
455 counter.fetch_add(1, Ordering::SeqCst);
456 }));
457 let size = WindowSize::default().with_safe_area(WindowSafeArea {
458 top: 28.0,
459 leading: 70.0,
460 trailing: 0.0,
461 });
462 assert!(publish(size));
463 assert_eq!(window_size(), size);
464 assert_eq!(calls.load(Ordering::SeqCst), 1);
465 assert!(!publish(size));
466 assert_eq!(calls.load(Ordering::SeqCst), 1);
467 }
468
469 #[test]
470 fn fr20_window_size_starts_compact_before_the_renderer_measures_anything() {
471 reset_window_size();
472 assert_eq!(window_size(), WindowSize::default());
473 assert!(window_size().is_compact());
474 }
475
476 #[test]
477 fn fr20_publishing_the_same_size_twice_wakes_nobody() {
478 reset_window_size();
479 let size = WindowSize::new(
480 700.0,
481 800.0,
482 WindowSizeClass::Medium,
483 WindowHeightClass::Medium,
484 );
485 assert!(publish(size));
486 assert!(!publish(size));
487 reset_window_size();
488 }
489}