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)]
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}