tauri icon indicating copy to clipboard operation
tauri copied to clipboard

Feature request: Add setting for titlebar style with native window controls support

Open fnky opened this issue 3 years ago • 34 comments

Is your feature request related to a problem? Please describe.

When creating frameless windows you're required to provide your own UI for window controls. This has multiple problems with consistency and accessibility and requires more work to get close to the UX that is already provided by the native window system.

For example on macOS there are different actions based on how you interact with the window controls ("traffic lights"):

  • Unfocused windows makes traffic lights appear gray
    • Unfocused windows still respond to mouse events for the traffic lights such as hovering.
  • Hovering the traffic lights will display the icons, based on system preference.
  • Holding Alt/Option key while clicking on "resize" will maximize the window as opposed to the default fullscreen behavior.
  • Traffic lights can still be used even if the underlying application becomes unresponsive.

And many more edge-cases that needs to be handled in order to provide native-like experience.

These cases only describes macOS-specific parts, but could easily apply to Windows and Linux as well, which would require more work to provide native-like UI.

Frameless windows also has several problems as outlined in https://github.com/tauri-apps/tauri/issues/2549 and described in Electron's documentation

Describe the solution you'd like

Support the ability to set the titlebar style similarly to Electron's titleBarStyle setting which allows for custom title bars while preserving native window controls in different ways.

Describe alternatives you've considered

I don't believe there are any alternatives and needs to be solved within Tauri.

fnky avatar Sep 26 '21 12:09 fnky

Any progress?

chhpt avatar Jan 16 '22 15:01 chhpt

Would love to see progress on this too, it's an important feature for me.

gardc avatar Jan 18 '22 19:01 gardc

@chhpt @gardc we're still mainly blocked by the feature freeze here. The audit process is coming to an end and with the 1.0 release being so close, we're completely focused on that one. To make our lives easier when merging the audit changes, we also closed the next branch for new features some time ago, fwiw.

Btw, if someone wants to help us out and take a stab at implementing this feature, we always appreciate PRs! ❤️

FabianLars avatar Jan 18 '22 19:01 FabianLars

I will tackle this one. On my roadmap this week.

JonasKruckenberg avatar Jan 18 '22 20:01 JonasKruckenberg

@FabianLars Thank you for the update, it's much appreciated. Can't wait to see whats in store for 1.0!

@JonasKruckenberg Great to hear! Let me know if there's anything I can do to assist with regards to macOS side of things.

fnky avatar Jan 18 '22 20:01 fnky

I will tackle this one. On my roadmap this week.

Hello, The feature will come in tauri 2.0 or use tauri-plugin to complete it?

awkj avatar Feb 13 '22 14:02 awkj

Alright, here goes my issue-only knowledge dump 😂:

I'm struggling a bit with implementing this in tauri+tao directly, but here is how you can implement this yourself using the ns_window() handle and the cocoa crate:

use cocoa::appkit::{NSWindow, NSWindowStyleMask};
use tauri::{Runtime, Window};

pub trait WindowExt {
  #[cfg(target_os = "macos")]
  fn set_transparent_titlebar(&self, transparent: bool);
}

impl<R: Runtime> WindowExt for Window<R> {
  #[cfg(target_os = "macos")]
  fn set_transparent_titlebar(&self, transparent: bool) {
    use cocoa::appkit::NSWindowTitleVisibility;

    unsafe {
      let id = self.ns_window().unwrap() as cocoa::base::id;

      let mut style_mask = id.styleMask();
      style_mask.set(
        NSWindowStyleMask::NSFullSizeContentViewWindowMask,
        transparent,
      );
      id.setStyleMask_(style_mask);

      id.setTitleVisibility_(if transparent {
        NSWindowTitleVisibility::NSWindowTitleHidden
      } else {
        NSWindowTitleVisibility::NSWindowTitleVisible
      });
      id.setTitlebarAppearsTransparent_(if transparent {
        cocoa::base::YES
      } else {
        cocoa::base::NO
      });
    }
  }
}

This is how you would use the defined function in your app to make a window have a transparent titlebar.

fn main() {
   tauri::Builder::default()
      .setup(|app| {
         let win = app.get_window("main").unwrap();
         win.set_transparent_titlebar(true);

         Ok(())
      })
      .run(tauri::generate_context!())
      .expect("error while running tauri application");
}

JonasKruckenberg avatar Apr 05 '22 15:04 JonasKruckenberg

Alright, here goes my issue-only knowledge dump 😂:

