agg_gui/app_state.rs
1//! OS-window state persistence helpers.
2//!
3//! Apps using agg-gui typically want to restore the OS window's size,
4//! fullscreen, and maximized state across launches. This module packages
5//! the small serializable record and shared cells that the platform harness
6//! writes into / reads from. The app-specific bits (which demo windows were
7//! open, positions of floating `Window` widgets, etc.) are NOT here — those
8//! belong to the app's own state struct; this handles only the OS-window
9//! level.
10//!
11//! # Usage sketch
12//!
13//! ```ignore
14//! // Somewhere in app setup:
15//! let os_window = agg_gui::OsWindowHandle::new();
16//!
17//! // In winit's Resized handler:
18//! os_window.size.set((new_size.width, new_size.height));
19//! os_window.fullscreen.set(window.fullscreen().is_some());
20//! os_window.maximized.set(window.is_maximized());
21//!
22//! // On close / every idle tick, serialize `OsWindowState::from_handle(&os_window)`
23//! // and write it wherever app state lives. On startup, parse it back and
24//! // apply via `WindowAttributes::with_inner_size` / `with_maximized` /
25//! // `with_fullscreen`, then after the window is live call
26//! // `window.set_fullscreen(...)` / `set_maximized(true)` as a
27//! // belt-and-suspenders (some platforms ignore the initial attribute).
28//! ```
29//!
30//! See `demo-native/src/main.rs` for a complete wiring.
31
32use std::cell::Cell;
33use std::rc::Rc;
34
35/// Snapshot of the OS window state — serialisable.
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
38pub struct OsWindowState {
39 /// Logical width in pixels. `None` = no saved size (use app default).
40 pub width: Option<u32>,
41 /// Logical height in pixels.
42 pub height: Option<u32>,
43 /// Window was borderless-fullscreen.
44 pub fullscreen: bool,
45 /// Window was maximized (not fullscreen).
46 pub maximized: bool,
47}
48
49impl OsWindowState {
50 /// Read the shared cells of an [`OsWindowHandle`] into a snapshot.
51 pub fn from_handle(h: &OsWindowHandle) -> Self {
52 let (w, hgt) = h.size.get();
53 Self {
54 width: if w > 0 { Some(w) } else { None },
55 height: if hgt > 0 { Some(hgt) } else { None },
56 fullscreen: h.fullscreen.get(),
57 maximized: h.maximized.get(),
58 }
59 }
60
61 /// Compact one-line form: `width,height,fullscreen,maximized` (integers,
62 /// comma-separated). Missing dimensions write as `0`.
63 pub fn serialize(&self) -> String {
64 format!(
65 "{},{},{},{}",
66 self.width.unwrap_or(0),
67 self.height.unwrap_or(0),
68 self.fullscreen as u8,
69 self.maximized as u8,
70 )
71 }
72
73 /// Parse the format produced by [`OsWindowState::serialize`]. Returns
74 /// `None` on malformed input. Backward-compatible: a 3-field
75 /// `width,height,fullscreen` input (no maximized) parses with
76 /// `maximized = false`.
77 pub fn deserialize(s: &str) -> Option<Self> {
78 let mut it = s.splitn(4, ',');
79 let w: u32 = it.next()?.trim().parse().ok()?;
80 let h: u32 = it.next()?.trim().parse().ok()?;
81 let fs: u8 = it.next()?.trim().parse().ok()?;
82 let mx: u8 = it.next().and_then(|v| v.trim().parse().ok()).unwrap_or(0);
83 Some(Self {
84 width: if w > 0 { Some(w) } else { None },
85 height: if h > 0 { Some(h) } else { None },
86 fullscreen: fs != 0,
87 maximized: mx != 0,
88 })
89 }
90}
91
92/// Live shared cells the platform harness mutates during the event loop.
93///
94/// Cheap to clone — all fields are `Rc<Cell<...>>`. The harness updates
95/// `size` on every `Resized`, `fullscreen` / `maximized` on the
96/// corresponding state-change events. Widgets that want to reflect the
97/// current OS window state read the cells directly.
98#[derive(Clone)]
99pub struct OsWindowHandle {
100 pub size: Rc<Cell<(u32, u32)>>,
101 pub fullscreen: Rc<Cell<bool>>,
102 pub maximized: Rc<Cell<bool>>,
103}
104
105impl OsWindowHandle {
106 pub fn new() -> Self {
107 Self {
108 size: Rc::new(Cell::new((0, 0))),
109 fullscreen: Rc::new(Cell::new(false)),
110 maximized: Rc::new(Cell::new(false)),
111 }
112 }
113
114 /// Load an initial snapshot into the cells so first-frame code sees the
115 /// about-to-be-applied state before any `Resized` event fires.
116 pub fn apply(&self, s: &OsWindowState) {
117 let w = s.width.unwrap_or(0);
118 let h = s.height.unwrap_or(0);
119 self.size.set((w, h));
120 self.fullscreen.set(s.fullscreen);
121 self.maximized.set(s.maximized);
122 }
123}
124
125impl Default for OsWindowHandle {
126 fn default() -> Self {
127 Self::new()
128 }
129}