Skip to main content

inset_embedder/
window.rs

1//! Windows the app creates, beyond the implicit view. Not a Flutter file.
2//!
3//! Flutter's windowing API (`widgets/_window.dart`, experimental) builds window archetypes
4//! over the engine's views, and on macOS its `FlutterViewController` is a view the app places
5//! in a window it owns. Here a window is a view the host puts on screen: the configuration
6//! every window wants up front, the native handle for anything beyond it, and adoption for
7//! a window only the app could make. Mobile and web hosts have no owner; the implicit view
8//! is all there is.
9
10use std::fmt;
11use std::future::Future;
12use std::pin::Pin;
13use std::rc::Rc;
14use std::time::Duration;
15
16pub use raw_window_handle::RawWindowHandle;
17
18use crate::{Rect, ViewRef};
19
20/// How a window is made. Sizes and positions are logical, in the host's screen space with
21/// the origin at the top left of the primary screen.
22///
23/// Every option names what the window is for, not how a system does it: the host maps each
24/// onto what its system has, and one its system lacks it leaves at the system's default.
25/// What no option expresses is set through the window's
26/// [`native_handle`](HostWindow::native_handle).
27#[derive(Clone, Debug, PartialEq)]
28pub struct WindowConfig {
29    pub title: String,
30    pub size: [f64; 2],
31    /// Where the window opens; the host chooses when `None`.
32    pub position: Option<[f64; 2]>,
33    /// The system's title bar and border.
34    pub decorations: bool,
35    pub resizable: bool,
36    pub level: WindowLevel,
37    /// Whether showing or clicking the window makes it the focused one. A host may not be
38    /// able to refuse activation; a window that must never take focus is one to `adopt`.
39    pub activating: bool,
40    /// Whether the window is on every virtual desktop: macOS Spaces, X11 workspaces.
41    pub all_desktops: bool,
42    pub background: WindowBackground,
43    /// Whether the window casts the system's shadow.
44    pub shadow: bool,
45    pub visible: bool,
46}
47
48impl Default for WindowConfig {
49    fn default() -> WindowConfig {
50        WindowConfig {
51            title: String::new(),
52            size: [400.0, 300.0],
53            position: None,
54            decorations: true,
55            resizable: true,
56            level: WindowLevel::Normal,
57            activating: true,
58            all_desktops: false,
59            background: WindowBackground::Solid,
60            shadow: true,
61            visible: true,
62        }
63    }
64}
65
66#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
67pub enum WindowLevel {
68    #[default]
69    Normal,
70    /// Above ordinary windows, as a palette or a panel sits.
71    AlwaysOnTop,
72    /// Below ordinary windows, as a desktop widget sits.
73    AlwaysOnBottom,
74}
75
76/// What the window shows behind the app's painting. Anything but `Solid` lets what is
77/// behind the window show where the app paints nothing.
78#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
79pub enum WindowBackground {
80    #[default]
81    Solid,
82    Transparent,
83    /// The system's blur of what lies behind the window (macOS vibrancy).
84    Blurred,
85    /// The system's glass material (macOS 26's Liquid Glass); the blur where the system
86    /// has none.
87    Glass,
88}
89
90impl WindowBackground {
91    /// Whether what is behind the window can show through it.
92    pub fn sees_through(self) -> bool {
93        self != WindowBackground::Solid
94    }
95}
96
97#[derive(Clone, Debug, PartialEq, Eq)]
98pub enum WindowError {
99    /// This host makes no windows, or cannot adopt one.
100    Unsupported,
101    Failed(String),
102}
103
104impl fmt::Display for WindowError {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            WindowError::Unsupported => f.write_str("the host does not support this window"),
108            WindowError::Failed(reason) => {
109                write!(f, "the host could not make the window: {reason}")
110            }
111        }
112    }
113}
114
115impl std::error::Error for WindowError {}
116
117/// A window being made: the host finishes it on its own loop.
118pub type WindowFuture = Pin<Box<dyn Future<Output = Result<WindowRef, WindowError>>>>;
119
120pub type WindowRef = Rc<dyn HostWindow>;
121
122/// A window the host has on screen. Its [`view`](Self::view) is what the framework draws
123/// into and receives input from, like the implicit view.
124pub trait HostWindow: 'static {
125    fn view(&self) -> ViewRef;
126
127    /// The window's logical frame in screen space.
128    fn frame(&self) -> Rect;
129
130    /// Moves and sizes the window, over `animate` when the host can animate a frame.
131    fn set_frame(&self, frame: Rect, animate: Option<Duration>);
132
133    fn set_title(&self, title: &str);
134
135    fn show(&self);
136
137    fn hide(&self);
138
139    /// Closes the window; its view goes away with it.
140    fn close(&self);
141
142    /// The native window, for configuration this interface does not carry.
143    fn native_handle(&self) -> Option<RawWindowHandle>;
144
145    /// What happens when the user asks to close the window. With no handler the host
146    /// closes it.
147    fn set_close_requested(&self, handler: Option<Rc<dyn Fn()>>);
148}
149
150/// The host's window maker. `None` from [`crate::Platform::windowing_owner`] where the
151/// implicit view is the only one there can be.
152pub trait WindowingOwner: 'static {
153    /// Makes a window. The host finishes it on its own loop, so the window arrives through
154    /// the future.
155    fn create(&self, config: WindowConfig) -> WindowFuture;
156
157    /// Takes over a native window the app made, for what no configuration can express:
158    /// on macOS an `NSPanel` that never activates. The host installs its view in it and
159    /// drives rendering and input for it. Hosts that cannot answer `Unsupported`.
160    fn adopt(&self, handle: RawWindowHandle) -> WindowFuture {
161        let _ = handle;
162        Box::pin(async { Err(WindowError::Unsupported) })
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn a_default_window_is_an_ordinary_visible_one() {
172        let config = WindowConfig::default();
173        assert!(config.decorations && config.visible && config.activating);
174        assert_eq!(config.level, WindowLevel::Normal);
175        assert_eq!(config.background, WindowBackground::Solid);
176    }
177
178    #[test]
179    fn every_background_but_solid_sees_through() {
180        assert!(!WindowBackground::Solid.sees_through());
181        assert!(WindowBackground::Transparent.sees_through());
182        assert!(WindowBackground::Blurred.sees_through());
183        assert!(WindowBackground::Glass.sees_through());
184    }
185
186    struct NoWindows;
187
188    impl WindowingOwner for NoWindows {
189        fn create(&self, _config: WindowConfig) -> WindowFuture {
190            Box::pin(async { Err(WindowError::Unsupported) })
191        }
192    }
193
194    #[test]
195    fn adoption_is_unsupported_unless_a_host_says_otherwise() {
196        let owner = NoWindows;
197        let mut future = owner.adopt(RawWindowHandle::Web(
198            raw_window_handle::WebWindowHandle::new(1),
199        ));
200        let waker = std::task::Waker::noop();
201        let mut cx = std::task::Context::from_waker(waker);
202        match future.as_mut().poll(&mut cx) {
203            std::task::Poll::Ready(Err(WindowError::Unsupported)) => {}
204            other => panic!(
205                "expected Unsupported, got {:?}",
206                other.map(|r| r.map(|_| ()))
207            ),
208        }
209    }
210}