feat(cef): implement traffic light positioning (#14660)

* feat(cef): implement traffic light positioning

* handle X axis as well
This commit is contained in:
Amr Bashir
2025-12-16 12:11:34 +02:00
committed by GitHub
parent 73a748ed20
commit e6e65e9bb5
3 changed files with 114 additions and 2 deletions

View File

@@ -714,6 +714,7 @@ wrap_window_delegate! {
attributes: Arc<RefCell<crate::CefWindowBuilder>>,
last_emitted_position: RefCell<PhysicalPosition<i32>>,
last_emitted_size: RefCell<PhysicalSize<u32>>,
context: Context<T>
}
impl ViewDelegate {
@@ -937,6 +938,19 @@ wrap_window_delegate! {
if a.visible.unwrap_or(true) {
window.show();
}
// Set traffic light position on macOS after window is fully created
// by posting a task to the UI thread to avoid issues with early setting
#[cfg(target_os = "macos")]
if let Some(pos) = a.traffic_light_position {
let window_message = WindowMessage::SetTrafficLightPosition(pos);
let message = Message::Window {
window_id: self.window_id,
message: window_message,
};
send_message_task(&self.context, message);
}
}
}
@@ -1017,6 +1031,11 @@ wrap_window_delegate! {
) {
let (Some(window), Some(bounds)) = (window, bounds) else { return; };
#[cfg(target_os = "macos")]
if let Some(pos) = &self.attributes.borrow().traffic_light_position {
apply_traffic_light_position(window.window_handle(), pos);
}
// Update autoresize overlay bounds (moved from on_layout_changed)
if let Some(app_window) = self.windows.borrow().get(&self.window_id) {
for wrapper in &app_window.webviews {
@@ -2335,7 +2354,13 @@ fn handle_window_message<T: UserEvent>(
// TODO: Implement title bar style
}
WindowMessage::SetTrafficLightPosition(_position) => {
// TODO: Implement traffic light position
#[cfg(target_os = "macos")]
if let Some(app_window) = context.windows.borrow().get(&window_id) {
app_window.attributes.borrow_mut().traffic_light_position = Some(_position);
if let Some(window) = app_window.window() {
apply_traffic_light_position(window.window_handle(), &_position);
}
}
}
WindowMessage::SetTheme(_theme) => {
// TODO: Implement theme
@@ -2608,6 +2633,7 @@ pub(crate) fn create_window<T: UserEvent>(
attributes.clone(),
RefCell::new(Default::default()),
RefCell::new(Default::default()),
context.clone(),
);
let window = window_create_top_level(Some(&mut delegate)).expect("Failed to create window");
@@ -2664,6 +2690,11 @@ wrap_task! {
}
}
fn send_message_task<T: UserEvent>(context: &Context<T>, message: Message<T>) {
let mut task = SendMessageTask::new(context.clone(), Arc::new(RefCell::new(message)));
cef::post_task(sys::cef_thread_id_t::TID_UI.into(), Some(&mut task));
}
fn send_window_event<T: UserEvent>(
window_id: WindowId,
windows: &Arc<RefCell<HashMap<WindowId, AppWindow>>>,
@@ -3166,3 +3197,50 @@ pub(crate) fn ensure_valid_content_view(
// No replacement needed; return the original handle
window_handle
}
#[cfg(target_os = "macos")]
fn apply_traffic_light_position(window: *mut std::ffi::c_void, position: &Position) {
use objc2::msg_send;
use objc2::rc::Retained;
use objc2_app_kit::{NSView, NSWindowButton};
let nsview = unsafe { Retained::<NSView>::retain(window as _) };
let Some(nsview) = nsview else {
return;
};
let Some(nswindow) = nsview.window() else {
return;
};
let Some(close) = nswindow.standardWindowButton(NSWindowButton::CloseButton) else {
return;
};
let Some(miniaturize) = nswindow.standardWindowButton(NSWindowButton::MiniaturizeButton) else {
return;
};
let Some(zoom) = nswindow.standardWindowButton(NSWindowButton::ZoomButton) else {
return;
};
let pos = position.to_logical::<f64>(nswindow.backingScaleFactor() as f64);
let (x, y) = (pos.x, pos.y);
let title_bar_container_view = unsafe { close.superview().unwrap().superview().unwrap() };
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 = nswindow.frame().size.height - title_bar_frame_height;
let _: () = unsafe { msg_send![&title_bar_container_view, setFrame: title_bar_rect] };
let window_buttons = vec![close, miniaturize.clone(), 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);
unsafe { button.setFrameOrigin(rect.origin) };
}
}

View File

@@ -859,10 +859,14 @@ impl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {
#[cfg(target_os = "macos")]
#[must_use]
pub fn traffic_light_position<P: Into<Position>>(mut self, position: P) -> Self {
let position = position.into();
self.window_builder = self.window_builder.traffic_light_position(position.clone());
self.webview_builder.webview_attributes = self
.webview_builder
.webview_attributes
.traffic_light_position(position.into());
.traffic_light_position(position);
self
}
@@ -2213,6 +2217,15 @@ impl<R: Runtime> WebviewWindow<R> {
self.window.set_title_bar_style(style)
}
/// Sets the position of the traffic light buttons. **macOS only**.
///
/// ## Platform-specific
///
/// - **macOS only**.
pub fn set_traffic_light_position(&self, position: Position) -> crate::Result<()> {
self.window.set_traffic_light_position(position)
}
/// Sets the theme for this window.
///
/// ## Platform-specific

View File

@@ -857,6 +857,14 @@ impl<'a, R: Runtime, M: Manager<R>> WindowBuilder<'a, R, M> {
self
}
/// Sets the [`crate::TitleBarStyle`].
#[cfg(target_os = "macos")]
#[must_use]
pub fn traffic_light_position<P: Into<Position>>(mut self, position: P) -> Self {
self.window_builder = self.window_builder.traffic_light_position(position);
self
}
/// Hide the window title.
#[cfg(target_os = "macos")]
#[must_use]
@@ -2174,6 +2182,19 @@ tauri::Builder::default()
.map_err(Into::into)
}
/// Sets the position of the traffic light buttons. **macOS only**.
///
/// ## Platform-specific
///
/// - **macOS only**.
pub fn set_traffic_light_position(&self, position: Position) -> crate::Result<()> {
self
.window
.dispatcher
.set_traffic_light_position(position)
.map_err(Into::into)
}
/// Sets the theme for this window.
///
/// ## Platform-specific