I'm struggling a bit with implementing this in tauri+tao directly, but here is how you can implement this yourself using the ns_window() handle and the cocoa crate:

use cocoa::appkit::{NSWindow, NSWindowStyleMask};
use tauri::{Runtime, Window};

pub trait WindowExt {
  #[cfg(target_os = "macos")]
  fn set_transparent_titlebar(&self, transparent: bool);
}

impl<R: Runtime> WindowExt for Window<R> {
  #[cfg(target_os = "macos")]
  fn set_transparent_titlebar(&self, transparent: bool) {
    use cocoa::appkit::NSWindowTitleVisibility;

    unsafe {
      let id = self.ns_window().unwrap() as cocoa::base::id;

      let mut style_mask = id.styleMask();
      style_mask.set(
        NSWindowStyleMask::NSFullSizeContentViewWindowMask,
        transparent,
      );
      id.setStyleMask_(style_mask);

      id.setTitleVisibility_(if transparent {
        NSWindowTitleVisibility::NSWindowTitleHidden
      } else {
        NSWindowTitleVisibility::NSWindowTitleVisible
      });
      id.setTitlebarAppearsTransparent_(if transparent {
        cocoa::base::YES
      } else {
        cocoa::base::NO
      });
    }
  }
}

This is how you would use the defined function in your app to make a window have a transparent titlebar.

fn main() {
   tauri::Builder::default()
      .setup(|app| {
         let win = app.get_window("main").unwrap();
         win.set_transparent_titlebar(true);

         Ok(())
      })
      .run(tauri::generate_context!())
      .expect("error while running tauri application");
}

Thanks, It's work for me image

but I not found API to hide toolbar(close maxsize fullsize button ) , if can hide the button, it will is perfect !

awkj avatar Apr 07 '22 07:04 awkj

but I not found API to hide toolbar(close maxsize fullsize button ) , if can hide the button, it will is perfect !

This can be archived with the same NSWindowStyleMask that I used in the above example if you merely want to "grey-out" the minimize and close buttons.

If you're looking to hide all window buttons you should have chosen WindowBuilder::decorations(false) that has already been available for a while

JonasKruckenberg avatar Apr 07 '22 10:04 JonasKruckenberg

WindowBuilder::decorations(false)

yes, I have use the API, it equal to setting tauri.conf.json "decoration" = false, but the setting will let window border lose round

awkj avatar Apr 08 '22 05:04 awkj

yes, I have use the API, it equal to setting tauri.conf.json "decoration" = false, but the setting will let window border lose round

Do you want to publish your app to the macOS App Store? If not you can use "transparent": true and use css to recreate the border radius. It's as easy as that, no need for unsafe objective C interop.

JonasKruckenberg avatar Apr 08 '22 07:04 JonasKruckenberg

yes, I have use the API, it equal to setting tauri.conf.json "decoration" = false, but the setting will let window border lose round

Do you want to publish your app to the macOS App Store? If not you can use "transparent": true and use css to recreate the border radius. It's as easy as that, no need for unsafe objective C interop.

