Skip to content

feat: traffic light position #12366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/traffic-light-builder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": "minor:feat"
---

Added `WebviewWindowBuilder::traffic_light_position` to set the traffic light buttons position on macOS.
10 changes: 10 additions & 0 deletions .changes/traffic-light-config.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions .changes/traffic-light-runtime.md
Original file line number Diff line number Diff line change
@@ -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.
32 changes: 32 additions & 0 deletions crates/tauri-cli/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down
34 changes: 34 additions & 0 deletions crates/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand Down Expand Up @@ -1069,6 +1074,12 @@ impl WindowBuilder for WindowBuilderWrapper {
self
}

#[cfg(target_os = "macos")]
fn traffic_light_position<P: Into<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);
Expand Down Expand Up @@ -1276,6 +1287,7 @@ pub enum WindowMessage {
SetOverlayIcon(Option<TaoIcon>),
SetProgressBar(ProgressBarState),
SetTitleBarStyle(tauri_utils::TitleBarStyle),
SetTrafficLightPosition(Position),
SetTheme(Option<Theme>),
SetBackgroundColor(Option<Color>),
DragWindow,
Expand Down Expand Up @@ -2194,6 +2206,16 @@ impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
)
}

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<Theme>) -> Result<()> {
send_user_message(
&self.context,
Expand Down Expand Up @@ -3248,6 +3270,10 @@ fn handle_user_message<T: UserEvent>(
}
};
}
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),
Expand Down Expand Up @@ -4451,6 +4477,14 @@ fn create_webview<T: UserEvent>(
}
}

#[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(),
Expand Down
11 changes: 10 additions & 1 deletion crates/tauri-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ pub enum RunEvent<T: UserEvent> {
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"))]
Expand Down Expand Up @@ -875,6 +875,15 @@ pub trait WindowDispatch<T: UserEvent>: 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<()>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably should be gated with #[cfg(target_os = "macos")]?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the functions here are gated 🤷

Copy link
Contributor

@Legend-Master Legend-Master Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, gtk_window and default_vbox are (because of the dependency), anyways, just thought it's kinda weird when I updated the tauri-runtime-verso and seeing this not being gated while the other ones are, maybe we should start gating these in v3 like what we did for the other things (like set_activation_policy in Runtime and RuntimeHandle)


/// Sets the theme for this window.
///
/// ## Platform-specific
Expand Down
24 changes: 23 additions & 1 deletion crates/tauri-runtime/src/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ pub struct WebviewAttributes {
pub use_https_scheme: bool,
pub devtools: Option<bool>,
pub background_color: Option<Color>,
pub traffic_light_position: Option<dpi::Position>,
pub background_throttling: Option<BackgroundThrottlingPolicy>,
pub javascript_disabled: bool,
}
Expand All @@ -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();
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions crates/tauri-runtime/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<P: Into<dpi::Position>>(self, position: P) -> Self;

/// Hide the window title.
#[cfg(target_os = "macos")]
#[must_use]
Expand Down
32 changes: 32 additions & 0 deletions crates/tauri-schema-generator/schemas/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down
26 changes: 26 additions & 0 deletions crates/tauri-utils/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down Expand Up @@ -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<LogicalPosition>,
/// If `true`, sets the window title to be hidden on macOS.
#[serde(default, alias = "hidden-title")]
pub hidden_title: bool,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -3090,6 +3115,7 @@ mod build {
window_classname,
theme,
title_bar_style,
traffic_light_position,
hidden_title,
accept_first_mouse,
tabbing_identifier,
Expand Down
9 changes: 9 additions & 0 deletions crates/tauri/src/test/mock_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,11 @@ impl WindowBuilder for MockWindowBuilder {
self
}

#[cfg(target_os = "macos")]
fn traffic_light_position<P: Into<Position>>(self, position: P) -> Self {
self
}

#[cfg(target_os = "macos")]
fn hidden_title(self, transparent: bool) -> Self {
self
Expand Down Expand Up @@ -1014,6 +1019,10 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
Ok(())
}

fn set_traffic_light_position(&self, position: Position) -> Result<()> {
Ok(())
}

fn set_size_constraints(
&self,
constraints: tauri_runtime::window::WindowSizeConstraints,
Expand Down
13 changes: 13 additions & 0 deletions crates/tauri/src/webview/webview_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,19 @@ impl<'a, R: Runtime, M: Manager<R>> 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<P: Into<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]
Expand Down