inset_embedder/window.rs
1//! Asking for views, and the windows around them. Not a Flutter file.
2//!
3//! An app asks for a view with a [`ViewRequest`] (`Platform::request_view`), and the view
4//! arrives later, as every view does, carrying the request's [`RequestId`], as iOS hands back
5//! the activity of `requestSceneSessionActivation` with the connecting scene. On a desktop the
6//! host makes a window for it; where the platform gives the app views and makes none, on a
7//! phone and when embedded, a request is refused. A view in a window the app may manage, one it
8//! asked for or one the platform gave, has a [`HostWindow`] (`View::window`): the configuration
9//! every window wants up front, whether the window is minimized or occluded, which the host
10//! reports with the window's moves (`EmbedderClient::window_changed`), the native handle for
11//! anything beyond it, and the window's closing, which is how a view closes.
12//!
13//! Flutter's windowing API (`widgets/_window.dart`, experimental) builds window archetypes
14//! over the engine's views, each controller making its window within its constructor.
15
16use std::fmt;
17use std::rc::Rc;
18use std::time::Duration;
19
20pub use raw_window_handle::RawWindowHandle;
21
22use crate::{Rect, ViewRef};
23
24/// How a window is made. Sizes and positions are logical, in the host's screen space with
25/// the origin at the top left of the primary screen.
26///
27/// Every option names what the window is for, not how a system does it: the host maps each
28/// onto what its system has, and one its system lacks it leaves at the system's default.
29/// What no option expresses is set through the window's
30/// [`native_handle`](HostWindow::native_handle).
31#[derive(Clone, Debug, PartialEq)]
32pub struct WindowConfig {
33 pub title: String,
34 pub size: [f64; 2],
35 /// Where the window opens; the host chooses when `None`.
36 pub position: Option<[f64; 2]>,
37 /// The system's title bar and border.
38 pub decorations: bool,
39 pub resizable: bool,
40 pub level: WindowLevel,
41 /// Whether showing or clicking the window makes it the focused one. A host may not be
42 /// able to refuse activation.
43 pub activating: bool,
44 /// Whether the window is on every virtual desktop: macOS Spaces, X11 workspaces.
45 pub all_desktops: bool,
46 pub background: WindowBackground,
47 /// Whether the window casts the system's shadow.
48 pub shadow: bool,
49 pub visible: bool,
50}
51
52impl Default for WindowConfig {
53 fn default() -> WindowConfig {
54 WindowConfig {
55 title: String::new(),
56 size: [900.0, 600.0],
57 position: None,
58 decorations: true,
59 resizable: true,
60 level: WindowLevel::Normal,
61 activating: true,
62 all_desktops: false,
63 background: WindowBackground::Solid,
64 shadow: true,
65 visible: true,
66 }
67 }
68}
69
70#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
71pub enum WindowLevel {
72 #[default]
73 Normal,
74 /// Above ordinary windows, as a palette or a panel sits.
75 AlwaysOnTop,
76 /// Below ordinary windows, as a desktop widget sits.
77 AlwaysOnBottom,
78}
79
80/// What the window shows behind the app's painting. Anything but `Solid` lets what is
81/// behind the window show where the app paints nothing.
82#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
83pub enum WindowBackground {
84 #[default]
85 Solid,
86 Transparent,
87 /// The system's blur of what lies behind the window (macOS vibrancy).
88 Blurred,
89 /// The system's glass material (macOS 26's Liquid Glass); the blur where the system
90 /// has none.
91 Glass,
92}
93
94impl WindowBackground {
95 /// Whether what is behind the window can show through it.
96 pub fn sees_through(self) -> bool {
97 self != WindowBackground::Solid
98 }
99}
100
101/// What an app asks for when it asks for a view.
102#[derive(Clone, Debug, Default, PartialEq)]
103pub struct ViewRequest {
104 /// The window a desktop makes for the view.
105 pub config: WindowConfig,
106 /// What the view carries when it arrives ([`View::initial_data`](crate::View::initial_data)),
107 /// such as which screen to show.
108 pub initial_data: Option<String>,
109}
110
111/// The number of a request for a view, which the view carries when it arrives
112/// ([`View::request_id`](crate::View::request_id)).
113#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
114pub struct RequestId(pub u64);
115
116/// Why a request for a view got no view.
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub enum ViewError {
119 /// This platform gives the app its views and makes none on request.
120 Unsupported,
121 Failed(String),
122}
123
124impl fmt::Display for ViewError {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 match self {
127 ViewError::Unsupported => f.write_str("the platform makes no views on request"),
128 ViewError::Failed(reason) => {
129 write!(f, "the platform could not make the view: {reason}")
130 }
131 }
132 }
133}
134
135impl std::error::Error for ViewError {}
136
137pub type WindowRef = Rc<dyn HostWindow>;
138
139/// The window around a view the host puts on screen in a window the app may manage.
140pub trait HostWindow: 'static {
141 fn view(&self) -> ViewRef;
142
143 /// The window's logical frame in screen space.
144 fn frame(&self) -> Rect;
145
146 /// Moves and sizes the window, over `animate` when the host can animate a frame.
147 fn set_frame(&self, frame: Rect, animate: Option<Duration>);
148
149 fn set_title(&self, title: &str);
150
151 fn show(&self);
152
153 fn hide(&self);
154
155 /// Flutter `WindowController.isMinimized`; macOS `isMiniaturized`, Windows `IsIconic`.
156 /// `false` where the system does not tell a client, as Wayland does not.
157 fn is_minimized(&self) -> bool;
158
159 /// Covered all over, or otherwise not shown: macOS's occlusion state without its visible bit,
160 /// winit's `Occluded`. Not in Flutter's controller; Inset's, after the platforms. `false`
161 /// where the system does not tell a window, as Windows and Wayland do not.
162 fn is_occluded(&self) -> bool;
163
164 /// Closes the window; its view goes away with it. The user's asking to close it only reports
165 /// it ([`EmbedderClient::view_close_requested`](crate::EmbedderClient::view_close_requested));
166 /// this closes it.
167 fn close(&self);
168
169 /// The native window, for configuration this interface does not carry.
170 fn native_handle(&self) -> Option<RawWindowHandle>;
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn a_default_window_is_an_ordinary_visible_one() {
179 let config = WindowConfig::default();
180 assert!(config.decorations && config.visible && config.activating);
181 assert_eq!(config.level, WindowLevel::Normal);
182 assert_eq!(config.background, WindowBackground::Solid);
183 }
184
185 #[test]
186 fn every_background_but_solid_sees_through() {
187 assert!(!WindowBackground::Solid.sees_through());
188 assert!(WindowBackground::Transparent.sees_through());
189 assert!(WindowBackground::Blurred.sees_through());
190 assert!(WindowBackground::Glass.sees_through());
191 }
192}