tauri
tauri copied to clipboard
[feat] Allowing for native inset OS X traffic lights on NSWindow
Describe the problem
Many apps (also electron apps) have customized traffic light position to fit their style better. In electron this is achieved through a config file that calls native objc code. I would like this in tauri as it would allow for cleaner more intentional designs.
Describe the solution you'd like
I have already worked on this in issue #2663. I used the electron source as inspiration.
A file called window_ext.rs
:
use tauri::{Runtime, Window};
pub trait WindowExt {
#[cfg(target_os = "macos")]
fn set_transparent_titlebar(&self, transparent: bool);
fn position_traffic_lights(&self, x: f64, y: f64);
}
impl<R: Runtime> WindowExt for Window<R> {
#[cfg(target_os = "macos")]
fn set_transparent_titlebar(&self, transparent: bool) {
use cocoa::appkit::{NSWindow, NSWindowTitleVisibility};
let window = self.ns_window().unwrap() as cocoa::base::id;
unsafe {
window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
if transparent {
window.setTitlebarAppearsTransparent_(cocoa::base::YES);
} else {
window.setTitlebarAppearsTransparent_(cocoa::base::NO);
}
}
}
#[cfg(target_os = "macos")]
fn position_traffic_lights(&self, x: f64, y: f64) {
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
use cocoa::foundation::NSRect;
let window = self.ns_window().unwrap() as cocoa::base::id;
unsafe {
let close = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
let miniaturize =
window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
let title_bar_container_view = close.superview().superview();
let close_rect: NSRect = msg_send![close, frame];
let button_height = close_rect.size.height;
let title_bar_frame_height = button_height + y;
let mut title_bar_rect = NSView::frame(title_bar_container_view);
title_bar_rect.size.height = title_bar_frame_height;
title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height;
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
let window_buttons = vec![close, miniaturize, zoom];
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect: NSRect = NSView::frame(button);
rect.origin.x = x + (i as f64 * space_between);
button.setFrameOrigin(rect.origin);
}
}
}
}
main.rs
:
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;
use tauri::{Manager, WindowEvent};
use window_ext::WindowExt;
mod window_ext;
fn main() {
tauri::Builder::default()
.setup(|app| {
let win = app.get_window("main").unwrap();
win.set_transparent_titlebar(true);
win.position_traffic_lights(30.0, 30.0);
Ok(())
})
.on_window_event(|e| {
if let WindowEvent::Resized(..) = e.event() {
let win = e.window();
win.position_traffic_lights(30., 30.);
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
In general this works:

The on_window_event
listener is there because without it the traffic lights would take their original position when the window is resized. This comes however with an annoying problem: The NSWindow
handle becomes only available after the window is already rendered. That causes some annoying artifacts. You can see the original traffic lights before they are repositioned for a very short time.
So the task is somehow tapping into the tao render pipeline or something else to draw the traffic lights before the window is rendered.
Alternatives considered
I don't really have any. Maybe somehow controlling the NSWindow completely ourselves. Please make suggestions if you know more about the tauri source.
Additional context
There are still some #[cfg(target_os = "macos")]
missing. Feel free to add them.
When changing the size of the window, there will be a problem of flashing OS X traffic lights, can this be solved?
So the task is somehow tapping into the tao render pipeline or something else to draw the traffic lights before the window is rendered.
I honestly don't believe that is possible, the traffic lights are part of the window and so everything there is rendered as one unit
Oh but in general I agree 100%, the current solution is definitely imperfect, but right now I rather have traffic lights that have ugly padding than traffic lights that flicker and jump around (this is not only a matter of taste, but also of usability)
After switching the system theme, the OSX traffic lights return to their original position
It’s definitely possible in native Tao: https://github.com/tauri-apps/tao/pull/513
this is a pull request I did on that issue. This works without artifacts. It’s not merged yet because of some conflicts. I have just now noticed that the PR wasn’t merged yet.
After switching the system theme, the OSX traffic lights return to their original position
@duhiqc you can adjust the above code to this, and that'll solve that issue:
.on_window_event(|e| {
let apply_offset = || {
let win = e.window();
win.position_traffic_lights(30., 30.);
}
match e.event() {
WindowEvent::Resized(..) => apply_offset(),
WindowEvent::ThemeChanged(..) => apply_offset(),
_ => {}
}
})
It's possible to just hide them ? For my app I don't need them.
I'm not a macOS developer and only did a few mins of research on this topic but I've found this gist. Maybe it's helpful in this context?
I don't really have the motivation, time or Know-how to work at this at the moment. Maybe someone else could take off from here? Please also take a look at my PR in Tao mentioned above somewhere. The Tao PR might get accepted into tauri.
It's possible to just hide them ? For my app I don't need them.
Yes. Definitely. I believe that's even officially possible through the tauri config file.
Would love to see this go through! Subscribing to this thread.
@dukeeagle funny to run into you on Tauri issues 😛
It's actually quite easy to implement this using the above two solutions. Feels native. Thanks for the great work @haasal and @tr3ysmith!
切换系统主题后,OSX交通灯返回原来的位置
@duhiqc您可以将上面的代码调整为此,这将解决该问题:
.on_window_event(|e| { let apply_offset = || { let win = e.window(); win.position_traffic_lights(30., 30.); } match e.event() { WindowEvent::Resized(..) => apply_offset(), WindowEvent::ThemeChanged(..) => apply_offset(), _ => {} } })
After using this scheme, the taskbar will flicker.
https://github.com/tauri-apps/tauri/assets/32786133/2ed5195d-1e40-4fc8-8c1a-4b2e437e1f10
切换系统主题后,OSX交通灯返回原来的位置
@duhiqc您可以将上面的代码调整为此,这将解决该问题:
.on_window_event(|e| { let apply_offset = || { let win = e.window(); win.position_traffic_lights(30., 30.); } match e.event() { WindowEvent::Resized(..) => apply_offset(), WindowEvent::ThemeChanged(..) => apply_offset(), _ => {} } })
After using this scheme, the taskbar will flicker.
2023-09-22.10.36.51.mov
Same here
Couldn't fix the flickering issue, which was terribly annoying. But after some searching, I found this article: https://juejin.cn/post/7114740998579847198.
In this article the author decided to go the way of using toolbar. I was quite satisfied with it. I hope it will help someone.
I have already described this issue in my initial post. This is the reason why this issue was opened. Because currently there isn't really any way to get rid of this artifact. Please read the issue for more information.
As I have already mentioned there is a PR in Tao where I have worked on this but it has proven quite annoying and I currently don't have time to finish this.
Important Update: The PR for Tao was merged today. We now have to wait until a new Tao version is used by wry and then by tauri before the feature can be implemented in tauri itself.
Important Update: The PR for Tao was merged today. We now have to wait until a new Tao version is used by wry and then by tauri before the feature can be implemented in tauri itself.
Awesome! Will that be part of 1.x too?
awesome. Can't wait :-)
Any updates on this?
@haasal could you explain how to use this? I could not find any information on that whatsoever. Thanks
I forgot to mention this. There is a weird bug in wry which causes the new inset implementation in Tao not to work and I have no idea how to fix this. This has been a huge pain already which is really unfortunate. Help is appreciated! Wry Issue
@appinteractive it unfortunately doesn't work yet. The best you can do is setting the title bar style somewhere in the tauri config file
Hoppscotch implement this function and solve the flickering issue.
https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs#L70-L101
Thanks @wyhaya that worked like a charm!
I'm pretty new to Rust, but I followed along with https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs#L70-L101 and updated it to work with Tauri 2.x
Gist here https://gist.github.com/charrondev/43150e940bd2771b1ea88256d491c7a9
In addition to working with Tauri 2.x it also works for all windows, rather than just the main one.