diff --git a/.changes/traffic-light-builder.md b/.changes/traffic-light-builder.md new file mode 100644 index 000000000000..47c303628a19 --- /dev/null +++ b/.changes/traffic-light-builder.md @@ -0,0 +1,5 @@ +--- +"tauri": "minor:feat" +--- + +Added `WebviewWindowBuilder::traffic_light_position` to set the traffic light buttons position on macOS. diff --git a/.changes/traffic-light-config.md b/.changes/traffic-light-config.md new file mode 100644 index 000000000000..f28b883f83ac --- /dev/null +++ b/.changes/traffic-light-config.md @@ -0,0 +1,10 @@ +--- +"tauri-utils": "minor:feat" +"tauri-runtime": "minor:feat" +"tauri-runtime-wry": "minor:feat" +"tauri": "minor:feat" +"@tauri-apps/cli": "minor:feat" +"tauri-cli": "minor:feat" +--- + +Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS. diff --git a/.changes/traffic-light-runtime.md b/.changes/traffic-light-runtime.md new file mode 100644 index 000000000000..418fb22b53d4 --- /dev/null +++ b/.changes/traffic-light-runtime.md @@ -0,0 +1,6 @@ +--- +"tauri-runtime": "minor:feat" +"tauri-runtime-wry": "minor:feat" +--- + +Added `traffic_light_position` window builder method to set the traffic light buttons position on macOS. diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index 02195b6cce13..f903685cab6d 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -424,6 +424,17 @@ } ] }, + "trafficLightPosition": { + "description": "The position of the window controls on macOS.\n\n Requires titleBarStyle: Overlay and decorations: true.", + "anyOf": [ + { + "$ref": "#/definitions/LogicalPosition" + }, + { + "type": "null" + } + ] + }, "hiddenTitle": { "description": "If `true`, sets the window title to be hidden on macOS.", "default": false, @@ -600,6 +611,27 @@ } ] }, + "LogicalPosition": { + "description": "Position coordinates struct.", + "type": "object", + "required": [ + "x", + "y" + ], + "properties": { + "x": { + "description": "X coordinate.", + "type": "number", + "format": "double" + }, + "y": { + "description": "Y coordinate.", + "type": "number", + "format": "double" + } + }, + "additionalProperties": false + }, "WindowEffectsConfig": { "description": "The window effects configuration object", "type": "object", diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index 8682a1ca7fa7..c97ada060ecc 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -788,6 +788,11 @@ impl WindowBuilder for WindowBuilderWrapper { if let Some(identifier) = &config.tabbing_identifier { window = window.tabbing_identifier(identifier); } + if let Some(position) = &config.traffic_light_position { + window = window.traffic_light_position(tauri_runtime::dpi::LogicalPosition::new( + position.x, position.y, + )); + } } #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] @@ -1069,6 +1074,12 @@ impl WindowBuilder for WindowBuilderWrapper { self } + #[cfg(target_os = "macos")] + fn traffic_light_position>(mut self, position: P) -> Self { + self.inner = self.inner.with_traffic_light_inset(position.into()); + self + } + #[cfg(target_os = "macos")] fn hidden_title(mut self, hidden: bool) -> Self { self.inner = self.inner.with_title_hidden(hidden); @@ -1276,6 +1287,7 @@ pub enum WindowMessage { SetOverlayIcon(Option), SetProgressBar(ProgressBarState), SetTitleBarStyle(tauri_utils::TitleBarStyle), + SetTrafficLightPosition(Position), SetTheme(Option), SetBackgroundColor(Option), DragWindow, @@ -2194,6 +2206,16 @@ impl WindowDispatch for WryWindowDispatcher { ) } + fn set_traffic_light_position(&self, position: Position) -> Result<()> { + send_user_message( + &self.context, + Message::Window( + self.window_id, + WindowMessage::SetTrafficLightPosition(position), + ), + ) + } + fn set_theme(&self, theme: Option) -> Result<()> { send_user_message( &self.context, @@ -3248,6 +3270,10 @@ fn handle_user_message( } }; } + WindowMessage::SetTrafficLightPosition(_position) => { + #[cfg(target_os = "macos")] + window.set_traffic_light_inset(_position); + } WindowMessage::SetTheme(theme) => { window.set_theme(match theme { Some(Theme::Light) => Some(TaoTheme::Light), @@ -4451,6 +4477,14 @@ fn create_webview( } } + #[cfg(target_os = "macos")] + { + use wry::WebViewBuilderExtDarwin; + if let Some(position) = &webview_attributes.traffic_light_position { + webview_builder = webview_builder.with_traffic_light_inset(*position); + } + } + webview_builder = webview_builder.with_ipc_handler(create_ipc_handler( kind, window_id.clone(), diff --git a/crates/tauri-runtime/src/lib.rs b/crates/tauri-runtime/src/lib.rs index 77b4189d02f4..ce97fabdcf49 100644 --- a/crates/tauri-runtime/src/lib.rs +++ b/crates/tauri-runtime/src/lib.rs @@ -238,7 +238,7 @@ pub enum RunEvent { Resumed, /// Emitted when all of the event loop's input events have been processed and redraw processing is about to begin. /// - /// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the “main body” of your event loop. + /// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the "main body" of your event loop. MainEventsCleared, /// Emitted when the user wants to open the specified resource with the app. #[cfg(any(target_os = "macos", target_os = "ios"))] @@ -875,6 +875,15 @@ pub trait WindowDispatch: Debug + Clone + Send + Sync + Sized + 's /// - **Linux / Windows / iOS / Android:** Unsupported. fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()>; + /// Change the position of the window controls. Available on macOS only. + /// + /// Requires titleBarStyle: Overlay and decorations: true. + /// + /// ## Platform-specific + /// + /// - **Linux / Windows / iOS / Android:** Unsupported. + fn set_traffic_light_position(&self, position: Position) -> Result<()>; + /// Sets the theme for this window. /// /// ## Platform-specific diff --git a/crates/tauri-runtime/src/webview.rs b/crates/tauri-runtime/src/webview.rs index aef72b49b691..895c10141f28 100644 --- a/crates/tauri-runtime/src/webview.rs +++ b/crates/tauri-runtime/src/webview.rs @@ -217,6 +217,7 @@ pub struct WebviewAttributes { pub use_https_scheme: bool, pub devtools: Option, pub background_color: Option, + pub traffic_light_position: Option, pub background_throttling: Option, pub javascript_disabled: bool, } @@ -236,6 +237,13 @@ impl From<&WindowConfig> for WebviewAttributes { { builder = builder.transparent(config.transparent); } + #[cfg(target_os = "macos")] + { + if let Some(position) = &config.traffic_light_position { + builder = + builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into()); + } + } builder = builder.accept_first_mouse(config.accept_first_mouse); if !config.drag_drop_enabled { builder = builder.disable_drag_drop_handler(); @@ -286,6 +294,7 @@ impl WebviewAttributes { use_https_scheme: false, devtools: None, background_color: None, + traffic_light_position: None, background_throttling: None, javascript_disabled: false, } @@ -454,7 +463,20 @@ impl WebviewAttributes { self } - /// Change the default background throttling behaviour. + /// Change the position of the window controls. Available on macOS only. + /// + /// Requires titleBarStyle: Overlay and decorations: true. + /// + /// ## Platform-specific + /// + /// - **Linux / Windows / iOS / Android:** Unsupported. + #[must_use] + pub fn traffic_light_position(mut self, position: dpi::Position) -> Self { + self.traffic_light_position = Some(position); + self + } + + /// Change the default background throttling behavior. /// /// By default, browsers use a suspend policy that will throttle timers and even unload /// the whole tab (view) to free resources after roughly 5 minutes when a view became diff --git a/crates/tauri-runtime/src/window.rs b/crates/tauri-runtime/src/window.rs index dc843100da98..911bb6064ea6 100644 --- a/crates/tauri-runtime/src/window.rs +++ b/crates/tauri-runtime/src/window.rs @@ -423,6 +423,13 @@ pub trait WindowBuilder: WindowBuilderBase { #[must_use] fn title_bar_style(self, style: tauri_utils::TitleBarStyle) -> Self; + /// Change the position of the window controls on macOS. + /// + /// Requires titleBarStyle: Overlay and decorations: true. + #[cfg(target_os = "macos")] + #[must_use] + fn traffic_light_position>(self, position: P) -> Self; + /// Hide the window title. #[cfg(target_os = "macos")] #[must_use] diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index 02195b6cce13..f903685cab6d 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -424,6 +424,17 @@ } ] }, + "trafficLightPosition": { + "description": "The position of the window controls on macOS.\n\n Requires titleBarStyle: Overlay and decorations: true.", + "anyOf": [ + { + "$ref": "#/definitions/LogicalPosition" + }, + { + "type": "null" + } + ] + }, "hiddenTitle": { "description": "If `true`, sets the window title to be hidden on macOS.", "default": false, @@ -600,6 +611,27 @@ } ] }, + "LogicalPosition": { + "description": "Position coordinates struct.", + "type": "object", + "required": [ + "x", + "y" + ], + "properties": { + "x": { + "description": "X coordinate.", + "type": "number", + "format": "double" + }, + "y": { + "description": "Y coordinate.", + "type": "number", + "format": "double" + } + }, + "additionalProperties": false + }, "WindowEffectsConfig": { "description": "The window effects configuration object", "type": "object", diff --git a/crates/tauri-utils/src/config.rs b/crates/tauri-utils/src/config.rs index d066e774587b..031e90ee7a16 100644 --- a/crates/tauri-utils/src/config.rs +++ b/crates/tauri-utils/src/config.rs @@ -511,6 +511,17 @@ pub struct Position { pub y: u32, } +/// Position coordinates struct. +#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct LogicalPosition { + /// X coordinate. + pub x: f64, + /// Y coordinate. + pub y: f64, +} + /// Size of the window. #[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] @@ -1582,6 +1593,11 @@ pub struct WindowConfig { /// The style of the macOS title bar. #[serde(default, alias = "title-bar-style")] pub title_bar_style: TitleBarStyle, + /// The position of the window controls on macOS. + /// + /// Requires titleBarStyle: Overlay and decorations: true. + #[serde(default, alias = "traffic-light-position")] + pub traffic_light_position: Option, /// If `true`, sets the window title to be hidden on macOS. #[serde(default, alias = "hidden-title")] pub hidden_title: bool, @@ -1758,6 +1774,7 @@ impl Default for WindowConfig { window_classname: None, theme: None, title_bar_style: Default::default(), + traffic_light_position: None, hidden_title: false, accept_first_mouse: false, tabbing_identifier: None, @@ -2951,6 +2968,13 @@ mod build { } } + impl ToTokens for LogicalPosition { + fn to_tokens(&self, tokens: &mut TokenStream) { + let LogicalPosition { x, y } = self; + literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y) + } + } + impl ToTokens for crate::WindowEffect { fn to_tokens(&self, tokens: &mut TokenStream) { let prefix = quote! { ::tauri::utils::WindowEffect }; @@ -3037,6 +3061,7 @@ mod build { let window_classname = opt_str_lit(self.window_classname.as_ref()); let theme = opt_lit(self.theme.as_ref()); let title_bar_style = &self.title_bar_style; + let traffic_light_position = opt_lit(self.traffic_light_position.as_ref()); let hidden_title = self.hidden_title; let accept_first_mouse = self.accept_first_mouse; let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref()); @@ -3090,6 +3115,7 @@ mod build { window_classname, theme, title_bar_style, + traffic_light_position, hidden_title, accept_first_mouse, tabbing_identifier, diff --git a/crates/tauri/src/test/mock_runtime.rs b/crates/tauri/src/test/mock_runtime.rs index db1a2f0fabb6..98f71be23640 100644 --- a/crates/tauri/src/test/mock_runtime.rs +++ b/crates/tauri/src/test/mock_runtime.rs @@ -482,6 +482,11 @@ impl WindowBuilder for MockWindowBuilder { self } + #[cfg(target_os = "macos")] + fn traffic_light_position>(self, position: P) -> Self { + self + } + #[cfg(target_os = "macos")] fn hidden_title(self, transparent: bool) -> Self { self @@ -1014,6 +1019,10 @@ impl WindowDispatch for MockWindowDispatcher { Ok(()) } + fn set_traffic_light_position(&self, position: Position) -> Result<()> { + Ok(()) + } + fn set_size_constraints( &self, constraints: tauri_runtime::window::WindowSizeConstraints, diff --git a/crates/tauri/src/webview/webview_window.rs b/crates/tauri/src/webview/webview_window.rs index 223463eff6b9..2b6ef1af4c8e 100644 --- a/crates/tauri/src/webview/webview_window.rs +++ b/crates/tauri/src/webview/webview_window.rs @@ -706,6 +706,19 @@ impl<'a, R: Runtime, M: Manager> WebviewWindowBuilder<'a, R, M> { self } + /// Change the position of the window controls on macOS. + /// + /// Requires titleBarStyle: Overlay and decorations: true. + #[cfg(target_os = "macos")] + #[must_use] + pub fn traffic_light_position>(mut self, position: P) -> Self { + self.webview_builder.webview_attributes = self + .webview_builder + .webview_attributes + .traffic_light_position(position.into()); + self + } + /// Hide the window title. #[cfg(target_os = "macos")] #[must_use]