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)]
37pub struct OsWindowState {
38 /// Logical width in pixels. `None` = no saved size (use app default).
39 pub width: Option<u32>,
40 /// Logical height in pixels.
41 pub height: Option<u32>,
42 /// Window was borderless-fullscreen.
43 pub fullscreen: bool,
44 /// Window was maximized (not fullscreen).
45 pub maximized: bool,
46}
47
48impl OsWindowState {
49 /// Read the shared cells of an [`OsWindowHandle`] into a snapshot.
50 pub fn from_handle(h: &OsWindowHandle) -> Self {
51 let (w, hgt) = h.size.get();
52 Self {
53 width: if w > 0 { Some(w) } else { None },
54 height: if hgt > 0 { Some(hgt) } else { None },
55 fullscreen: h.fullscreen.get(),
56 maximized: h.maximized.get(),
57 }
58 }
59
60 /// Compact one-line form: `width,height,fullscreen,maximized` (integers,
61 /// comma-separated). Missing dimensions write as `0`.
62 pub fn serialize(&self) -> String {
63 format!(
64 "{},{},{},{}",
65 self.width.unwrap_or(0),
66 self.height.unwrap_or(0),
67 self.fullscreen as u8,
68 self.maximized as u8,
69 )
70 }
71
72 /// Parse the format produced by [`OsWindowState::serialize`]. Returns
73 /// `None` on malformed input. Backward-compatible: a 3-field
74 /// `width,height,fullscreen` input (no maximized) parses with
75 /// `maximized = false`.
76 pub fn deserialize(s: &str) -> Option<Self> {
77 let mut it = s.splitn(4, ',');
78 let w: u32 = it.next()?.trim().parse().ok()?;
79 let h: u32 = it.next()?.trim().parse().ok()?;
80 let fs: u8 = it.next()?.trim().parse().ok()?;
81 let mx: u8 = it.next().and_then(|v| v.trim().parse().ok()).unwrap_or(0);
82 Some(Self {
83 width: if w > 0 { Some(w) } else { None },
84 height: if h > 0 { Some(h) } else { None },
85 fullscreen: fs != 0,
86 maximized: mx != 0,
87 })
88 }
89}
90
91/// Live shared cells the platform harness mutates during the event loop.
92///
93/// Cheap to clone — all fields are `Rc<Cell<...>>`. The harness updates
94/// `size` on every `Resized`, `fullscreen` / `maximized` on the
95/// corresponding state-change events. Widgets that want to reflect the
96/// current OS window state read the cells directly.
97#[derive(Clone)]
98pub struct OsWindowHandle {
99 pub size: Rc<Cell<(u32, u32)>>,
100 pub fullscreen: Rc<Cell<bool>>,
101 pub maximized: Rc<Cell<bool>>,
102}
103
104impl OsWindowHandle {
105 pub fn new() -> Self {
106 Self {
107 size: Rc::new(Cell::new((0, 0))),
108 fullscreen: Rc::new(Cell::new(false)),
109 maximized: Rc::new(Cell::new(false)),
110 }
111 }
112
113 /// Load an initial snapshot into the cells so first-frame code sees the
114 /// about-to-be-applied state before any `Resized` event fires.
115 pub fn apply(&self, s: &OsWindowState) {
116 let w = s.width.unwrap_or(0);
117 let h = s.height.unwrap_or(0);
118 self.size.set((w, h));
119 self.fullscreen.set(s.fullscreen);
120 self.maximized.set(s.maximized);
121 }
122}
123
124impl Default for OsWindowHandle {
125 fn default() -> Self {
126 Self::new()
127 }
128}