1use tauri_runtime::{
6 Icon, Result,
7 dpi::{PhysicalSize, Size},
8 window::{WindowBuilder, WindowBuilderBase, WindowSizeConstraints},
9};
10use tauri_utils::{
11 Theme,
12 config::{Color, PreventOverflowConfig, WindowConfig},
13};
14use winit::{
15 dpi::{LogicalPosition, LogicalSize},
16 monitor::Fullscreen,
17 window::{WindowAttributes, WindowButtons},
18};
19
20use crate::window::{
21 AppWindowAttrs, max_size_constraint, min_size_constraint, tauri_theme_to_winit_theme,
22 winit_theme_to_tauri_theme,
23};
24
25#[cfg(any(windows, target_os = "macos"))]
26use winit::raw_window_handle::RawWindowHandle;
27
28#[cfg(target_os = "macos")]
29use std::ptr::NonNull;
30#[cfg(target_os = "macos")]
31use tauri_runtime::dpi::Position;
32#[cfg(target_os = "macos")]
33use tauri_utils::TitleBarStyle;
34
35#[cfg(windows)]
36use std::num::NonZeroIsize;
37#[cfg(windows)]
38use windows::Win32::Foundation::HWND;
39
40#[derive(Clone, Default, Debug)]
41pub struct WindowBuilderWrapper {
42 pub(crate) attrs: AppWindowAttrs,
43}
44
45unsafe impl Send for WindowBuilderWrapper {}
46
47impl WindowBuilderBase for WindowBuilderWrapper {}
48
49impl WindowBuilder for WindowBuilderWrapper {
50 fn new() -> Self {
51 #[allow(unused_mut)]
52 let mut builder = Self {
53 attrs: AppWindowAttrs {
54 inner: WindowAttributes::default()
55 .with_title("Tauri App")
56 .with_visible(true),
57 ..Default::default()
58 },
59 }
60 .focused(true);
61
62 #[cfg(windows)]
63 {
64 builder = builder.window_classname("Tauri Window");
65 }
66
67 builder
68 }
69
70 fn with_config(config: &WindowConfig) -> Self {
71 let mut builder = Self::new()
72 .title(config.title.to_string())
73 .inner_size(config.width, config.height)
74 .resizable(config.resizable)
75 .fullscreen(config.fullscreen)
76 .focused(config.focus)
77 .focusable(config.focusable)
78 .visible(config.visible)
79 .decorations(config.decorations)
80 .maximized(config.maximized)
81 .content_protected(config.content_protected)
82 .closable(config.closable)
83 .maximizable(config.maximizable)
84 .minimizable(config.minimizable)
85 .skip_taskbar(config.skip_taskbar)
86 .shadow(config.shadow)
87 .visible_on_all_workspaces(config.visible_on_all_workspaces)
88 .theme(config.theme);
89 if config.always_on_bottom {
90 builder = builder.always_on_bottom(true);
91 } else if config.always_on_top {
92 builder = builder.always_on_top(true);
93 }
94 builder = builder.transparent(config.transparent);
95 let mut constraints = WindowSizeConstraints::default();
96 if let Some(min_width) = config.min_width {
97 constraints.min_width = Some(tauri_runtime::dpi::LogicalUnit::new(min_width).into());
98 }
99 if let Some(min_height) = config.min_height {
100 constraints.min_height = Some(tauri_runtime::dpi::LogicalUnit::new(min_height).into());
101 }
102 if let Some(max_width) = config.max_width {
103 constraints.max_width = Some(tauri_runtime::dpi::LogicalUnit::new(max_width).into());
104 }
105 if let Some(max_height) = config.max_height {
106 constraints.max_height = Some(tauri_runtime::dpi::LogicalUnit::new(max_height).into());
107 }
108 builder = builder.inner_size_constraints(constraints);
109 if let Some(color) = config.background_color {
110 builder = builder.background_color(color);
111 }
112 if let (Some(x), Some(y)) = (config.x, config.y) {
113 builder = builder.position(x, y);
114 }
115 #[cfg(target_os = "macos")]
116 {
117 builder = builder
118 .hidden_title(config.hidden_title)
119 .title_bar_style(config.title_bar_style);
120 if let Some(position) = &config.traffic_light_position {
121 builder = builder.traffic_light_position(LogicalPosition::new(position.x, position.y));
122 }
123 if let Some(identifier) = &config.tabbing_identifier {
124 builder = builder.tabbing_identifier(identifier);
125 }
126 }
127 if config.center {
128 builder = builder.center();
129 }
130 if let Some(window_classname) = &config.window_classname {
131 builder = builder.window_classname(window_classname);
132 }
133 if let Some(prevent_overflow) = &config.prevent_overflow {
134 builder = match prevent_overflow {
135 PreventOverflowConfig::Enable(true) => builder.prevent_overflow(),
136 PreventOverflowConfig::Margin(margin) => {
137 let margin = PhysicalSize::new(margin.width, margin.height);
138 builder.prevent_overflow_with_margin(margin.into())
139 }
140 _ => builder,
141 };
142 }
143 builder
144 }
145
146 fn center(mut self) -> Self {
147 self.attrs.center = true;
148 self
149 }
150
151 fn position(mut self, x: f64, y: f64) -> Self {
152 self.attrs.inner = self.attrs.inner.with_position(LogicalPosition::new(x, y));
153 self
154 }
155
156 fn inner_size(mut self, width: f64, height: f64) -> Self {
157 self.attrs.inner = self
158 .attrs
159 .inner
160 .with_surface_size(LogicalSize::new(width, height));
161 self
162 }
163
164 fn min_inner_size(mut self, min_width: f64, min_height: f64) -> Self {
165 self.attrs.inner = self
166 .attrs
167 .inner
168 .with_min_surface_size(LogicalSize::new(min_width, min_height));
169 self
170 }
171
172 fn max_inner_size(mut self, max_width: f64, max_height: f64) -> Self {
173 self.attrs.inner = self
174 .attrs
175 .inner
176 .with_max_surface_size(LogicalSize::new(max_width, max_height));
177 self
178 }
179
180 fn inner_size_constraints(mut self, constraints: WindowSizeConstraints) -> Self {
181 self.attrs.inner.min_surface_size =
182 min_size_constraint(constraints.min_width, constraints.min_height);
183 self.attrs.inner.max_surface_size =
184 max_size_constraint(constraints.max_width, constraints.max_height);
185 self
186 }
187
188 fn prevent_overflow(mut self) -> Self {
189 self.attrs.prevent_overflow = Some(PhysicalSize::new(0, 0).into());
190 self
191 }
192
193 fn prevent_overflow_with_margin(mut self, margin: Size) -> Self {
194 self.attrs.prevent_overflow = Some(margin);
195 self
196 }
197
198 fn resizable(mut self, resizable: bool) -> Self {
199 self.attrs.inner = self.attrs.inner.with_resizable(resizable);
200 self
201 }
202
203 fn maximizable(mut self, maximizable: bool) -> Self {
204 self
205 .attrs
206 .inner
207 .enabled_buttons
208 .set(WindowButtons::MAXIMIZE, maximizable);
209 self
210 }
211
212 fn minimizable(mut self, minimizable: bool) -> Self {
213 self
214 .attrs
215 .inner
216 .enabled_buttons
217 .set(WindowButtons::MINIMIZE, minimizable);
218 self
219 }
220
221 fn closable(mut self, closable: bool) -> Self {
222 self
223 .attrs
224 .inner
225 .enabled_buttons
226 .set(WindowButtons::CLOSE, closable);
227 self
228 }
229
230 fn title<S: Into<String>>(mut self, title: S) -> Self {
231 self.attrs.inner = self.attrs.inner.with_title(title);
232 self
233 }
234
235 fn fullscreen(mut self, fullscreen: bool) -> Self {
236 self.attrs.inner = self
237 .attrs
238 .inner
239 .with_fullscreen(fullscreen.then_some(Fullscreen::Borderless(None)));
240 self
241 }
242
243 fn focused(mut self, focused: bool) -> Self {
244 self.attrs.inner = self.attrs.inner.with_active(focused);
245 self
246 }
247
248 fn focusable(self, _focusable: bool) -> Self {
249 self
251 }
252
253 fn maximized(mut self, maximized: bool) -> Self {
254 self.attrs.inner = self.attrs.inner.with_maximized(maximized);
255 self
256 }
257
258 fn visible(mut self, visible: bool) -> Self {
259 self.attrs.inner = self.attrs.inner.with_visible(visible);
260 self
261 }
262
263 fn transparent(mut self, transparent: bool) -> Self {
264 self.attrs.inner = self.attrs.inner.with_transparent(transparent);
265 self
266 }
267
268 fn decorations(mut self, decorations: bool) -> Self {
269 self.attrs.inner = self.attrs.inner.with_decorations(decorations);
270 self
271 }
272
273 fn always_on_bottom(mut self, always_on_bottom: bool) -> Self {
274 self.attrs.inner = self.attrs.inner.with_window_level(if always_on_bottom {
275 winit::window::WindowLevel::AlwaysOnBottom
276 } else {
277 winit::window::WindowLevel::Normal
278 });
279 self
280 }
281
282 fn always_on_top(mut self, always_on_top: bool) -> Self {
283 self.attrs.inner = self.attrs.inner.with_window_level(if always_on_top {
284 winit::window::WindowLevel::AlwaysOnTop
285 } else {
286 winit::window::WindowLevel::Normal
287 });
288 self
289 }
290
291 #[cfg_attr(windows, allow(unused))]
292 fn visible_on_all_workspaces(mut self, visible_on_all_workspaces: bool) -> Self {
293 #[cfg(any(
294 target_os = "macos",
295 target_os = "linux",
296 target_os = "dragonfly",
297 target_os = "freebsd",
298 target_os = "netbsd",
299 target_os = "openbsd"
300 ))]
301 {
302 self.attrs.visible_on_all_workspaces = visible_on_all_workspaces;
303 }
304
305 self
306 }
307
308 fn content_protected(mut self, protected: bool) -> Self {
309 self.attrs.inner = self.attrs.inner.with_content_protected(protected);
310 self
311 }
312
313 fn icon(mut self, icon: Icon) -> Result<Self> {
314 let icon = super::window::tauri_icon_to_winit_icon(icon)?;
315 self.attrs.inner = self.attrs.inner.with_window_icon(Some(icon));
316 Ok(self)
317 }
318
319 #[allow(unused_mut)]
320 fn skip_taskbar(mut self, skip: bool) -> Self {
321 #[cfg(windows)]
322 {
323 let pl_attrs = platform_attrs(&mut self.attrs.inner).with_skip_taskbar(skip);
324 self.attrs.inner = self
325 .attrs
326 .inner
327 .with_platform_attributes(Box::new(pl_attrs));
328 }
329
330 #[cfg(any(
331 target_os = "linux",
332 target_os = "dragonfly",
333 target_os = "freebsd",
334 target_os = "netbsd",
335 target_os = "openbsd"
336 ))]
337 {
338 self.attrs.skip_taskbar = skip;
339 }
340
341 #[cfg(target_os = "macos")]
342 let _skip = skip;
343
344 self
345 }
346
347 fn background_color(mut self, color: Color) -> Self {
348 self.attrs.background_color = Some(color);
349 self
350 }
351
352 #[cfg_attr(
353 not(any(windows, target_os = "macos")),
354 allow(unused_mut, unused_variables)
355 )]
356 fn shadow(mut self, enable: bool) -> Self {
357 #[cfg(windows)]
358 {
359 let pl_attrs = platform_attrs(&mut self.attrs.inner).with_undecorated_shadow(enable);
360 self.attrs.inner = self
361 .attrs
362 .inner
363 .with_platform_attributes(Box::new(pl_attrs));
364 }
365
366 #[cfg(target_os = "macos")]
367 {
368 let pl_attrs = platform_attrs(&mut self.attrs.inner).with_has_shadow(enable);
369 self.attrs.inner = self
370 .attrs
371 .inner
372 .with_platform_attributes(Box::new(pl_attrs));
373 }
374
375 self
376 }
377
378 #[cfg(windows)]
379 fn owner(mut self, owner: HWND) -> Self {
380 let pl_attrs = platform_attrs(&mut self.attrs.inner).with_owner_window(owner.0);
381 self.attrs.inner = self
382 .attrs
383 .inner
384 .with_platform_attributes(Box::new(pl_attrs));
385 self
386 }
387
388 #[cfg(windows)]
389 fn parent(mut self, parent: HWND) -> Self {
390 if let Some(hwnd) = NonZeroIsize::new(parent.0 as isize) {
391 let handle = RawWindowHandle::Win32(winit::raw_window_handle::Win32WindowHandle::new(hwnd));
392 self.attrs.inner = unsafe { self.attrs.inner.with_parent_window(Some(handle)) };
394 }
395 self
396 }
397
398 #[cfg(windows)]
399 fn drag_and_drop(mut self, enabled: bool) -> Self {
400 let pl_attrs = platform_attrs(&mut self.attrs.inner).with_drag_and_drop(enabled);
401 self.attrs.inner = self
402 .attrs
403 .inner
404 .with_platform_attributes(Box::new(pl_attrs));
405 self
406 }
407
408 #[cfg(target_os = "macos")]
409 fn parent(mut self, parent: *mut std::ffi::c_void) -> Self {
410 use objc2::rc::Retained;
411 use objc2_app_kit::{NSView, NSWindow};
412
413 let Some(nswindow) = NonNull::new(parent) else {
414 return self;
415 };
416 let Some(nswindow) = (unsafe { Retained::<NSWindow>::from_raw(nswindow.as_ptr() as _) }) else {
417 return self;
418 };
419
420 let Some(nsview) = nswindow.contentView() else {
421 return self;
422 };
423 let nsview = Retained::<NSView>::into_raw(nsview);
424 let Some(nsview) = NonNull::new(nsview as _) else {
425 return self;
426 };
427
428 let handle = winit::raw_window_handle::AppKitWindowHandle::new(nsview);
429 let handle = RawWindowHandle::AppKit(handle);
430 self.attrs.inner = unsafe { self.attrs.inner.with_parent_window(Some(handle)) };
431
432 self
433 }
434
435 #[cfg(any(
436 target_os = "linux",
437 target_os = "dragonfly",
438 target_os = "freebsd",
439 target_os = "netbsd",
440 target_os = "openbsd"
441 ))]
442 fn transient_for(mut self, parent: *mut std::ffi::c_void) -> Self {
443 use gtk::glib::translate::FromGlibPtrFull;
444
445 self.attrs.transient_for =
448 Some(unsafe { gtk::Window::from_glib_full(parent as *mut gtk::ffi::GtkWindow) });
449 self
450 }
451
452 #[cfg(target_os = "macos")]
453 fn title_bar_style(mut self, style: TitleBarStyle) -> Self {
454 let pl_attrs = *platform_attrs(&mut self.attrs.inner);
455 let pl_attrs = match style {
456 TitleBarStyle::Visible => pl_attrs
457 .with_titlebar_transparent(false)
458 .with_fullsize_content_view(false),
459 TitleBarStyle::Transparent => pl_attrs
460 .with_titlebar_transparent(true)
461 .with_fullsize_content_view(false),
462 TitleBarStyle::Overlay => pl_attrs
463 .with_titlebar_transparent(true)
464 .with_fullsize_content_view(true),
465 _ => pl_attrs,
466 };
467 self.attrs.inner = self
468 .attrs
469 .inner
470 .with_platform_attributes(Box::new(pl_attrs));
471 self
472 }
473
474 #[cfg(target_os = "macos")]
475 fn traffic_light_position<P: Into<Position>>(mut self, position: P) -> Self {
476 self.attrs.traffic_light_position = Some(position.into());
477 self
478 }
479
480 #[cfg(target_os = "macos")]
481 fn hidden_title(mut self, hidden: bool) -> Self {
482 let pl_attrs = platform_attrs(&mut self.attrs.inner).with_title_hidden(hidden);
483 self.attrs.inner = self
484 .attrs
485 .inner
486 .with_platform_attributes(Box::new(pl_attrs));
487 self
488 }
489
490 #[cfg(target_os = "macos")]
491 fn tabbing_identifier(mut self, identifier: &str) -> Self {
492 let pl_attrs = platform_attrs(&mut self.attrs.inner).with_tabbing_identifier(identifier);
493 self.attrs.inner = self
494 .attrs
495 .inner
496 .with_platform_attributes(Box::new(pl_attrs));
497 self
498 }
499
500 fn theme(mut self, theme: Option<Theme>) -> Self {
501 self.attrs.inner = self
502 .attrs
503 .inner
504 .with_theme(tauri_theme_to_winit_theme(theme));
505 self
506 }
507
508 #[allow(unused_mut)]
509 fn window_classname<S: Into<String>>(mut self, _window_classname: S) -> Self {
510 #[cfg(windows)]
511 {
512 let pl_attrs =
513 platform_attrs(&mut self.attrs.inner).with_class_name(_window_classname.into());
514 self.attrs.inner = self
515 .attrs
516 .inner
517 .with_platform_attributes(Box::new(pl_attrs));
518 }
519
520 self
521 }
522
523 fn no_redirection_bitmap(#[allow(unused_mut)] mut self, _enable: bool) -> Self {
525 self
526 }
527
528 fn has_icon(&self) -> bool {
529 self.attrs.inner.window_icon.is_some()
530 }
531
532 fn get_theme(&self) -> Option<Theme> {
533 self
534 .attrs
535 .inner
536 .preferred_theme
537 .map(winit_theme_to_tauri_theme)
538 }
539}
540
541#[cfg(windows)]
542type PlatformAttributes = winit::platform::windows::WindowAttributesWindows;
543#[cfg(target_os = "macos")]
544type PlatformAttributes = winit::platform::macos::WindowAttributesMacOS;
545
546#[cfg(any(windows, target_os = "macos"))]
547fn platform_attrs(attrs: &mut WindowAttributes) -> Box<PlatformAttributes> {
548 attrs
549 .platform
550 .take()
551 .and_then(|attrs| attrs.cast::<PlatformAttributes>().ok())
552 .unwrap_or_default()
553}