diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 2f0d9083f..568287149 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -7,7 +7,7 @@ use std::os::raw::c_void; use crate::{ - dpi::LogicalSize, + dpi::{LogicalSize, Position}, event_loop::{EventLoop, EventLoopWindowTarget}, monitor::MonitorHandle, platform_impl::{get_aux_state_mut, Parent}, @@ -49,6 +49,8 @@ pub trait WindowExtMacOS { /// Sets whether or not the window has shadow. fn set_has_shadow(&self, has_shadow: bool); + /// Set the window traffic light position relative to the upper left corner + fn set_traffic_light_inset>(&self, position: P); /// Put the window in a state which indicates a file save is required. /// /// @@ -105,6 +107,11 @@ impl WindowExtMacOS for Window { self.window.set_has_shadow(has_shadow) } + #[inline] + fn set_traffic_light_inset>(&self, position: P) { + self.window.set_traffic_light_inset(position) + } + #[inline] fn set_is_document_edited(&self, edited: bool) { self.window.set_is_document_edited(edited) @@ -194,6 +201,8 @@ pub trait WindowBuilderExtMacOS { fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder; /// Sets whether or not the window has shadow. fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder; + /// Sets the traffic light position to (x, y) relative to the upper left corner + fn with_traffic_light_inset>(self, inset: P) -> WindowBuilder; /// Sets whether the system can automatically organize windows into tabs. fn with_automatic_window_tabbing(self, automatic_tabbing: bool) -> WindowBuilder; /// Defines the window [tabbing identifier]. @@ -266,6 +275,12 @@ impl WindowBuilderExtMacOS for WindowBuilder { self } + #[inline] + fn with_traffic_light_inset>(mut self, inset: P) -> WindowBuilder { + self.platform_specific.traffic_light_inset = Some(inset.into()); + self + } + #[inline] fn with_automatic_window_tabbing(mut self, automatic_tabbing: bool) -> WindowBuilder { self.platform_specific.automatic_tabbing = automatic_tabbing; diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 9ae2d1d30..19188c87c 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -11,7 +11,7 @@ use std::{ }; use cocoa::{ - appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow}, + appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow, NSWindowButton}, base::{id, nil}, foundation::{NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger}, }; @@ -70,6 +70,7 @@ pub(super) struct ViewState { pub(super) modifiers: ModifiersState, phys_modifiers: HashSet, tracking_rect: Option, + pub(super) traffic_light_inset: Option>, } impl ViewState { @@ -91,6 +92,7 @@ pub fn new_view(ns_window: id) -> (IdRef, Weak>) { modifiers: Default::default(), phys_modifiers: Default::default(), tracking_rect: None, + traffic_light_inset: None, }; unsafe { // This is free'd in `dealloc` @@ -384,6 +386,11 @@ extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) { let state_ptr: *mut c_void = *this.get_ivar("taoState"); let state = &mut *(state_ptr as *mut ViewState); + if let Some(position) = state.traffic_light_inset { + let window = state.ns_window; + inset_traffic_lights(window, position); + } + AppState::handle_redraw(WindowId(get_window_id(state.ns_window))); let superclass = util::superclass(this); @@ -1176,3 +1183,29 @@ extern "C" fn wants_key_down_for_event(_this: &Object, _sel: Sel, _event: id) -> extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL { YES } + +pub unsafe fn inset_traffic_lights(window: W, position: LogicalPosition) { + let (x, y) = (position.x, position.y); + + 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 = NSView::frame(close); + let title_bar_frame_height = close_rect.size.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 = window.frame().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 - close_rect.origin.x; + + for (i, button) in window_buttons.into_iter().enumerate() { + let mut rect = NSView::frame(button); + rect.origin.x = x + (i as f64 * space_between); + button.setFrameOrigin(rect.origin); + } +} diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 63370d041..85b6e86c4 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -55,7 +55,7 @@ use objc::{ runtime::{Class, Object, Sel, BOOL, NO, YES}, }; -use super::util::ns_string_to_rust; +use super::{util::ns_string_to_rust, view::ViewState}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id(pub usize); @@ -91,6 +91,7 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub resize_increments: Option>, pub disallow_hidpi: bool, pub has_shadow: bool, + pub traffic_light_inset: Option, pub automatic_tabbing: bool, pub tabbing_identifier: Option, } @@ -109,6 +110,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { resize_increments: None, disallow_hidpi: false, has_shadow: true, + traffic_light_inset: None, automatic_tabbing: true, tabbing_identifier: None, } @@ -125,6 +127,14 @@ unsafe fn create_view( ns_view.setWantsBestResolutionOpenGLSurface_(YES); } + if let Some(position) = pl_attribs.traffic_light_inset { + let state_ptr: *mut c_void = *(**ns_view).get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + let scale_factor = NSWindow::backingScaleFactor(ns_window); + let position = position.to_logical(scale_factor); + state.traffic_light_inset = Some(position); + } + // On Mojave, views automatically become layer-backed shortly after being added to // a window. Changing the layer-backedness of a view breaks the association between // the view and its associated OpenGL context. To work around this, on Mojave we @@ -1547,6 +1557,16 @@ impl WindowExtMacOS for UnownedWindow { } } + #[inline] + fn set_traffic_light_inset>(&self, position: P) { + let position: Position = position.into(); + unsafe { + let state_ptr: *mut c_void = *(**self.ns_view).get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + state.traffic_light_inset = Some(position.to_logical(self.scale_factor())); + } + } + #[inline] fn set_is_document_edited(&self, edited: bool) { unsafe {