Skip to main content

argui_platform/
window.rs

1#[cfg(not(target_arch = "wasm32"))]
2use winit::dpi::LogicalSize;
3use winit::window::Window;
4use winit::window::WindowAttributes;
5
6use crate::ApplicationIdentity;
7
8#[derive(Clone, Debug, Eq, Hash, PartialEq)]
9pub struct WindowKey(String);
10
11impl WindowKey {
12    pub const MAIN_VALUE: &'static str = "main";
13
14    #[must_use]
15    pub fn new(value: impl Into<String>) -> Self {
16        Self(value.into())
17    }
18
19    #[must_use]
20    pub fn main() -> Self {
21        Self::new(Self::MAIN_VALUE)
22    }
23
24    #[must_use]
25    pub fn as_str(&self) -> &str {
26        &self.0
27    }
28}
29
30#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
31pub enum CloseBehavior {
32    #[default]
33    Quit,
34    CloseWindow,
35    Hide,
36    NotifyApp,
37}
38
39#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
40pub enum WindowLevel {
41    AlwaysOnBottom,
42    #[default]
43    Normal,
44    AlwaysOnTop,
45}
46
47impl From<WindowLevel> for winit::window::WindowLevel {
48    fn from(level: WindowLevel) -> Self {
49        match level {
50            WindowLevel::AlwaysOnBottom => Self::AlwaysOnBottom,
51            WindowLevel::Normal => Self::Normal,
52            WindowLevel::AlwaysOnTop => Self::AlwaysOnTop,
53        }
54    }
55}
56
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub enum WindowBackend {
59    Windows,
60    MacOs,
61    Android,
62    Ios,
63    X11,
64    Wayland,
65    Web,
66    Other,
67}
68
69#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub struct WindowCapabilities {
71    pub backend: WindowBackend,
72    pub native_drag: bool,
73    pub native_shadow: bool,
74    pub minimize: bool,
75    pub maximize: bool,
76    pub window_level: bool,
77    pub mouse_passthrough: bool,
78}
79
80impl WindowBackend {
81    #[must_use]
82    pub const fn capabilities(self) -> WindowCapabilities {
83        let desktop = matches!(
84            self,
85            Self::Windows | Self::MacOs | Self::X11 | Self::Wayland
86        );
87        WindowCapabilities {
88            backend: self,
89            native_drag: desktop,
90            native_shadow: matches!(self, Self::Windows | Self::MacOs),
91            minimize: desktop,
92            maximize: desktop,
93            window_level: matches!(self, Self::Windows | Self::MacOs | Self::X11),
94            mouse_passthrough: desktop,
95        }
96    }
97}
98
99#[derive(Clone, Debug, PartialEq)]
100pub struct WindowSpec {
101    pub key: WindowKey,
102    pub window: WindowConfig,
103    pub visible: bool,
104}
105
106impl WindowSpec {
107    #[must_use]
108    pub fn new(key: WindowKey, window: WindowConfig) -> Self {
109        Self {
110            key,
111            window,
112            visible: true,
113        }
114    }
115}
116
117#[derive(Clone, Debug, PartialEq)]
118pub struct WindowConfig {
119    pub title: String,
120    pub width: f64,
121    pub height: f64,
122    pub decorations: bool,
123    pub resizable: bool,
124    pub transparent: bool,
125    /// Opt in to desktop effects. Regions are supplied by Element::desktop_backdrop.
126    pub desktop_backdrop: Option<argui_core::BackdropMaterial>,
127    pub native_shadow: bool,
128    pub level: WindowLevel,
129    pub append_to_document: bool,
130    pub web_parent_id: Option<String>,
131    pub close_behavior: CloseBehavior,
132    pub pointer: argui_core::PointerSettings,
133    /// Explicit safe-area override in logical pixels. `None` uses native
134    /// insets where Winit exposes them and zero elsewhere.
135    pub safe_area_insets: Option<argui_core::Insets>,
136}
137
138impl Default for WindowConfig {
139    fn default() -> Self {
140        Self {
141            title: "Argui".into(),
142            width: 960.0,
143            height: 640.0,
144            decorations: true,
145            resizable: true,
146            transparent: false,
147            desktop_backdrop: None,
148            native_shadow: false,
149            level: WindowLevel::Normal,
150            append_to_document: true,
151            web_parent_id: None,
152            close_behavior: CloseBehavior::Quit,
153            pointer: argui_core::PointerSettings::default(),
154            safe_area_insets: None,
155        }
156    }
157}
158
159impl WindowConfig {
160    #[must_use]
161    pub fn with_safe_area_insets(mut self, insets: argui_core::Insets) -> Self {
162        self.safe_area_insets = Some(insets);
163        self
164    }
165
166    #[must_use]
167    pub fn into_attributes(self) -> WindowAttributes {
168        let attributes = WindowAttributes::default()
169            .with_title(self.title)
170            .with_decorations(self.decorations)
171            .with_resizable(self.resizable)
172            .with_transparent(self.transparent || self.desktop_backdrop.is_some())
173            .with_window_level(self.level.into());
174
175        #[cfg(target_os = "windows")]
176        let attributes = {
177            use winit::platform::windows::WindowAttributesExtWindows;
178
179            attributes
180                .with_undecorated_shadow(self.native_shadow)
181                .with_no_redirection_bitmap(self.transparent || self.desktop_backdrop.is_some())
182        };
183
184        #[cfg(target_os = "macos")]
185        let attributes = {
186            use winit::platform::macos::WindowAttributesExtMacOS;
187
188            if self.decorations {
189                attributes
190            } else {
191                attributes.with_has_shadow(self.native_shadow)
192            }
193        };
194
195        #[cfg(target_arch = "wasm32")]
196        {
197            use winit::platform::web::WindowAttributesExtWebSys;
198
199            attributes.with_append(self.append_to_document)
200        }
201
202        #[cfg(not(target_arch = "wasm32"))]
203        attributes.with_inner_size(LogicalSize::new(self.width, self.height))
204    }
205
206    #[must_use]
207    pub fn into_attributes_with_identity(self, identity: &ApplicationIdentity) -> WindowAttributes {
208        let title = if self.title.is_empty() {
209            identity.display_name.clone()
210        } else {
211            self.title.clone()
212        };
213        let mut attributes = self.into_attributes().with_title(&title);
214        if let Some(icon) = identity.icons.best_square(32)
215            && let Ok(icon) =
216                winit::window::Icon::from_rgba(icon.rgba8.to_vec(), icon.width, icon.height)
217        {
218            attributes = attributes.with_window_icon(Some(icon));
219        }
220
221        #[cfg(target_os = "linux")]
222        {
223            use winit::platform::{
224                wayland::WindowAttributesExtWayland, x11::WindowAttributesExtX11,
225            };
226            attributes = WindowAttributesExtWayland::with_name(
227                attributes,
228                identity.linux_application_id().to_owned(),
229                title.clone(),
230            );
231            attributes = WindowAttributesExtX11::with_name(
232                attributes,
233                identity.linux_application_id().to_owned(),
234                title,
235            );
236        }
237        attributes
238    }
239}
240
241#[must_use]
242#[cfg_attr(coverage_nightly, coverage(off))]
243pub fn window_capabilities(window: &Window) -> WindowCapabilities {
244    window_backend(window).capabilities()
245}
246
247#[cfg(target_arch = "wasm32")]
248fn window_backend(_window: &Window) -> WindowBackend {
249    WindowBackend::Web
250}
251
252#[cfg(target_os = "windows")]
253fn window_backend(_window: &Window) -> WindowBackend {
254    WindowBackend::Windows
255}
256
257#[cfg(target_os = "macos")]
258fn window_backend(_window: &Window) -> WindowBackend {
259    WindowBackend::MacOs
260}
261
262#[cfg(target_os = "android")]
263fn window_backend(_window: &Window) -> WindowBackend {
264    WindowBackend::Android
265}
266
267#[cfg(target_os = "ios")]
268fn window_backend(_window: &Window) -> WindowBackend {
269    WindowBackend::Ios
270}
271
272#[cfg(target_os = "linux")]
273fn window_backend(window: &Window) -> WindowBackend {
274    use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
275
276    match window.window_handle().map(|handle| handle.as_raw()) {
277        Ok(RawWindowHandle::Xlib(_) | RawWindowHandle::Xcb(_)) => WindowBackend::X11,
278        Ok(RawWindowHandle::Wayland(_)) => WindowBackend::Wayland,
279        _ => WindowBackend::Other,
280    }
281}
282
283#[cfg(not(any(
284    target_arch = "wasm32",
285    target_os = "windows",
286    target_os = "macos",
287    target_os = "android",
288    target_os = "ios",
289    target_os = "linux"
290)))]
291fn window_backend(_window: &Window) -> WindowBackend {
292    WindowBackend::Other
293}