Skip to main content

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}