Skip to main content

tauri_runtime_cef/
window_builder.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use 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    // TODO
250    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      // SAFETY: Tauri passes a live parent HWND owned by the application.
393      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    // SAFETY: `transient_for` receives the parent as transfer full, so the wrapper adopts the
446    // reference and releases it when the builder (or the window it creates) is dropped.
447    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  // TODO
524  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}