Selaa lähdekoodia

feat(macos): `title_bar_style` and `hidden_title` window options, closes #2663 (#3965)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Kasper 2 vuotta sitten
vanhempi
sitoutus
321f3fed19

+ 6 - 0
.changes/hidden-title-macos.md

@@ -0,0 +1,6 @@
+---
+'tauri': minor
+"tauri-runtime-wry": minor
+---
+
+Add `hidden_title` option for macOS windows.

+ 6 - 0
.changes/transparent-titlebar-macos.md

@@ -0,0 +1,6 @@
+---
+'tauri': minor
+"tauri-runtime-wry": minor
+---
+
+Add `title_bar_style` option for macOS windows.

+ 35 - 4
core/tauri-runtime-wry/src/lib.rs

@@ -35,6 +35,8 @@ use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
 #[cfg(windows)]
 use wry::application::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};
 
+#[cfg(target_os = "macos")]
+use tauri_utils::TitleBarStyle;
 use tauri_utils::{config::WindowConfig, debug_eprintln, Theme};
 use uuid::Uuid;
 use wry::{
@@ -739,6 +741,13 @@ impl WindowBuilder for WindowBuilderWrapper {
       .skip_taskbar(config.skip_taskbar)
       .theme(config.theme);
 
+    #[cfg(target_os = "macos")]
+    {
+      window = window
+        .hidden_title(config.hidden_title)
+        .title_bar_style(config.title_bar_style);
+    }
+
     #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
     {
       window = window.transparent(config.transparent);
@@ -879,6 +888,32 @@ impl WindowBuilder for WindowBuilderWrapper {
     self
   }
 
+  #[cfg(target_os = "macos")]
+  fn title_bar_style(mut self, style: TitleBarStyle) -> Self {
+    match style {
+      TitleBarStyle::Visible => {
+        self.inner = self.inner.with_titlebar_transparent(false);
+        // Fixes rendering issue when resizing window with devtools open (https://github.com/tauri-apps/tauri/issues/3914)
+        self.inner = self.inner.with_fullsize_content_view(true);
+      }
+      TitleBarStyle::Transparent => {
+        self.inner = self.inner.with_titlebar_transparent(true);
+        self.inner = self.inner.with_fullsize_content_view(false);
+      }
+      TitleBarStyle::Overlay => {
+        self.inner = self.inner.with_titlebar_transparent(true);
+        self.inner = self.inner.with_fullsize_content_view(true);
+      }
+    }
+    self
+  }
+
+  #[cfg(target_os = "macos")]
+  fn hidden_title(mut self, hidden: bool) -> Self {
+    self.inner = self.inner.with_title_hidden(hidden);
+    self
+  }
+
   fn icon(mut self, icon: Icon) -> Result<Self> {
     self.inner = self
       .inner
@@ -2878,10 +2913,6 @@ fn create_webview<T: UserEvent>(
 
   let window_event_listeners = WindowEventListeners::default();
 
-  #[cfg(target_os = "macos")]
-  {
-    window_builder.inner = window_builder.inner.with_fullsize_content_view(true);
-  }
   #[cfg(windows)]
   {
     window_builder.inner = window_builder

+ 12 - 0
core/tauri-runtime/src/webview.rs

@@ -6,6 +6,8 @@
 
 use crate::{menu::Menu, window::DetachedWindow, Icon};
 
+#[cfg(target_os = "macos")]
+use tauri_utils::TitleBarStyle;
 use tauri_utils::{
   config::{WindowConfig, WindowUrl},
   Theme,
@@ -189,6 +191,16 @@ pub trait WindowBuilder: WindowBuilderBase {
   #[must_use]
   fn owner_window(self, owner: HWND) -> Self;
 
+  /// Hide the titlebar. Titlebar buttons will still be visible.
+  #[cfg(target_os = "macos")]
+  #[must_use]
+  fn title_bar_style(self, style: TitleBarStyle) -> Self;
+
+  /// Hide the window title.
+  #[cfg(target_os = "macos")]
+  #[must_use]
+  fn hidden_title(self, hidden: bool) -> Self;
+
   /// Forces a theme or uses the system settings if None was provided.
   fn theme(self, theme: Option<Theme>) -> Self;
 

+ 27 - 1
core/tauri-utils/src/config.rs

@@ -34,6 +34,8 @@ use std::{
 /// Items to help with parsing content into a [`Config`].
 pub mod parse;
 
+use crate::TitleBarStyle;
+
 pub use self::parse::parse;
 
 /// An URL to open on a Tauri webview window.
@@ -859,6 +861,12 @@ pub struct WindowConfig {
   pub skip_taskbar: bool,
   /// The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.
   pub theme: Option<crate::Theme>,
+  /// The style of the macOS title bar.
+  #[serde(default, alias = "title-bar-style")]
+  pub title_bar_style: TitleBarStyle,
+  /// If `true`, sets the window title to be hidden on macOS.
+  #[serde(default, alias = "hidden-title")]
+  pub hidden_title: bool,
 }
 
 impl Default for WindowConfig {
@@ -887,6 +895,8 @@ impl Default for WindowConfig {
       always_on_top: false,
       skip_taskbar: false,
       theme: None,
+      title_bar_style: Default::default(),
+      hidden_title: false,
     }
   }
 }
@@ -2934,6 +2944,18 @@ mod build {
     }
   }
 
+  impl ToTokens for crate::TitleBarStyle {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+      let prefix = quote! { ::tauri::utils::TitleBarStyle };
+
+      tokens.append_all(match self {
+        Self::Visible => quote! { #prefix::Visible },
+        Self::Transparent => quote! { #prefix::Transparent },
+        Self::Overlay => quote! { #prefix::Overlay },
+      })
+    }
+  }
+
   impl ToTokens for WindowConfig {
     fn to_tokens(&self, tokens: &mut TokenStream) {
       let label = str_lit(&self.label);
@@ -2959,6 +2981,8 @@ mod build {
       let always_on_top = self.always_on_top;
       let skip_taskbar = self.skip_taskbar;
       let theme = opt_lit(self.theme.as_ref());
+      let title_bar_style = &self.title_bar_style;
+      let hidden_title = self.hidden_title;
 
       literal_struct!(
         tokens,
@@ -2985,7 +3009,9 @@ mod build {
         decorations,
         always_on_top,
         skip_taskbar,
-        theme
+        theme,
+        title_bar_style,
+        hidden_title
       );
     }
   }

+ 62 - 0
core/tauri-utils/src/lib.rs

@@ -50,6 +50,68 @@ impl PackageInfo {
   }
 }
 
+/// How the window title bar should be displayed.
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
+pub enum TitleBarStyle {
+  /// A normal title bar.
+  Visible,
+  /// Makes the title bar transparent, so the window background color is shown instead.
+  ///
+  /// Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color.
+  Transparent,
+  /// Shows the title bar as a transparent overlay over the window's content.
+  ///
+  /// Keep in mind:
+  /// - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect.
+  /// - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus (https://github.com/tauri-apps/tauri/issues/4316).
+  /// - The color of the window title depends on the system theme.
+  Overlay,
+}
+
+impl Default for TitleBarStyle {
+  fn default() -> Self {
+    Self::Visible
+  }
+}
+
+impl Serialize for TitleBarStyle {
+  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+  where
+    S: Serializer,
+  {
+    serializer.serialize_str(self.to_string().as_ref())
+  }
+}
+
+impl<'de> Deserialize<'de> for TitleBarStyle {
+  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+  where
+    D: Deserializer<'de>,
+  {
+    let s = String::deserialize(deserializer)?;
+    Ok(match s.to_lowercase().as_str() {
+      "transparent" => Self::Transparent,
+      "overlay" => Self::Overlay,
+      _ => Self::Visible,
+    })
+  }
+}
+
+impl Display for TitleBarStyle {
+  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    write!(
+      f,
+      "{}",
+      match self {
+        Self::Visible => "Visible",
+        Self::Transparent => "Transparent",
+        Self::Overlay => "Overlay",
+      }
+    )
+  }
+}
+
 /// System theme.
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]

+ 2 - 0
core/tauri/src/lib.rs

@@ -206,6 +206,8 @@ pub use runtime::http;
 #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
 pub use runtime::{menu::NativeImage, ActivationPolicy};
 
+#[cfg(target_os = "macos")]
+pub use self::utils::TitleBarStyle;
 #[cfg(all(desktop, feature = "system-tray"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
 pub use {

+ 12 - 0
core/tauri/src/test/mock_runtime.rs

@@ -20,6 +20,8 @@ use tauri_runtime::{
   menu::{SystemTrayMenu, TrayHandle},
   SystemTray, SystemTrayEvent, TrayId,
 };
+#[cfg(target_os = "macos")]
+use tauri_utils::TitleBarStyle;
 use tauri_utils::{config::WindowConfig, Theme};
 use uuid::Uuid;
 
@@ -259,6 +261,16 @@ impl WindowBuilder for MockWindowBuilder {
     self
   }
 
+  #[cfg(target_os = "macos")]
+  fn title_bar_style(self, style: TitleBarStyle) -> Self {
+    self
+  }
+
+  #[cfg(target_os = "macos")]
+  fn hidden_title(self, transparent: bool) -> Self {
+    self
+  }
+
   fn theme(self, theme: Option<Theme>) -> Self {
     self
   }

+ 18 - 0
core/tauri/src/window.rs

@@ -8,6 +8,8 @@ pub(crate) mod menu;
 
 pub use menu::{MenuEvent, MenuHandle};
 
+#[cfg(target_os = "macos")]
+use crate::TitleBarStyle;
 use crate::{
   app::AppHandle,
   command::{CommandArg, CommandItem},
@@ -433,6 +435,22 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
     self
   }
 
+  /// Sets the [`TitleBarStyle`].
+  #[cfg(target_os = "macos")]
+  #[must_use]
+  pub fn title_bar_style(mut self, style: TitleBarStyle) -> Self {
+    self.window_builder = self.window_builder.title_bar_style(style);
+    self
+  }
+
+  /// Hide the window title.
+  #[cfg(target_os = "macos")]
+  #[must_use]
+  pub fn hidden_title(mut self, hidden: bool) -> Self {
+    self.window_builder = self.window_builder.hidden_title(hidden);
+    self
+  }
+
   // ------------------------------------------- Webview attributes -------------------------------------------
 
   /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,

+ 9 - 0
tooling/api/src/window.ts

@@ -67,6 +67,7 @@ import { emit, Event, listen, once } from './helpers/event'
 import { TauriEvent } from './event'
 
 type Theme = 'light' | 'dark'
+type TitleBarStyle = 'visible' | 'transparent' | 'overlay'
 
 /**
  * Allows you to retrieve information about a given monitor.
@@ -2033,6 +2034,14 @@ interface WindowOptions {
    * Only implemented on Windows and macOS 10.14+.
    */
   theme?: Theme
+  /**
+   * The style of the macOS title bar.
+   */
+  titleBarStyle?: TitleBarStyle
+  /**
+   * If `true`, sets the window title to be hidden on macOS.
+   */
+  hiddenTitle?: boolean
 }
 
 function mapMonitor(m: Monitor | null): Monitor | null {

+ 23 - 0
tooling/cli/schema.json

@@ -639,6 +639,20 @@
               "type": "null"
             }
           ]
+        },
+        "titleBarStyle": {
+          "description": "The style of the macOS title bar.",
+          "default": "Visible",
+          "allOf": [
+            {
+              "$ref": "#/definitions/TitleBarStyle"
+            }
+          ]
+        },
+        "hiddenTitle": {
+          "description": "Sets the window title to be hidden on macOS.",
+          "default": false,
+          "type": "boolean"
         }
       },
       "additionalProperties": false
@@ -665,6 +679,15 @@
         "Dark"
       ]
     },
+    "TitleBarStyle": {
+      "description": "How the window title bar should be displayed.",
+      "type": "string",
+      "enum": [
+        "Visible",
+        "Transparent",
+        "Overlay"
+      ]
+    },
     "CliConfig": {
       "description": "describes a CLI configuration",
       "type": "object",