Skip to content
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

[feat] Allowing for native inset OS X traffic lights on NSWindow #4789

Open
haasal opened this issue Jul 28, 2022 · 26 comments
Open

[feat] Allowing for native inset OS X traffic lights on NSWindow #4789

haasal opened this issue Jul 28, 2022 · 26 comments

Comments

@haasal
Copy link

haasal commented Jul 28, 2022

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:

Screenshot 2022-07-27 at 14 36 04

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.

@ghost
Copy link

ghost commented Jan 15, 2023

When changing the size of the window, there will be a problem of flashing OS X traffic lights, can this be solved?

@JonasKruckenberg
Copy link
Member

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

@JonasKruckenberg
Copy link
Member

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)

@ghost
Copy link

ghost commented Jan 15, 2023

After switching the system theme, the OSX traffic lights return to their original position

@haasal
Copy link
Author

haasal commented Jan 15, 2023

It’s definitely possible in native Tao: tauri-apps/tao#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.

@tr3ysmith
Copy link
Contributor

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(),
        _ => {}
    }

})

@KaliaJS
Copy link

KaliaJS commented Jan 27, 2023

It's possible to just hide them ? For my app I don't need them.

@schickling
Copy link

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?

@haasal
Copy link
Author

haasal commented Apr 6, 2023

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.

@haasal
Copy link
Author

haasal commented Apr 6, 2023

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.

@dukeeagle
Copy link

Would love to see this go through! Subscribing to this thread.

@raunakdoesdev
Copy link

@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!

@notbucai
Copy link

notbucai commented Sep 22, 2023

切换系统主题后,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

@yooouuri
Copy link

切换系统主题后,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

@ilsur1103
Copy link

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.
image

@haasal
Copy link
Author

haasal commented Sep 24, 2023

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.

@haasal
Copy link
Author

haasal commented Oct 26, 2023

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.

@appinteractive
Copy link

appinteractive commented Oct 26, 2023

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?

@PavelLaptev
Copy link

awesome. Can't wait :-)

@rickmartensnl

This comment was marked as spam.

@appinteractive
Copy link

@haasal could you explain how to use this? I could not find any information on that whatsoever. Thanks

@haasal
Copy link
Author

haasal commented Jan 2, 2024

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

@wyhaya
Copy link

wyhaya commented Jan 3, 2024

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

@appinteractive
Copy link

Thanks @wyhaya that worked like a charm!

@charrondev
Copy link

charrondev commented Mar 24, 2024

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.

@Wind-Explorer
Copy link

Wind-Explorer commented Nov 5, 2024

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.

Thank you! gist works great.

However I would just like to bring up a small issue with this implementation:

When system's colour scheme changes, the control buttons' height gets reset, and only goes back to their desired position after resizing the window.

2024-11-05.09.38.13.mov

It's one of those things where its all peaceful until you found out about it then it lives rent-free in your head

Would be delightful if a solution can be identified 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 📬Proposal
Development

No branches or pull requests