the solution I found it in tauri document (https://tauri.studio/docs/guides/window-customization) ,but not perfect ,when I drag my windows , the border have display blank block on border. I want a setting can hide button and have round border(the style like native swift ui on mac)

Yes, I want publish to App Store , Why rust need use private API, but use swift or electron can ?

awkj avatar Apr 08 '22 08:04 awkj

hello, I found the mask will let mouse can't move the window, it is indeterminacy for me is Bug or macos feauter, Can you give me a suggestion ?

 NSWindow::setTitlebarAppearsTransparent_(id, cocoa::base::YES);
            let mut style_mask = id.styleMask();
            style_mask.set(
                NSWindowStyleMask::NSFullSizeContentViewWindowMask,
                transparent,
            );
            id.setStyleMask_(style_mask);

I try call the possiable API but not work

id.setMovable_(cocoa::base::YES);
id.setMovableByWindowBackground_(cocoa::base::YES);

I use create a empty div and use data-tauri-drag-region to solve it ,but have some complex

<body>
  <div data-tauri-drag-region style="height:30px">
  </div>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx"></script>
</body>

awkj avatar Apr 09 '22 16:04 awkj

I use create a empty div and use data-tauri-drag-region to solve it ,but have some complex


<body>

  <div data-tauri-drag-region style="height:30px">

  </div>

  <div id="root"></div>

  <script type="module" src="/src/main.tsx"></script>

</body>

You need the data-tauri-drag-region to make it work as there is technically no titlebar anymore. You also need to enable window > startDragging, window > maximize and window > unmaximize in your allowlist

JonasKruckenberg avatar Apr 09 '22 19:04 JonasKruckenberg

I use create a empty div and use data-tauri-drag-region to solve it ,but have some complex

<body>

  <div data-tauri-drag-region style="height:30px">

  </div>

  <div id="root"></div>

  <script type="module" src="/src/main.tsx"></script>

</body>

You need the data-tauri-drag-region to make it work as there is technically no titlebar anymore. You also need to enable window > startDragging, window > maximize and window > unmaximize in your allowlist

ok, I now setting tauri->allowlist is "all" and don't need the setting up again ,but this is good suggestion, this the doc https://tauri.studio/docs/api/config#tauri.allowlist.window wish help other user.

awkj avatar Apr 10 '22 03:04 awkj

Great! BTW, how to apply this feature on Windows platform? @JonasKruckenberg

Alright, here goes my issue-only knowledge dump 😂:

I'm struggling a bit with implementing this in tauri+tao directly, but here is how you can implement this yourself using the ns_window() handle and the cocoa crate:

use cocoa::appkit::{NSWindow, NSWindowStyleMask};
use tauri::{Runtime, Window};

pub trait WindowExt {
  #[cfg(target_os = "macos")]
  fn set_transparent_titlebar(&self, transparent: bool);
}

impl<R: Runtime> WindowExt for Window<R> {
  #[cfg(target_os = "macos")]
  fn set_transparent_titlebar(&self, transparent: bool) {
    use cocoa::appkit::NSWindowTitleVisibility;

    unsafe {
      let id = self.ns_window().unwrap() as cocoa::base::id;

      let mut style_mask = id.styleMask();
      style_mask.set(
        NSWindowStyleMask::NSFullSizeContentViewWindowMask,
        transparent,
      );
      id.setStyleMask_(style_mask);

      id.setTitleVisibility_(if transparent {
        NSWindowTitleVisibility::NSWindowTitleHidden
      } else {
        NSWindowTitleVisibility::NSWindowTitleVisible
      });
      id.setTitlebarAppearsTransparent_(if transparent {
        cocoa::base::YES
      } else {
        cocoa::base::NO
      });
    }
  }
}

This is how you would use the defined function in your app to make a window have a transparent titlebar.

fn main() {
   tauri::Builder::default()
      .setup(|app| {
         let win = app.get_window("main").unwrap();
         win.set_transparent_titlebar(true);

         Ok(())
      })
      .run(tauri::generate_context!())
      .expect("error while running tauri application");
}

xuchaoqian avatar May 11 '22 05:05 xuchaoqian

BTW, how to apply this feature on Windows platform? @JonasKruckenberg

This is not a thing on windows as far as I'm aware. The common practice (with electron too) seems to be to remove decorations entirely and create the titlebar from scratch using html/css/js

JonasKruckenberg avatar May 11 '22 06:05 JonasKruckenberg

Got it. Thank you!

On Windows, removing decorations entirely and creating the titlebar from scratch using html/css/js, traffic lights can still be used even if the underlying application becomes inactive.

But on macOS, we must use native traffic lights to get this feature.

This is not a thing on windows as far as I'm aware. The common practice (with electron too) seems to be to remove decorations entirely and create the titlebar from scratch using html/css/js

xuchaoqian avatar May 11 '22 07:05 xuchaoqian

Yeah! The reason why having this "floating" traffic lights feature on MacOS is so important, is that it's really complex, you have this dropdown when you hover over the maximize button etc. It's almost impossible to perfectly recreate this in html/css/js.

But window controls on windows and linux are way less complicated so you can realistically archive something that feels good on those platforms by using html/css/js

JonasKruckenberg avatar May 11 '22 08:05 JonasKruckenberg

if want hide toolbar_button and let title transparent , I fix and complete a perfercet solution, thanks @JonasKruckenberg

use cocoa::appkit::{NSWindow, NSWindowStyleMask, NSWindowTitleVisibility};

pub trait WindowExt {
    #[cfg(target_os = "macos")]
    fn set_transparent_titlebar(&self, title_transparent: bool, remove_toolbar: bool);
}

impl<R: Runtime> WindowExt for Window<R> {
    #[cfg(target_os = "macos")]
    fn set_transparent_titlebar(&self, title_transparent: bool, remove_tool_bar: bool) {
        unsafe {
            let id = self.ns_window().unwrap() as cocoa::base::id;
            NSWindow::setTitlebarAppearsTransparent_(id, cocoa::base::YES);
            let mut style_mask = id.styleMask();
            style_mask.set(
                NSWindowStyleMask::NSFullSizeContentViewWindowMask,
                title_transparent,
            );

            if remove_tool_bar {
                style_mask.remove(
                    NSWindowStyleMask::NSClosableWindowMask
                        | NSWindowStyleMask::NSMiniaturizableWindowMask
                        | NSWindowStyleMask::NSResizableWindowMask,
                );
            }

            id.setStyleMask_(style_mask);

            id.setTitleVisibility_(if title_transparent {
                NSWindowTitleVisibility::NSWindowTitleHidden
            } else {
                NSWindowTitleVisibility::NSWindowTitleVisible
            });

            id.setTitlebarAppearsTransparent_(if title_transparent {
                cocoa::base::YES
            } else {
                cocoa::base::NO
            });
        }
    }
}
 let win = app.get_window("main").unwrap();
win.set_transparent_titlebar(true, false);
image
 let win = app.get_window("main").unwrap();
win.set_transparent_titlebar(true, true);
image

awkj avatar May 11 '22 12:05 awkj

for removing traffic lights, from previous solution, window will not be resized or minimized manually or from Tauri API

I found another method for this (inspired from previous comment)

#[cfg(target_os = "macos")]
use cocoa::appkit::{NSWindow, NSWindowButton, NSWindowStyleMask, NSWindowTitleVisibility};

#[cfg(target_os = "macos")]
use objc::runtime::YES;

use tauri::{Runtime, Window};

#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;

pub trait WindowExt {
    #[cfg(target_os = "macos")]
    fn set_transparent_titlebar(&self, title_transparent: bool, remove_toolbar: bool);
}

impl<R: Runtime> WindowExt for Window<R> {
    #[cfg(target_os = "macos")]
    fn set_transparent_titlebar(&self, title_transparent: bool, remove_tool_bar: bool) {
        unsafe {
            let id = self.ns_window().unwrap() as cocoa::base::id;
            NSWindow::setTitlebarAppearsTransparent_(id, cocoa::base::YES);
            let mut style_mask = id.styleMask();
            style_mask.set(
                NSWindowStyleMask::NSFullSizeContentViewWindowMask,
                title_transparent,
            );

            id.setStyleMask_(style_mask);

            if remove_tool_bar {
                let close_button = id.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
                let _: () = msg_send![close_button, setHidden: YES];
                let min_button = id.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
                let _: () = msg_send![min_button, setHidden: YES];
                let zoom_button = id.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
                let _: () = msg_send![zoom_button, setHidden: YES];
            }

            id.setTitleVisibility_(if title_transparent {
                NSWindowTitleVisibility::NSWindowTitleHidden
            } else {
                NSWindowTitleVisibility::NSWindowTitleVisible
            });

            id.setTitlebarAppearsTransparent_(if title_transparent {
                cocoa::base::YES
            } else {
                cocoa::base::NO
            });
        }
    }
}
// see previous comment for usage and result
let win = app.get_window("main").unwrap();
win.set_transparent_titlebar(true, true);

add this in Cargo.toml

[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.24"
objc = "0.2.7"

RtthLvr avatar May 19 '22 16:05 RtthLvr

Hey. Is there anyway to make the titlebar inset? Should I make this a seperate issue? I've currently got this ↓

Screenshot 2022-05-23 at 08 47 36

Lalit64 avatar May 23 '22 06:05 Lalit64

https://github.com/electron/electron/pull/33066 This could serve as a good guide for theming the native windows titlebar, it was implemented in an effort to bring Windows 11 features to the themed titlebar VSCode has.

MulverineX avatar May 27 '22 04:05 MulverineX

@JonasKruckenberg How to change the traffic light position, i found some information, but I don't understand object-c

https://github.com/patr0nus/DeskGap/blob/master/lib/src/platform/mac/BrowserWindow.mm#L86 https://github.com/electron/electron/blob/37d93b04824d4f0151b36edb867209b53272457f/shell/browser/native_window_mac.mm#L1529

i found answer:

id.setToolbar_(msg_send![class!(NSToolbar), new]);

https://github.com/lukakerr/NSWindowStyles#12-transparent-toolbar-without-seperator https://developer.apple.com/documentation/appkit/nstoolbar

mantou132 avatar Jun 09 '22 15:06 mantou132

if want hide toolbar_button and let title transparent , I fix and complete a perfercet solution, thanks @JonasKruckenberg

use cocoa::appkit::{NSWindow, NSWindowStyleMask, NSWindowTitleVisibility};

pub trait WindowExt {
    #[cfg(target_os = "macos")]
    fn set_transparent_titlebar(&self, title_transparent: bool, remove_toolbar: bool);
}

impl<R: Runtime> WindowExt for Window<R> {
    #[cfg(target_os = "macos")]
    fn set_transparent_titlebar(&self, title_transparent: bool, remove_tool_bar: bool) {
        unsafe {
            let id = self.ns_window().unwrap() as cocoa::base::id;
            NSWindow::setTitlebarAppearsTransparent_(id, cocoa::base::YES);
            let mut style_mask = id.styleMask();
            style_mask.set(
                NSWindowStyleMask::NSFullSizeContentViewWindowMask,
                title_transparent,
            );

            if remove_tool_bar {
                style_mask.remove(
                    NSWindowStyleMask::NSClosableWindowMask
                        | NSWindowStyleMask::NSMiniaturizableWindowMask
                        | NSWindowStyleMask::NSResizableWindowMask,
                );
            }

            id.setStyleMask_(style_mask);

            id.setTitleVisibility_(if title_transparent {
                NSWindowTitleVisibility::NSWindowTitleHidden
            } else {
                NSWindowTitleVisibility::NSWindowTitleVisible
            });

            id.setTitlebarAppearsTransparent_(if title_transparent {
                cocoa::base::YES
            } else {
                cocoa::base::NO
            });
        }
    }
}
 let win = app.get_window("main").unwrap();
win.set_transparent_titlebar(true, false);
image
 let win = app.get_window("main").unwrap();
win.set_transparent_titlebar(true, true);
image

Works well for main window and window created by WindowBuilder in command. But @awkj 's answer not worked in command.

erguotou520 avatar Jun 18 '22 03:06 erguotou520

Can we have this on windows too? Electron actually exposes an API titleBarOverlay that can be set to true with titleBarStyle to keep system native window controls and hide the titlebar.

const win = new BrowserWindow({
  titleBarStyle: 'hidden',
  titleBarOverlay: true
})

image

metkm avatar Jul 17 '22 17:07 metkm

@JonasKruckenberg How to change the traffic light position, i found some information, but I don't understand object-c

https://github.com/patr0nus/DeskGap/blob/master/lib/src/platform/mac/BrowserWindow.mm#L86 https://github.com/electron/electron/blob/37d93b04824d4f0151b36edb867209b53272457f/shell/browser/native_window_mac.mm#L1529

i found answer:

id.setToolbar_(msg_send![class!(NSToolbar), new]);

https://github.com/lukakerr/NSWindowStyles#12-transparent-toolbar-without-seperator https://developer.apple.com/documentation/appkit/nstoolbar

I tried this which should work, but the app compiles fine and then just quits after id.init_() without any error. I don't know if that's some undefined behavior or a segfault.

  fn set_transparent_titlebar(&self, transparent: bool) {
        use cocoa::appkit::{NSToolbar, NSWindowTitleVisibility};

        let id = self.ns_window().unwrap() as cocoa::base::id;

        unsafe {
            let toolbar = id.init_();

            println!("{:?}", toolbar);

            id.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);

            if transparent {
                id.setTitlebarAppearsTransparent_(cocoa::base::YES);
            } else {
                id.setTitlebarAppearsTransparent_(cocoa::base::NO);
            }

            id.setToolbar_(toolbar);
        }
    }

Edit: I did it yaaaaay :D. It's apparently not recommended to readjust the position completely. But almost all apps I saw like slack, discord, firefox etc. look like this.

impl<R: Runtime> WindowExt for Window<R> {
    #[cfg(target_os = "macos")]
    fn set_transparent_titlebar(&self, transparent: bool) {
        use cocoa::appkit::{NSToolbar, NSWindowTitleVisibility};

        let id = self.ns_window().unwrap() as cocoa::base::id;

        unsafe {
            let new_toolbar = NSToolbar::alloc(id);
            new_toolbar.init_();

            id.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);

            if transparent {
                id.setTitlebarAppearsTransparent_(cocoa::base::YES);
            } else {
                id.setTitlebarAppearsTransparent_(cocoa::base::NO);
            }

            id.setToolbar_(new_toolbar)
        }
    }
}
Screenshot 2022-07-26 at 15 32 33

