103 lines
4.2 KiB
Rust
103 lines
4.2 KiB
Rust
//! macOS 原生标题栏适配。定位系统窗口按钮并沿用用户的标题栏双击设置。
|
|
//! 仅在 macOS 编译;Objective-C 交互局限在原生窗口层,不参与 Endor 布局与事件分发。
|
|
//!
|
|
//! Keep AppKit's real window buttons aligned with the custom tab strip.
|
|
use objc2_app_kit::{NSApplication, NSView, NSWindowButton, NSWindowStyleMask};
|
|
use objc2_foundation::{MainThreadMarker, NSPoint, NSString, NSUserDefaults};
|
|
use winit::{
|
|
raw_window_handle::{HasWindowHandle, RawWindowHandle},
|
|
window::Window,
|
|
};
|
|
|
|
/// The custom draggable row does not receive AppKit's titlebar double-click
|
|
/// action automatically. Use the native click count and native window actions.
|
|
/// 读取系统偏好的标题栏双击行为,委托原生窗口执行相应动作。
|
|
pub fn titlebar_double_click(window: &Window) -> bool {
|
|
let Some(main_thread) = MainThreadMarker::new() else {
|
|
return false;
|
|
};
|
|
let Ok(handle) = window.window_handle() else {
|
|
return false;
|
|
};
|
|
let RawWindowHandle::AppKit(handle) = handle.as_raw() else {
|
|
return false;
|
|
};
|
|
// SAFETY: called on the event-loop thread during a left-button press, with
|
|
// a live winit-owned NSView. No native object outlives the Window borrow.
|
|
unsafe {
|
|
let Some(event) = NSApplication::sharedApplication(main_thread).currentEvent() else {
|
|
return false;
|
|
};
|
|
if event.clickCount() != 2 {
|
|
return false;
|
|
}
|
|
let view = &*handle.ns_view.as_ptr().cast::<NSView>();
|
|
let Some(native) = view.window() else {
|
|
return false;
|
|
};
|
|
if native.styleMask().contains(NSWindowStyleMask::FullScreen) {
|
|
return true;
|
|
}
|
|
let action = NSUserDefaults::standardUserDefaults()
|
|
.stringForKey(&NSString::from_str("AppleActionOnDoubleClick"))
|
|
.map(|value| value.to_string());
|
|
match action.as_deref() {
|
|
Some("Minimize") => native.performMiniaturize(None),
|
|
None | Some("Maximize" | "Fill") => native.performZoom(None),
|
|
// Respect disabled/unrecognized system actions without moving it.
|
|
_ => {}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
/// Called only on winit's main thread, after native creation and before drawing.
|
|
/// AppKit can restore its default button positions when laying out the title bar.
|
|
/// 调整真实 macOS 窗口三键的位置,与全尺寸内容和侧栏控制区对齐。
|
|
pub fn align_controls(window: &Window) {
|
|
let Ok(handle) = window.window_handle() else {
|
|
return;
|
|
};
|
|
let RawWindowHandle::AppKit(handle) = handle.as_raw() else {
|
|
return;
|
|
};
|
|
// SAFETY: winit owns this NSView for the lifetime of the borrowed Window.
|
|
// All AppKit access is on the event-loop thread; no native pointers escape.
|
|
unsafe {
|
|
let view = &*handle.ns_view.as_ptr().cast::<NSView>();
|
|
let Some(native) = view.window() else {
|
|
return;
|
|
};
|
|
// Fullscreen traffic lights belong to AppKit's separate floating toolbar.
|
|
if native.styleMask().contains(NSWindowStyleMask::FullScreen) {
|
|
return;
|
|
}
|
|
let bounds = view.bounds();
|
|
let center = f64::from(crate::sidebar::HEADER) / 2.0;
|
|
let content_y = if view.isFlipped() {
|
|
bounds.origin.y + center
|
|
} else {
|
|
bounds.origin.y + bounds.size.height - center
|
|
};
|
|
for kind in [
|
|
NSWindowButton::NSWindowCloseButton,
|
|
NSWindowButton::NSWindowMiniaturizeButton,
|
|
NSWindowButton::NSWindowZoomButton,
|
|
] {
|
|
let Some(button) = native.standardWindowButton(kind) else {
|
|
continue;
|
|
};
|
|
let Some(parent) = button.superview() else {
|
|
continue;
|
|
};
|
|
let target = parent.convertPoint_fromView(NSPoint::new(0.0, content_y), Some(view));
|
|
let frame = button.frame();
|
|
let y = target.y - frame.size.height / 2.0;
|
|
// Preserve native button size, horizontal spacing and behavior.
|
|
if (frame.origin.y - y).abs() > 0.01 {
|
|
button.setFrameOrigin(NSPoint::new(frame.origin.x, y));
|
|
}
|
|
}
|
|
}
|
|
}
|