1use crate::schema::{WindowHeightClass, WindowSizeClass};
10use std::cell::{Cell, RefCell};
11use std::sync::Arc;
12
13#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct WindowSize {
21 pub width_dp: f32,
22 pub height_dp: f32,
23 pub class: WindowSizeClass,
24 pub height_class: WindowHeightClass,
25}
26
27impl WindowSize {
28 pub const fn new(
29 width_dp: f32,
30 height_dp: f32,
31 class: WindowSizeClass,
32 height_class: WindowHeightClass,
33 ) -> Self {
34 Self {
35 width_dp,
36 height_dp,
37 class,
38 height_class,
39 }
40 }
41
42 pub fn is_compact(&self) -> bool {
44 matches!(self.class, WindowSizeClass::Compact)
45 }
46
47 pub fn is_medium(&self) -> bool {
49 matches!(self.class, WindowSizeClass::Medium)
50 }
51
52 pub fn is_expanded(&self) -> bool {
54 matches!(self.class, WindowSizeClass::Expanded)
55 }
56
57 pub fn is_short(&self) -> bool {
60 matches!(self.height_class, WindowHeightClass::Compact)
61 }
62
63 pub fn is_medium_height(&self) -> bool {
65 matches!(self.height_class, WindowHeightClass::Medium)
66 }
67
68 pub fn is_tall(&self) -> bool {
70 matches!(self.height_class, WindowHeightClass::Expanded)
71 }
72}
73
74impl Default for WindowSize {
75 fn default() -> Self {
81 Self::new(
82 0.0,
83 0.0,
84 WindowSizeClass::Compact,
85 WindowHeightClass::Compact,
86 )
87 }
88}
89
90struct Subscriber {
91 id: u64,
92 notify: Arc<dyn Fn() + Send + Sync>,
93}
94
95thread_local! {
96 static NODES: RefCell<Vec<(u32, WindowSize)>> = const { RefCell::new(Vec::new()) };
101 static NODE_SUBSCRIBERS: RefCell<Vec<(u32, Subscriber)>> = const { RefCell::new(Vec::new()) };
102 static CURRENT: Cell<WindowSize> = const { Cell::new(WindowSize {
103 width_dp: 0.0,
104 height_dp: 0.0,
105 class: WindowSizeClass::Compact,
106 height_class: WindowHeightClass::Compact,
107 }) };
108 static SUBSCRIBERS: RefCell<Vec<Subscriber>> = const { RefCell::new(Vec::new()) };
109 static NEXT_SUBSCRIBER_ID: Cell<u64> = const { Cell::new(1) };
110}
111
112pub fn window_size() -> WindowSize {
114 CURRENT.with(Cell::get)
115}
116
117pub(crate) fn publish(size: WindowSize) -> bool {
122 if CURRENT.with(Cell::get) == size {
123 return false;
124 }
125 CURRENT.with(|current| current.set(size));
126 SUBSCRIBERS.with_borrow(|subscribers| {
127 for subscriber in subscribers {
128 (subscriber.notify)();
129 }
130 });
131 true
132}
133
134pub fn node_size(token: u32) -> WindowSize {
136 NODES.with_borrow(|nodes| {
137 nodes
138 .iter()
139 .find(|(name, _)| *name == token)
140 .map(|(_, size)| *size)
141 .unwrap_or_default()
142 })
143}
144
145pub(crate) fn publish_node(token: u32, size: WindowSize) -> bool {
150 let changed =
151 NODES.with_borrow_mut(
152 |nodes| match nodes.iter_mut().find(|(name, _)| *name == token) {
153 Some(entry) => {
154 if entry.1 == size {
155 return false;
156 }
157 entry.1 = size;
158 true
159 }
160 None => {
161 nodes.push((token, size));
162 true
163 }
164 },
165 );
166 if !changed {
167 return false;
168 }
169 NODE_SUBSCRIBERS.with_borrow(|subscribers| {
170 let mut woke = false;
171 for (name, subscriber) in subscribers {
172 if *name == token {
173 (subscriber.notify)();
174 woke = true;
175 }
176 }
177 woke
178 })
179}
180
181#[doc(hidden)]
183pub fn reset_window_size() {
184 CURRENT.with(|current| current.set(WindowSize::default()));
185 SUBSCRIBERS.with_borrow_mut(Vec::clear);
186 NODES.with_borrow_mut(Vec::clear);
187 NODE_SUBSCRIBERS.with_borrow_mut(Vec::clear);
188}
189
190pub struct WindowSizeSubscription {
193 id: u64,
194}
195
196pub fn subscribe(notify: Arc<dyn Fn() + Send + Sync>) -> WindowSizeSubscription {
199 let id = next_subscriber_id();
200 SUBSCRIBERS.with_borrow_mut(|subscribers| subscribers.push(Subscriber { id, notify }));
201 WindowSizeSubscription { id }
202}
203
204fn next_subscriber_id() -> u64 {
205 NEXT_SUBSCRIBER_ID.with(|next| {
206 let id = next.get();
207 next.set(id + 1);
208 id
209 })
210}
211
212impl Drop for WindowSizeSubscription {
213 fn drop(&mut self) {
214 SUBSCRIBERS.with_borrow_mut(|subscribers| {
215 subscribers.retain(|subscriber| subscriber.id != self.id);
216 });
217 }
218}
219
220#[derive(Clone, Copy, Debug, PartialEq)]
240pub struct NodeSize {
241 token: u32,
242 size: WindowSize,
243}
244
245impl NodeSize {
246 pub fn of(token: u32) -> Self {
248 Self {
249 token,
250 size: node_size(token),
251 }
252 }
253
254 pub const fn token(&self) -> i64 {
256 self.token as i64
257 }
258
259 pub fn is_compact(&self) -> bool {
261 self.size.is_compact()
262 }
263
264 pub fn is_medium(&self) -> bool {
266 self.size.is_medium()
267 }
268
269 pub fn is_expanded(&self) -> bool {
271 self.size.is_expanded()
272 }
273
274 pub const fn measured(&self) -> WindowSize {
276 self.size
277 }
278}
279
280thread_local! {
281 static NEXT_TOKEN: Cell<u32> = const { Cell::new(1) };
282}
283
284pub fn next_node_token() -> u32 {
286 NEXT_TOKEN.with(|next| {
287 let token = next.get();
288 next.set(token + 1);
289 token
290 })
291}
292
293pub struct NodeSizeSubscription {
298 token: u32,
299 id: u64,
300}
301
302impl NodeSizeSubscription {
303 pub fn token(&self) -> u32 {
305 self.token
306 }
307}
308
309pub fn subscribe_node(token: u32, notify: Arc<dyn Fn() + Send + Sync>) -> NodeSizeSubscription {
312 let id = next_subscriber_id();
313 NODE_SUBSCRIBERS.with_borrow_mut(|subscribers| {
314 subscribers.push((token, Subscriber { id, notify }));
315 });
316 NodeSizeSubscription { token, id }
317}
318
319impl Drop for NodeSizeSubscription {
320 fn drop(&mut self) {
321 NODE_SUBSCRIBERS.with_borrow_mut(|subscribers| {
324 subscribers.retain(|(_, subscriber)| subscriber.id != self.id);
325 });
326 NODES.with_borrow_mut(|nodes| nodes.retain(|(token, _)| *token != self.token));
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn fr20_window_size_starts_compact_before_the_renderer_measures_anything() {
336 reset_window_size();
337 assert_eq!(window_size(), WindowSize::default());
338 assert!(window_size().is_compact());
339 }
340
341 #[test]
342 fn fr20_publishing_the_same_size_twice_wakes_nobody() {
343 reset_window_size();
344 let size = WindowSize::new(
345 700.0,
346 800.0,
347 WindowSizeClass::Medium,
348 WindowHeightClass::Medium,
349 );
350 assert!(publish(size));
351 assert!(!publish(size));
352 reset_window_size();
353 }
354}