diff --git a/.changes/hidden-title-macos.md b/.changes/hidden-title-macos.md new file mode 100644 index 000000000000..2ff415c61946 --- /dev/null +++ b/.changes/hidden-title-macos.md @@ -0,0 +1,6 @@ +--- +'tauri': minor +"tauri-runtime-wry": minor +--- + +Add `hidden_title` option for macOS windows. diff --git a/.changes/transparent-titlebar-macos.md b/.changes/transparent-titlebar-macos.md new file mode 100644 index 000000000000..fba683818639 --- /dev/null +++ b/.changes/transparent-titlebar-macos.md @@ -0,0 +1,6 @@ +--- +'tauri': minor +"tauri-runtime-wry": minor +--- + +Add `title_bar_style` option for macOS windows. diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index edbfe9de1165..6eb8194f1ea3 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/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.inner = self .inner @@ -2878,10 +2913,6 @@ fn create_webview( 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 diff --git a/core/tauri-runtime/src/webview.rs b/core/tauri-runtime/src/webview.rs index cf3ce532eefc..7db22146692b 100644 --- a/core/tauri-runtime/src/webview.rs +++ b/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) -> Self; diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 76e49dfe3271..993481b74c6f 100644 --- a/core/tauri-utils/src/config.rs +++ b/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, + /// 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 ); } } diff --git a/core/tauri-utils/src/lib.rs b/core/tauri-utils/src/lib.rs index 976101bb508e..b63e90ff38aa 100644 --- a/core/tauri-utils/src/lib.rs +++ b/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(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +impl<'de> Deserialize<'de> for TitleBarStyle { + fn deserialize(deserializer: D) -> std::result::Result + 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))] diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index c84b37210b6b..73888eed03ae 100644 --- a/core/tauri/src/lib.rs +++ b/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 { diff --git a/core/tauri/src/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index 655c0206e877..cc531545928c 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/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) -> Self { self } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 19653779b3c2..e4b60f14d4f2 100644 --- a/core/tauri/src/window.rs +++ b/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, diff --git a/tooling/api/src/window.ts b/tooling/api/src/window.ts index 4017e095d539..06743721600a 100644 --- a/tooling/api/src/window.ts +++ b/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 { diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index 78510c913f0a..3e21db80590c 100644 --- a/tooling/cli/schema.json +++ b/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",