Skip to content

Commit 30f5a15

Browse files
feat: traffic light position (#12366)
* moving to macbook * that was so weird to implement * rm patch * Discard changes to Cargo.lock * Create change-pr-12366.md * add missing builder fn * remove test * split change files --------- Co-authored-by: Lucas Nogueira <[email protected]>
1 parent 08de8a1 commit 30f5a15

File tree

12 files changed

+207
-2
lines changed

12 files changed

+207
-2
lines changed

.changes/traffic-light-builder.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": "minor:feat"
3+
---
4+
5+
Added `WebviewWindowBuilder::traffic_light_position` to set the traffic light buttons position on macOS.

.changes/traffic-light-config.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"tauri-utils": "minor:feat"
3+
"tauri-runtime": "minor:feat"
4+
"tauri-runtime-wry": "minor:feat"
5+
"tauri": "minor:feat"
6+
"@tauri-apps/cli": "minor:feat"
7+
"tauri-cli": "minor:feat"
8+
---
9+
10+
Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS.

.changes/traffic-light-runtime.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-runtime": "minor:feat"
3+
"tauri-runtime-wry": "minor:feat"
4+
---
5+
6+
Added `traffic_light_position` window builder method to set the traffic light buttons position on macOS.

crates/tauri-cli/config.schema.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,17 @@
424424
}
425425
]
426426
},
427+
"trafficLightPosition": {
428+
"description": "The position of the window controls on macOS.\n\n Requires titleBarStyle: Overlay and decorations: true.",
429+
"anyOf": [
430+
{
431+
"$ref": "#/definitions/LogicalPosition"
432+
},
433+
{
434+
"type": "null"
435+
}
436+
]
437+
},
427438
"hiddenTitle": {
428439
"description": "If `true`, sets the window title to be hidden on macOS.",
429440
"default": false,
@@ -600,6 +611,27 @@
600611
}
601612
]
602613
},
614+
"LogicalPosition": {
615+
"description": "Position coordinates struct.",
616+
"type": "object",
617+
"required": [
618+
"x",
619+
"y"
620+
],
621+
"properties": {
622+
"x": {
623+
"description": "X coordinate.",
624+
"type": "number",
625+
"format": "double"
626+
},
627+
"y": {
628+
"description": "Y coordinate.",
629+
"type": "number",
630+
"format": "double"
631+
}
632+
},
633+
"additionalProperties": false
634+
},
603635
"WindowEffectsConfig": {
604636
"description": "The window effects configuration object",
605637
"type": "object",

crates/tauri-runtime-wry/src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,11 @@ impl WindowBuilder for WindowBuilderWrapper {
788788
if let Some(identifier) = &config.tabbing_identifier {
789789
window = window.tabbing_identifier(identifier);
790790
}
791+
if let Some(position) = &config.traffic_light_position {
792+
window = window.traffic_light_position(tauri_runtime::dpi::LogicalPosition::new(
793+
position.x, position.y,
794+
));
795+
}
791796
}
792797

793798
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
@@ -1069,6 +1074,12 @@ impl WindowBuilder for WindowBuilderWrapper {
10691074
self
10701075
}
10711076

1077+
#[cfg(target_os = "macos")]
1078+
fn traffic_light_position<P: Into<Position>>(mut self, position: P) -> Self {
1079+
self.inner = self.inner.with_traffic_light_inset(position.into());
1080+
self
1081+
}
1082+
10721083
#[cfg(target_os = "macos")]
10731084
fn hidden_title(mut self, hidden: bool) -> Self {
10741085
self.inner = self.inner.with_title_hidden(hidden);
@@ -1276,6 +1287,7 @@ pub enum WindowMessage {
12761287
SetOverlayIcon(Option<TaoIcon>),
12771288
SetProgressBar(ProgressBarState),
12781289
SetTitleBarStyle(tauri_utils::TitleBarStyle),
1290+
SetTrafficLightPosition(Position),
12791291
SetTheme(Option<Theme>),
12801292
SetBackgroundColor(Option<Color>),
12811293
DragWindow,
@@ -2194,6 +2206,16 @@ impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
21942206
)
21952207
}
21962208

2209+
fn set_traffic_light_position(&self, position: Position) -> Result<()> {
2210+
send_user_message(
2211+
&self.context,
2212+
Message::Window(
2213+
self.window_id,
2214+
WindowMessage::SetTrafficLightPosition(position),
2215+
),
2216+
)
2217+
}
2218+
21972219
fn set_theme(&self, theme: Option<Theme>) -> Result<()> {
21982220
send_user_message(
21992221
&self.context,
@@ -3248,6 +3270,10 @@ fn handle_user_message<T: UserEvent>(
32483270
}
32493271
};
32503272
}
3273+
WindowMessage::SetTrafficLightPosition(_position) => {
3274+
#[cfg(target_os = "macos")]
3275+
window.set_traffic_light_inset(_position);
3276+
}
32513277
WindowMessage::SetTheme(theme) => {
32523278
window.set_theme(match theme {
32533279
Some(Theme::Light) => Some(TaoTheme::Light),
@@ -4451,6 +4477,14 @@ fn create_webview<T: UserEvent>(
44514477
}
44524478
}
44534479

4480+
#[cfg(target_os = "macos")]
4481+
{
4482+
use wry::WebViewBuilderExtDarwin;
4483+
if let Some(position) = &webview_attributes.traffic_light_position {
4484+
webview_builder = webview_builder.with_traffic_light_inset(*position);
4485+
}
4486+
}
4487+
44544488
webview_builder = webview_builder.with_ipc_handler(create_ipc_handler(
44554489
kind,
44564490
window_id.clone(),

crates/tauri-runtime/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ pub enum RunEvent<T: UserEvent> {
238238
Resumed,
239239
/// Emitted when all of the event loop's input events have been processed and redraw processing is about to begin.
240240
///
241-
/// 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.
241+
/// 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.
242242
MainEventsCleared,
243243
/// Emitted when the user wants to open the specified resource with the app.
244244
#[cfg(any(target_os = "macos", target_os = "ios"))]
@@ -875,6 +875,15 @@ pub trait WindowDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 's
875875
/// - **Linux / Windows / iOS / Android:** Unsupported.
876876
fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()>;
877877

878+
/// Change the position of the window controls. Available on macOS only.
879+
///
880+
/// Requires titleBarStyle: Overlay and decorations: true.
881+
///
882+
/// ## Platform-specific
883+
///
884+
/// - **Linux / Windows / iOS / Android:** Unsupported.
885+
fn set_traffic_light_position(&self, position: Position) -> Result<()>;
886+
878887
/// Sets the theme for this window.
879888
///
880889
/// ## Platform-specific

crates/tauri-runtime/src/webview.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ pub struct WebviewAttributes {
217217
pub use_https_scheme: bool,
218218
pub devtools: Option<bool>,
219219
pub background_color: Option<Color>,
220+
pub traffic_light_position: Option<dpi::Position>,
220221
pub background_throttling: Option<BackgroundThrottlingPolicy>,
221222
pub javascript_disabled: bool,
222223
}
@@ -236,6 +237,13 @@ impl From<&WindowConfig> for WebviewAttributes {
236237
{
237238
builder = builder.transparent(config.transparent);
238239
}
240+
#[cfg(target_os = "macos")]
241+
{
242+
if let Some(position) = &config.traffic_light_position {
243+
builder =
244+
builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into());
245+
}
246+
}
239247
builder = builder.accept_first_mouse(config.accept_first_mouse);
240248
if !config.drag_drop_enabled {
241249
builder = builder.disable_drag_drop_handler();
@@ -286,6 +294,7 @@ impl WebviewAttributes {
286294
use_https_scheme: false,
287295
devtools: None,
288296
background_color: None,
297+
traffic_light_position: None,
289298
background_throttling: None,
290299
javascript_disabled: false,
291300
}
@@ -454,7 +463,20 @@ impl WebviewAttributes {
454463
self
455464
}
456465

457-
/// Change the default background throttling behaviour.
466+
/// Change the position of the window controls. Available on macOS only.
467+
///
468+
/// Requires titleBarStyle: Overlay and decorations: true.
469+
///
470+
/// ## Platform-specific
471+
///
472+
/// - **Linux / Windows / iOS / Android:** Unsupported.
473+
#[must_use]
474+
pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {
475+
self.traffic_light_position = Some(position);
476+
self
477+
}
478+
479+
/// Change the default background throttling behavior.
458480
///
459481
/// By default, browsers use a suspend policy that will throttle timers and even unload
460482
/// the whole tab (view) to free resources after roughly 5 minutes when a view became

crates/tauri-runtime/src/window.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,13 @@ pub trait WindowBuilder: WindowBuilderBase {
423423
#[must_use]
424424
fn title_bar_style(self, style: tauri_utils::TitleBarStyle) -> Self;
425425

426+
/// Change the position of the window controls on macOS.
427+
///
428+
/// Requires titleBarStyle: Overlay and decorations: true.
429+
#[cfg(target_os = "macos")]
430+
#[must_use]
431+
fn traffic_light_position<P: Into<dpi::Position>>(self, position: P) -> Self;
432+
426433
/// Hide the window title.
427434
#[cfg(target_os = "macos")]
428435
#[must_use]

crates/tauri-schema-generator/schemas/config.schema.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,17 @@
424424
}
425425
]
426426
},
427+
"trafficLightPosition": {
428+
"description": "The position of the window controls on macOS.\n\n Requires titleBarStyle: Overlay and decorations: true.",
429+
"anyOf": [
430+
{
431+
"$ref": "#/definitions/LogicalPosition"
432+
},
433+
{
434+
"type": "null"
435+
}
436+
]
437+
},
427438
"hiddenTitle": {
428439
"description": "If `true`, sets the window title to be hidden on macOS.",
429440
"default": false,
@@ -600,6 +611,27 @@
600611
}
601612
]
602613
},
614+
"LogicalPosition": {
615+
"description": "Position coordinates struct.",
616+
"type": "object",
617+
"required": [
618+
"x",
619+
"y"
620+
],
621+
"properties": {
622+
"x": {
623+
"description": "X coordinate.",
624+
"type": "number",
625+
"format": "double"
626+
},
627+
"y": {
628+
"description": "Y coordinate.",
629+
"type": "number",
630+
"format": "double"
631+
}
632+
},
633+
"additionalProperties": false
634+
},
603635
"WindowEffectsConfig": {
604636
"description": "The window effects configuration object",
605637
"type": "object",

crates/tauri-utils/src/config.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,17 @@ pub struct Position {
511511
pub y: u32,
512512
}
513513

514+
/// Position coordinates struct.
515+
#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)]
516+
#[cfg_attr(feature = "schema", derive(JsonSchema))]
517+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
518+
pub struct LogicalPosition {
519+
/// X coordinate.
520+
pub x: f64,
521+
/// Y coordinate.
522+
pub y: f64,
523+
}
524+
514525
/// Size of the window.
515526
#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
516527
#[cfg_attr(feature = "schema", derive(JsonSchema))]
@@ -1582,6 +1593,11 @@ pub struct WindowConfig {
15821593
/// The style of the macOS title bar.
15831594
#[serde(default, alias = "title-bar-style")]
15841595
pub title_bar_style: TitleBarStyle,
1596+
/// The position of the window controls on macOS.
1597+
///
1598+
/// Requires titleBarStyle: Overlay and decorations: true.
1599+
#[serde(default, alias = "traffic-light-position")]
1600+
pub traffic_light_position: Option<LogicalPosition>,
15851601
/// If `true`, sets the window title to be hidden on macOS.
15861602
#[serde(default, alias = "hidden-title")]
15871603
pub hidden_title: bool,
@@ -1758,6 +1774,7 @@ impl Default for WindowConfig {
17581774
window_classname: None,
17591775
theme: None,
17601776
title_bar_style: Default::default(),
1777+
traffic_light_position: None,
17611778
hidden_title: false,
17621779
accept_first_mouse: false,
17631780
tabbing_identifier: None,
@@ -2951,6 +2968,13 @@ mod build {
29512968
}
29522969
}
29532970

2971+
impl ToTokens for LogicalPosition {
2972+
fn to_tokens(&self, tokens: &mut TokenStream) {
2973+
let LogicalPosition { x, y } = self;
2974+
literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)
2975+
}
2976+
}
2977+
29542978
impl ToTokens for crate::WindowEffect {
29552979
fn to_tokens(&self, tokens: &mut TokenStream) {
29562980
let prefix = quote! { ::tauri::utils::WindowEffect };
@@ -3037,6 +3061,7 @@ mod build {
30373061
let window_classname = opt_str_lit(self.window_classname.as_ref());
30383062
let theme = opt_lit(self.theme.as_ref());
30393063
let title_bar_style = &self.title_bar_style;
3064+
let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());
30403065
let hidden_title = self.hidden_title;
30413066
let accept_first_mouse = self.accept_first_mouse;
30423067
let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
@@ -3090,6 +3115,7 @@ mod build {
30903115
window_classname,
30913116
theme,
30923117
title_bar_style,
3118+
traffic_light_position,
30933119
hidden_title,
30943120
accept_first_mouse,
30953121
tabbing_identifier,

0 commit comments

Comments
 (0)