Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/windowing/state.rs
1use pathfinder_geometry::rect::RectF;
2use std::{
3 collections::VecDeque,
4 fmt::{Display, Formatter},
5};
6 
7use crate::{
8 geometry,
9 platform::{self, FullscreenState, TerminationMode, WindowFocusBehavior},
10 scene::{CornerRadius, Radius},
11 windowing, DisplayId, DisplayIdx, Entity, ModelContext, OptionalPlatformWindow,
12 SingletonEntity, WindowId,
13};
14 
15use super::WindowCallbacks;
16 
17/// Description of the current stage in the lifecycle of the app.
18#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
19pub enum ApplicationStage {
20 #[default]
21 /// The application is starting. We move from `Starting` to `Active` when we process the
22 /// lifecycle event when we open the first window.
23 Starting,
24 /// The app is currently the active application.
25 Active,
26 /// Some other application is currently active.
27 Inactive,
28 /// The application is in the process of terminating.
29 Terminating,
30}
31 
32impl Display for ApplicationStage {
33 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34 match self {
35 ApplicationStage::Starting => write!(f, "Starting"),
36 ApplicationStage::Active => write!(f, "Active"),
37 ApplicationStage::Inactive => write!(f, "Inactive"),
38 ApplicationStage::Terminating => write!(f, "Terminating"),
39 }
40 }
41}
42 
43#[derive(Clone, Debug, Default)]
44pub struct State {
45 /// The current stage of the app.
46 pub stage: ApplicationStage,
47 /// The [`WindowId`] of the currently active (frontmost) window. If Warp goes out of focus,
48 /// this will go back to None.
49 pub active_window: Option<WindowId>,
50 /// A stack of [`WindowId`]s which had been active before.
51 active_window_stack: VecDeque<WindowId>,
52 /// Whether the active window is fullscreen.
53 pub is_active_window_fullscreen: Option<bool>,
54}
55 
56/// Struct that enumerates windowing-related state within the application.
57pub struct WindowManager {
58 state: State,
59 platform: Box<dyn platform::WindowManager>,
60}
61 
62impl WindowManager {
63 /// Constructs a new [`State`] in an inactive state.
64 /// NOTE the `State` begins with no windows and an inactive state--it is updated by platform
65 /// code when the application first becomes active.
66 pub(crate) fn new(window_manager: Box<dyn platform::WindowManager>) -> Self {
67 Self {
68 state: Default::default(),
69 platform: window_manager,
70 }
71 }
72 
73 #[cfg(any(test, feature = "test-util", feature = "integration_tests"))]
74 pub fn overwrite_for_test(&mut self, stage: ApplicationStage, active_window: Option<WindowId>) {
75 self.state.stage = stage;
76 self.state.active_window = active_window;
77 self.state.active_window_stack = active_window.into_iter().collect();
78 self.state.is_active_window_fullscreen = Some(false);
79 }
80 
81 pub fn platform_window(&self, window_id: WindowId) -> OptionalPlatformWindow {
82 self.platform.platform_window(window_id)
83 }
84 
85 pub fn app_is_active(&self) -> bool {
86 self.platform.app_is_active()
87 }
88 
89 pub fn hide_window(&self, window_id: WindowId) {
90 self.platform.hide_window(window_id)
91 }
92 
93 pub fn set_window_bounds(&self, window_id: WindowId, bound: RectF) {
94 self.platform.set_window_bounds(window_id, bound)
95 }
96 
97 pub fn cancel_synthetic_drag(&self, window_id: WindowId) {
98 self.platform.cancel_synthetic_drag(window_id)
99 }
100 
101 #[cfg(target_os = "macos")]
102 pub fn show_window_and_focus_app_without_ordering_front(&self, window_id: WindowId) {
103 self.platform
104 .show_window_and_focus_app(window_id, WindowFocusBehavior::RetainZIndex)
105 }
106 
107 pub fn show_window_and_focus_app(&self, window_id: WindowId) {
108 self.platform
109 .show_window_and_focus_app(window_id, WindowFocusBehavior::default())
110 }
111 
112 pub fn activate_app(&self) -> Option<WindowId> {
113 self.platform.activate_app(self.frontmost_window_id())
114 }
115 
116 pub fn hide_app(&self) {
117 self.platform.hide_app()
118 }
119 
120 pub fn set_all_windows_background_blur_radius(&self, blur_radius_pixels: u8) {
121 self.platform
122 .set_all_windows_background_blur_radius(blur_radius_pixels)
123 }
124 
125 pub fn set_all_windows_background_blur_texture(&self, use_blur_texture: bool) {
126 self.platform
127 .set_all_windows_background_blur_texture(use_blur_texture)
128 }
129 
130 pub fn set_window_title(&self, window_id: WindowId, title: &str) {
131 self.platform.set_window_title(window_id, title)
132 }
133 
134 pub fn key_window_is_modal_panel(&self) -> bool {
135 self.platform.key_window_is_modal_panel()
136 }
137 
138 pub fn close_window(&self, window_id: WindowId, termination_mode: TerminationMode) {
139 self.platform
140 .close_window_async(window_id, termination_mode)
141 }
142 
143 pub fn active_cursor_position_updated(&self) {
144 self.platform.active_cursor_position_updated();
145 }
146 
147 pub fn active_window(&self) -> Option<WindowId> {
148 self.platform.active_window_id()
149 }
150 
151 // Get the rect of the current active screen. We need the bound instead of just
152 // the size of the screen because Mac has a global coordination system containing
153 // all user's screens. So the active screen may not have a origin of (0, 0).
154 pub fn active_display_bounds(&self) -> geometry::rect::RectF {
155 self.platform.active_display_bounds()
156 }
157 
158 pub fn active_display_id(&self) -> DisplayId {
159 self.platform.active_display_id()
160 }
161 
162 pub fn bounds_for_display_idx(&self, idx: DisplayIdx) -> Option<RectF> {
163 self.platform.bounds_for_display_idx(idx)
164 }
165 
166 pub fn display_count(&self) -> usize {
167 self.platform.display_count()
168 }
169 
170 pub fn is_tiling_window_manager(&self) -> bool {
171 self.platform.is_tiling_window_manager()
172 }
173 
174 pub fn os_window_manager_name(&self) -> Option<String> {
175 self.platform.os_window_manager_name()
176 }
177 
178 pub fn did_window_change_focus(window_id: WindowId, current: &State, previous: &State) -> bool {
179 let current_window_is_active = current.active_window == Some(window_id);
180 let previous_window_was_active = previous.active_window == Some(window_id);
181 current_window_is_active != previous_window_was_active
182 }
183 
184 /// The window itself usually has rounded corners, except when running in a tiling window
185 /// manager or when on Windows. We don't need to specify a custom window corner radius on
186 /// Windows because we use OS APIs to round the corners of the window.
187 pub fn window_corner_radius(&self) -> CornerRadius {
188 let radius = if self.is_tiling_window_manager() || cfg!(windows) {
189 0.
190 } else {
191 8.
192 };
193 CornerRadius::with_all(Radius::Pixels(radius))
194 }
195 
196 pub(crate) fn open_window(
197 &mut self,
198 window_id: WindowId,
199 window_options: platform::WindowOptions,
200 callbacks: WindowCallbacks,
201 ) -> anyhow::Result<()> {
202 self.platform
203 .open_window(window_id, window_options, callbacks)
204 }
205 
206 pub(crate) fn remove_window(&mut self, window_id: WindowId, ctx: &mut ModelContext<Self>) {
207 self.update(
208 |state| {
209 state.active_window_stack.retain(|id| *id != window_id);
210 },
211 ctx,
212 );
213 self.platform.remove_window(window_id)
214 }
215 
216 pub(crate) fn close_window_async(
217 &self,
218 window_id: WindowId,
219 termination_mode: TerminationMode,
220 ) {
221 self.platform
222 .close_window_async(window_id, termination_mode)
223 }
224 
225 /// Sets the current active window to `window_id`.
226 pub(crate) fn set_active_window(
227 &mut self,
228 window_id: Option<WindowId>,
229 ctx: &mut ModelContext<Self>,
230 ) {
231 self.update(
232 |state| {
233 state.active_window = window_id;
234 if let Some(window_id) = window_id {
235 state.active_window_stack.retain(|id| *id != window_id);
236 state.active_window_stack.push_back(window_id);
237 state.stage = ApplicationStage::Active;
238 } else {
239 state.stage = ApplicationStage::Inactive;
240 }
241 },
242 ctx,
243 );
244 }
245 
246 pub fn toggle_fullscreen(&mut self, window_id: WindowId, ctx: &mut ModelContext<Self>) {
247 if let Some(window) = self.platform_window(window_id) {
248 window.toggle_fullscreen();
249 }
250 if self.state.active_window == Some(window_id) {
251 self.update_is_active_window_fullscreen(ctx);
252 }
253 }
254 
255 /// Updates saved state corresponding to whether the active window is fullscreen.
256 pub(crate) fn update_is_active_window_fullscreen(&mut self, ctx: &mut ModelContext<Self>) {
257 let is_active_window_fullscreen = self.state.active_window.map(|id| {
258 self.platform_window(id)
259 .map(|window| window.fullscreen_state() == FullscreenState::Fullscreen)
260 .unwrap_or(false)
261 });
262 
263 self.update(
264 |state| state.is_active_window_fullscreen = is_active_window_fullscreen,
265 ctx,
266 );
267 }
268 
269 /// Sets the current stage of the application to `stage`.
270 pub(crate) fn set_stage(&mut self, stage: ApplicationStage, ctx: &mut ModelContext<Self>) {
271 self.update(|state| state.stage = stage, ctx);
272 }
273 
274 /// Returns the current [`ApplicationStage`] of the application.
275 pub fn stage(&self) -> ApplicationStage {
276 self.state.stage
277 }
278 
279 pub fn frontmost_window_id(&self) -> Option<WindowId> {
280 self.state.active_window_stack.back().cloned()
281 }
282 
283 /// Returns boolean representing whether the active window is fullscreen. Returns `false` if
284 /// no window is currently active.
285 pub fn is_active_window_fullscreen(&self) -> bool {
286 self.state.is_active_window_fullscreen.unwrap_or(false)
287 }
288 
289 pub fn ordered_window_ids(&self) -> Vec<WindowId> {
290 self.platform.ordered_window_ids()
291 }
292 
293 pub fn state(&self) -> &State {
294 &self.state
295 }
296 
297 pub fn windowing_system(&self) -> Option<windowing::System> {
298 self.platform.windowing_system()
299 }
300 
301 /// Helper function used to ensure that updates to [`State`] end up triggering the proper event
302 /// updates.
303 fn update(&mut self, update_fn: impl FnOnce(&mut State), ctx: &mut ModelContext<Self>) {
304 let previous = self.state.clone();
305 update_fn(&mut self.state);
306 ctx.emit(StateEvent::ValueChanged {
307 previous,
308 current: self.state.clone(),
309 });
310 ctx.notify();
311 }
312}
313 
314/// The set of events that are emitted by the [`crate::windowing::State`] model.
315pub enum StateEvent {
316 /// The state changed from `previous` to `current`.
317 ValueChanged { current: State, previous: State },
318}
319 
320impl Entity for WindowManager {
321 type Event = StateEvent;
322}
323 
324/// Mark [`State`] as global application state.
325impl SingletonEntity for WindowManager {}
326