Edit2: The part about other apps is wrong. There has to be a way to increase the padding even more. Edit3: thats how they did it in electron. This is the commit.

Edit 4: Now it's working. I partly translated chunks from the electron source:

impl<R: Runtime> WindowExt for Window<R> {
    #[cfg(target_os = "macos")]
    fn set_transparent_titlebar(&self, transparent: bool) {
        use cocoa::appkit::{NSView, NSWindow, NSWindowButton, NSWindowTitleVisibility};
        use cocoa::foundation::NSRect;

        let window = self.ns_window().unwrap() as cocoa::base::id;
        let traffic_light_offset_y = 30.;
        let traffic_light_offset_x = 50.;

        unsafe {
            window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);

            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 + traffic_light_offset_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 = traffic_light_offset_x + (i as f64 * space_between);
                button.setFrameOrigin(rect.origin);
            }

            if transparent {
                window.setTitlebarAppearsTransparent_(cocoa::base::YES);
            } else {
                window.setTitlebarAppearsTransparent_(cocoa::base::NO);
            }
        }
    }
}

Unfortunately it jumps back when resizing the window or going into fullscreen. I guess this can simply be fixed with an event listener on window resize.

haasal avatar Jul 26 '22 13:07 haasal

I deleted my previous comment as it became too cluttered.

