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 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 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}