Skip to main content

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 { Self::new() }
126}