A summary: I implemented custom traffic light offset and took inspiration from here.

    #[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(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");
}

The issue is that the buttons jump back whenever the window is resized. I tried to mitigate this by attaching a listener on window resize. That approach works but you can clearly see artifacts as the buttons get repositioned after the native renderer places them. This has to be fixed somehow.

Screenshot 2022-07-27 at 14 36 04

Edit: I talked with the people from the tauri discord. Apparently there is no way to intercept the native window rendering without modifying the tao source. I didn't quite get it, but maybe there is a way to specify the whole NSWindow upfront. I believe we are doing just that inside the setup() function. Maybe someone has an idea... Edit2: I will open a new issue as this seems to be a larger topic.

haasal avatar Jul 27 '22 12:07 haasal

If you want to make an artifact free version of the inset traffic lights, you can use this small (admittedly ugly) hack that I created:

use tauri::{Runtime, Window};

#[allow(dead_code)]
pub enum ToolbarThickness {
    Thick,
    Medium,
    Thin,
}

pub trait WindowExt {
    #[cfg(target_os = "macos")]
    fn set_transparent_titlebar(&self, thickness: ToolbarThickness);
}

impl<R: Runtime> WindowExt for Window<R> {
    #[cfg(target_os = "macos")]
    fn set_transparent_titlebar(&self, thickness: ToolbarThickness) {
        use cocoa::appkit::{NSWindow, NSWindowTitleVisibility};

        unsafe {
            let id = self.ns_window().unwrap() as cocoa::base::id;

            id.setTitlebarAppearsTransparent_(cocoa::base::YES);

            match thickness {
                ToolbarThickness::Thick => {
                    self.set_title("").expect("Title wasn't set to ''");
                    make_toolbar(id);
                }
                ToolbarThickness::Medium => {
                    id.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
                    make_toolbar(id);
                }
                ToolbarThickness::Thin => {
                    id.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
                }
            }
        }
    }
}

#[cfg(target_os = "macos")]
unsafe fn make_toolbar(id: cocoa::base::id) {
    use cocoa::appkit::{NSToolbar, NSWindow};

    let new_toolbar = NSToolbar::alloc(id);
    new_toolbar.init_();
    id.setToolbar_(new_toolbar);
}

Replace the one line in main from the previous comment to (or any thickness you like):

win.set_transparent_titlebar(ToolbarThickness::Thick);

This gives at least some control over the traffic light inset...

Screenshot 2022-08-04 at 14 19 08 Screenshot 2022-08-04 at 14 19 37 Screenshot 2022-08-04 at 14 20 09

haasal avatar Aug 04 '22 12:08 haasal

@haasal So here's an interesting one, with your code and opening devtools, I get some crazy behavior on the front end in terms of divs disappearing whenever you resize the window... If I don't open dev tools, everything seems fine, Or vice versa, if I don't run set_transparent_titlebar and open dev tools, it runs fine... Weird! Is anyone else seeing this?

tr3ysmith avatar Aug 09 '22 00:08 tr3ysmith