tauri_runtime/window.rs
1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! A layer between raw [`Runtime`] windows and Tauri.
6
7use crate::{
8 webview::{DetachedWebview, PendingWebview},
9 Icon, Runtime, UserEvent, WindowDispatch,
10};
11
12use dpi::PixelUnit;
13use serde::{Deserialize, Deserializer, Serialize};
14use tauri_utils::{
15 config::{Color, WindowConfig},
16 Theme,
17};
18#[cfg(windows)]
19use windows::Win32::Foundation::HWND;
20
21use std::{
22 hash::{Hash, Hasher},
23 marker::PhantomData,
24 path::PathBuf,
25 sync::mpsc::Sender,
26};
27
28/// An event from a window.
29#[derive(Debug, Clone)]
30pub enum WindowEvent {
31 /// The size of the window has changed. Contains the client area's new dimensions.
32 Resized(dpi::PhysicalSize<u32>),
33 /// The position of the window has changed. Contains the window's new position.
34 Moved(dpi::PhysicalPosition<i32>),
35 /// The window has been requested to close.
36 CloseRequested {
37 /// A signal sender. If a `true` value is emitted, the window won't be closed.
38 signal_tx: Sender<bool>,
39 },
40 /// The window has been destroyed.
41 Destroyed,
42 /// The window gained or lost focus.
43 ///
44 /// The parameter is true if the window has gained focus, and false if it has lost focus.
45 Focused(bool),
46 /// The window's scale factor has changed.
47 ///
48 /// The following user actions can cause DPI changes:
49 ///
50 /// - Changing the display's resolution.
51 /// - Changing the display's scale factor (e.g. in Control Panel on Windows).
52 /// - Moving the window to a display with a different scale factor.
53 ScaleFactorChanged {
54 /// The new scale factor.
55 scale_factor: f64,
56 /// The window inner size.
57 new_inner_size: dpi::PhysicalSize<u32>,
58 },
59 /// An event associated with the drag and drop action.
60 DragDrop(DragDropEvent),
61 /// The system window theme has changed.
62 ///
63 /// Applications might wish to react to this to change the theme of the content of the window when the system changes the window theme.
64 ThemeChanged(Theme),
65}
66
67/// An event from a window.
68#[derive(Debug, Clone)]
69pub enum WebviewEvent {
70 /// An event associated with the drag and drop action.
71 DragDrop(DragDropEvent),
72}
73
74/// The drag drop event payload.
75#[derive(Debug, Clone)]
76#[non_exhaustive]
77pub enum DragDropEvent {
78 /// A drag operation has entered the webview.
79 Enter {
80 /// List of paths that are being dragged onto the webview.
81 paths: Vec<PathBuf>,
82 /// The position of the mouse cursor.
83 position: dpi::PhysicalPosition<f64>,
84 },
85 /// A drag operation is moving over the webview.
86 Over {
87 /// The position of the mouse cursor.
88 position: dpi::PhysicalPosition<f64>,
89 },
90 /// The file(s) have been dropped onto the webview.
91 Drop {
92 /// List of paths that are being dropped onto the window.
93 paths: Vec<PathBuf>,
94 /// The position of the mouse cursor.
95 position: dpi::PhysicalPosition<f64>,
96 },
97 /// The drag operation has been cancelled or left the window.
98 Leave,
99}
100
101/// Describes the appearance of the mouse cursor.
102#[non_exhaustive]
103#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
104pub enum CursorIcon {
105 /// The platform-dependent default cursor.
106 #[default]
107 Default,
108 /// A simple crosshair.
109 Crosshair,
110 /// A hand (often used to indicate links in web browsers).
111 Hand,
112 /// Self explanatory.
113 Arrow,
114 /// Indicates something is to be moved.
115 Move,
116 /// Indicates text that may be selected or edited.
117 Text,
118 /// Program busy indicator.
119 Wait,
120 /// Help indicator (often rendered as a "?")
121 Help,
122 /// Progress indicator. Shows that processing is being done. But in contrast
123 /// with "Wait" the user may still interact with the program. Often rendered
124 /// as a spinning beach ball, or an arrow with a watch or hourglass.
125 Progress,
126
127 /// Cursor showing that something cannot be done.
128 NotAllowed,
129 ContextMenu,
130 Cell,
131 VerticalText,
132 Alias,
133 Copy,
134 NoDrop,
135 /// Indicates something can be grabbed.
136 Grab,
137 /// Indicates something is grabbed.
138 Grabbing,
139 AllScroll,
140 ZoomIn,
141 ZoomOut,
142
143 /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor
144 /// is used when the movement starts from the south-east corner of the box.
145 EResize,
146 NResize,
147 NeResize,
148 NwResize,
149 SResize,
150 SeResize,
151 SwResize,
152 WResize,
153 EwResize,
154 NsResize,
155 NeswResize,
156 NwseResize,
157 ColResize,
158 RowResize,
159}
160
161impl<'de> Deserialize<'de> for CursorIcon {
162 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
163 where
164 D: Deserializer<'de>,
165 {
166 let s = String::deserialize(deserializer)?;
167 Ok(match s.to_lowercase().as_str() {
168 "default" => CursorIcon::Default,
169 "crosshair" => CursorIcon::Crosshair,
170 "hand" => CursorIcon::Hand,
171 "arrow" => CursorIcon::Arrow,
172 "move" => CursorIcon::Move,
173 "text" => CursorIcon::Text,
174 "wait" => CursorIcon::Wait,
175 "help" => CursorIcon::Help,
176 "progress" => CursorIcon::Progress,
177 "notallowed" => CursorIcon::NotAllowed,
178 "contextmenu" => CursorIcon::ContextMenu,
179 "cell" => CursorIcon::Cell,
180 "verticaltext" => CursorIcon::VerticalText,
181 "alias" => CursorIcon::Alias,
182 "copy" => CursorIcon::Copy,
183 "nodrop" => CursorIcon::NoDrop,
184 "grab" => CursorIcon::Grab,
185 "grabbing" => CursorIcon::Grabbing,
186 "allscroll" => CursorIcon::AllScroll,
187 "zoomin" => CursorIcon::ZoomIn,
188 "zoomout" => CursorIcon::ZoomOut,
189 "eresize" => CursorIcon::EResize,
190 "nresize" => CursorIcon::NResize,
191 "neresize" => CursorIcon::NeResize,
192 "nwresize" => CursorIcon::NwResize,
193 "sresize" => CursorIcon::SResize,
194 "seresize" => CursorIcon::SeResize,
195 "swresize" => CursorIcon::SwResize,
196 "wresize" => CursorIcon::WResize,
197 "ewresize" => CursorIcon::EwResize,
198 "nsresize" => CursorIcon::NsResize,
199 "neswresize" => CursorIcon::NeswResize,
200 "nwseresize" => CursorIcon::NwseResize,
201 "colresize" => CursorIcon::ColResize,
202 "rowresize" => CursorIcon::RowResize,
203 _ => CursorIcon::Default,
204 })
205 }
206}
207
208/// Window size constraints
209#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)]
210#[serde(rename_all = "camelCase")]
211pub struct WindowSizeConstraints {
212 /// The minimum width a window can be, If this is `None`, the window will have no minimum width.
213 ///
214 /// The default is `None`.
215 pub min_width: Option<PixelUnit>,
216 /// The minimum height a window can be, If this is `None`, the window will have no minimum height.
217 ///
218 /// The default is `None`.
219 pub min_height: Option<PixelUnit>,
220 /// The maximum width a window can be, If this is `None`, the window will have no maximum width.
221 ///
222 /// The default is `None`.
223 pub max_width: Option<PixelUnit>,
224 /// The maximum height a window can be, If this is `None`, the window will have no maximum height.
225 ///
226 /// The default is `None`.
227 pub max_height: Option<PixelUnit>,
228}
229
230/// Do **NOT** implement this trait except for use in a custom [`Runtime`]
231///
232/// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation.
233pub trait WindowBuilderBase: std::fmt::Debug + Clone + Sized {}
234
235/// A builder for all attributes related to a single window.
236///
237/// This trait is only meant to be implemented by a custom [`Runtime`]
238/// and not by applications.
239pub trait WindowBuilder: WindowBuilderBase {
240 /// Initializes a new window attributes builder.
241 fn new() -> Self;
242
243 /// Initializes a new window builder from a [`WindowConfig`]
244 fn with_config(config: &WindowConfig) -> Self;
245
246 /// Show window in the center of the screen.
247 #[must_use]
248 fn center(self) -> Self;
249
250 /// The initial position of the window's.
251 #[must_use]
252 fn position(self, x: f64, y: f64) -> Self;
253
254 /// Window size.
255 #[must_use]
256 fn inner_size(self, width: f64, height: f64) -> Self;
257
258 /// Window min inner size.
259 #[must_use]
260 fn min_inner_size(self, min_width: f64, min_height: f64) -> Self;
261
262 /// Window max inner size.
263 #[must_use]
264 fn max_inner_size(self, max_width: f64, max_height: f64) -> Self;
265
266 /// Window inner size constraints.
267 #[must_use]
268 fn inner_size_constraints(self, constraints: WindowSizeConstraints) -> Self;
269
270 /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size) on creation
271 ///
272 /// ## Platform-specific
273 ///
274 /// - **iOS / Android:** Unsupported.
275 #[must_use]
276 fn prevent_overflow(self) -> Self;
277
278 /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)
279 /// on creation with a margin
280 ///
281 /// ## Platform-specific
282 ///
283 /// - **iOS / Android:** Unsupported.
284 #[must_use]
285 fn prevent_overflow_with_margin(self, margin: dpi::Size) -> Self;
286
287 /// Whether the window is resizable or not.
288 /// When resizable is set to false, native window's maximize button is automatically disabled.
289 #[must_use]
290 fn resizable(self, resizable: bool) -> Self;
291
292 /// Whether the window's native maximize button is enabled or not.
293 /// If resizable is set to false, this setting is ignored.
294 ///
295 /// ## Platform-specific
296 ///
297 /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode.
298 /// - **Linux / iOS / Android:** Unsupported.
299 #[must_use]
300 fn maximizable(self, maximizable: bool) -> Self;
301
302 /// Whether the window's native minimize button is enabled or not.
303 ///
304 /// ## Platform-specific
305 ///
306 /// - **Linux / iOS / Android:** Unsupported.
307 #[must_use]
308 fn minimizable(self, minimizable: bool) -> Self;
309
310 /// Whether the window's native close button is enabled or not.
311 ///
312 /// ## Platform-specific
313 ///
314 /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button.
315 /// Depending on the system, this function may not have any effect when called on a window that is already visible"
316 /// - **iOS / Android:** Unsupported.
317 #[must_use]
318 fn closable(self, closable: bool) -> Self;
319
320 /// The title of the window in the title bar.
321 #[must_use]
322 fn title<S: Into<String>>(self, title: S) -> Self;
323
324 /// Whether to start the window in fullscreen or not.
325 #[must_use]
326 fn fullscreen(self, fullscreen: bool) -> Self;
327
328 /// Whether the window will be initially focused or not.
329 #[must_use]
330 fn focused(self, focused: bool) -> Self;
331
332 /// Whether the window will be focusable or not.
333 #[must_use]
334 fn focusable(self, focusable: bool) -> Self;
335
336 /// Whether the window should be maximized upon creation.
337 #[must_use]
338 fn maximized(self, maximized: bool) -> Self;
339
340 /// Whether the window should be immediately visible upon creation.
341 #[must_use]
342 fn visible(self, visible: bool) -> Self;
343
344 /// Whether the window should be transparent. If this is true, writing colors
345 /// with alpha values different than `1.0` will produce a transparent window.
346 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
347 #[cfg_attr(
348 docsrs,
349 doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api")))
350 )]
351 #[must_use]
352 fn transparent(self, transparent: bool) -> Self;
353
354 /// Whether the window should have borders and bars.
355 #[must_use]
356 fn decorations(self, decorations: bool) -> Self;
357
358 /// Whether the window should always be below other windows.
359 #[must_use]
360 fn always_on_bottom(self, always_on_bottom: bool) -> Self;
361
362 /// Whether the window should always be on top of other windows.
363 #[must_use]
364 fn always_on_top(self, always_on_top: bool) -> Self;
365
366 /// Whether the window should be visible on all workspaces or virtual desktops.
367 #[must_use]
368 fn visible_on_all_workspaces(self, visible_on_all_workspaces: bool) -> Self;
369
370 /// Prevents the window contents from being captured by other apps.
371 #[must_use]
372 fn content_protected(self, protected: bool) -> Self;
373
374 /// Sets the window icon.
375 fn icon(self, icon: Icon) -> crate::Result<Self>;
376
377 /// Sets whether or not the window icon should be added to the taskbar.
378 #[must_use]
379 fn skip_taskbar(self, skip: bool) -> Self;
380
381 /// Set the window background color.
382 #[must_use]
383 fn background_color(self, color: Color) -> Self;
384
385 /// Sets whether or not the window has shadow.
386 ///
387 /// ## Platform-specific
388 ///
389 /// - **Windows:**
390 /// - `false` has no effect on decorated window, shadows are always ON.
391 /// - `true` will make undecorated window have a 1px white border,
392 /// and on Windows 11, it will have a rounded corners.
393 /// - **Linux:** Unsupported.
394 #[must_use]
395 fn shadow(self, enable: bool) -> Self;
396
397 /// Set an owner to the window to be created.
398 ///
399 /// From MSDN:
400 /// - An owned window is always above its owner in the z-order.
401 /// - The system automatically destroys an owned window when its owner is destroyed.
402 /// - An owned window is hidden when its owner is minimized.
403 ///
404 /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
405 #[cfg(windows)]
406 #[must_use]
407 fn owner(self, owner: HWND) -> Self;
408
409 /// Sets a parent to the window to be created.
410 ///
411 /// A child window has the WS_CHILD style and is confined to the client area of its parent window.
412 ///
413 /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
414 #[cfg(windows)]
415 #[must_use]
416 fn parent(self, parent: HWND) -> Self;
417
418 /// Sets a parent to the window to be created.
419 ///
420 /// See <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>
421 #[cfg(target_os = "macos")]
422 #[must_use]
423 fn parent(self, parent: *mut std::ffi::c_void) -> Self;
424
425 /// Sets the window to be created transient for parent.
426 ///
427 /// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
428 #[cfg(any(
429 target_os = "linux",
430 target_os = "dragonfly",
431 target_os = "freebsd",
432 target_os = "netbsd",
433 target_os = "openbsd"
434 ))]
435 fn transient_for(self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self;
436
437 /// Enables or disables drag and drop support.
438 #[cfg(windows)]
439 #[must_use]
440 fn drag_and_drop(self, enabled: bool) -> Self;
441
442 /// Hide the titlebar. Titlebar buttons will still be visible.
443 #[cfg(target_os = "macos")]
444 #[must_use]
445 fn title_bar_style(self, style: tauri_utils::TitleBarStyle) -> Self;
446
447 /// Change the position of the window controls on macOS.
448 ///
449 /// Requires titleBarStyle: Overlay and decorations: true.
450 #[cfg(target_os = "macos")]
451 #[must_use]
452 fn traffic_light_position<P: Into<dpi::Position>>(self, position: P) -> Self;
453
454 /// Hide the window title.
455 #[cfg(target_os = "macos")]
456 #[must_use]
457 fn hidden_title(self, hidden: bool) -> Self;
458
459 /// Defines the window [tabbing identifier] for macOS.
460 ///
461 /// Windows with matching tabbing identifiers will be grouped together.
462 /// If the tabbing identifier is not set, automatic tabbing will be disabled.
463 ///
464 /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
465 #[cfg(target_os = "macos")]
466 #[must_use]
467 fn tabbing_identifier(self, identifier: &str) -> Self;
468
469 /// Forces a theme or uses the system settings if None was provided.
470 fn theme(self, theme: Option<Theme>) -> Self;
471
472 /// Whether the icon was set or not.
473 fn has_icon(&self) -> bool;
474
475 fn get_theme(&self) -> Option<Theme>;
476
477 /// Sets custom name for Windows' window class. **Windows only**.
478 #[must_use]
479 fn window_classname<S: Into<String>>(self, window_classname: S) -> Self;
480}
481
482/// A window that has yet to be built.
483pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
484 /// The label that the window will be named.
485 pub label: String,
486
487 /// The [`WindowBuilder`] that the window will be created with.
488 pub window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
489
490 /// The webview that gets added to the window. Optional in case you want to use child webviews or other window content instead.
491 pub webview: Option<PendingWebview<T, R>>,
492}
493
494pub fn is_label_valid(label: &str) -> bool {
495 label
496 .chars()
497 .all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_')
498}
499
500pub fn assert_label_is_valid(label: &str) {
501 assert!(
502 is_label_valid(label),
503 "Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`."
504 );
505}
506
507impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
508 /// Create a new [`PendingWindow`] with a label from the given [`WindowBuilder`].
509 pub fn new(
510 window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
511 label: impl Into<String>,
512 ) -> crate::Result<Self> {
513 let label = label.into();
514 if !is_label_valid(&label) {
515 Err(crate::Error::InvalidWindowLabel)
516 } else {
517 Ok(Self {
518 window_builder,
519 label,
520 webview: None,
521 })
522 }
523 }
524
525 /// Sets a webview to be created on the window.
526 pub fn set_webview(&mut self, webview: PendingWebview<T, R>) -> &mut Self {
527 self.webview.replace(webview);
528 self
529 }
530}
531
532/// Identifier of a window.
533#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
534pub struct WindowId(u32);
535
536impl From<u32> for WindowId {
537 fn from(value: u32) -> Self {
538 Self(value)
539 }
540}
541
542/// A window that is not yet managed by Tauri.
543#[derive(Debug)]
544pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
545 /// The identifier of the window.
546 pub id: WindowId,
547 /// Name of the window
548 pub label: String,
549
550 /// The [`WindowDispatch`] associated with the window.
551 pub dispatcher: R::WindowDispatcher,
552
553 /// The webview dispatcher in case this window has an attached webview.
554 pub webview: Option<DetachedWindowWebview<T, R>>,
555}
556
557/// A detached webview associated with a window.
558#[derive(Debug)]
559pub struct DetachedWindowWebview<T: UserEvent, R: Runtime<T>> {
560 pub webview: DetachedWebview<T, R>,
561 pub use_https_scheme: bool,
562}
563
564impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindowWebview<T, R> {
565 fn clone(&self) -> Self {
566 Self {
567 webview: self.webview.clone(),
568 use_https_scheme: self.use_https_scheme,
569 }
570 }
571}
572
573impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
574 fn clone(&self) -> Self {
575 Self {
576 id: self.id,
577 label: self.label.clone(),
578 dispatcher: self.dispatcher.clone(),
579 webview: self.webview.clone(),
580 }
581 }
582}
583
584impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWindow<T, R> {
585 /// Only use the [`DetachedWindow`]'s label to represent its hash.
586 fn hash<H: Hasher>(&self, state: &mut H) {
587 self.label.hash(state)
588 }
589}
590
591impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWindow<T, R> {}
592impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWindow<T, R> {
593 /// Only use the [`DetachedWindow`]'s label to compare equality.
594 fn eq(&self, other: &Self) -> bool {
595 self.label.eq(&other.label)
596 }
597}
598
599/// A raw window type that contains fields to access
600/// the HWND on Windows, gtk::ApplicationWindow on Linux
601pub struct RawWindow<'a> {
602 #[cfg(windows)]
603 pub hwnd: isize,
604 #[cfg(any(
605 target_os = "linux",
606 target_os = "dragonfly",
607 target_os = "freebsd",
608 target_os = "netbsd",
609 target_os = "openbsd"
610 ))]
611 pub gtk_window: &'a gtk::ApplicationWindow,
612 #[cfg(any(
613 target_os = "linux",
614 target_os = "dragonfly",
615 target_os = "freebsd",
616 target_os = "netbsd",
617 target_os = "openbsd"
618 ))]
619 pub default_vbox: Option<&'a gtk::Box>,
620 pub _marker: &'a PhantomData<()>,
621}