mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
When using `egui::ViewportBuilder::with_fullsize_content_view` one must be careful not to paint anything where the "traffic light" buttons are: <img width="87" height="47" alt="image" src="https://github.com/user-attachments/assets/0e878c8e-7141-4fed-bbc8-4d542ddb5251" /> `eframe::WindowChromeMetrics` helps you with that!
77 lines
2.6 KiB
Rust
77 lines
2.6 KiB
Rust
use egui::Vec2;
|
|
use objc2_app_kit::{NSView, NSWindow, NSWindowButton};
|
|
use raw_window_handle::{AppKitWindowHandle, RawWindowHandle};
|
|
|
|
/// Size of the "traffic lights" (red/yellow/green close/minimize/maximize buttons)
|
|
/// on the native macOS window.
|
|
///
|
|
/// This is very useful together with [`egui::ViewportBuilder::with_fullsize_content_view`].
|
|
#[derive(Debug)]
|
|
pub struct WindowChromeMetrics {
|
|
/// Size of the "traffic lights" (red/yellow/green close/minimize/maximize buttons),
|
|
/// including margins.
|
|
///
|
|
/// The unit here is in "native scale", which means it needs to be divided by [`egui::Context::zoom_factor`]
|
|
/// to get the size in egui points.
|
|
pub traffic_lights_size: Vec2,
|
|
}
|
|
|
|
impl WindowChromeMetrics {
|
|
/// Get the window chrome metrics for a given window handle.
|
|
pub fn from_window_handle(window_handle: &RawWindowHandle) -> Option<Self> {
|
|
window_chrome_metrics(window_handle)
|
|
}
|
|
}
|
|
|
|
fn window_chrome_metrics(window_handle: &RawWindowHandle) -> Option<WindowChromeMetrics> {
|
|
let RawWindowHandle::AppKit(appkit_handle) = window_handle else {
|
|
return None;
|
|
};
|
|
|
|
let ns_view = ns_view_from_handle(appkit_handle)?;
|
|
let ns_window = ns_view.window()?;
|
|
|
|
Some(WindowChromeMetrics {
|
|
traffic_lights_size: traffic_lights_metrics(&ns_window)?,
|
|
})
|
|
}
|
|
|
|
fn traffic_lights_metrics(ns_window: &NSWindow) -> Option<Vec2> {
|
|
// Button order is CloseButton, MiniaturizeButton, ZoomButton:
|
|
let close_button = ns_window
|
|
.standardWindowButton(NSWindowButton::CloseButton)?
|
|
.frame();
|
|
let zoom_button = ns_window
|
|
.standardWindowButton(NSWindowButton::ZoomButton)?
|
|
.frame();
|
|
|
|
let left_margin = close_button.origin.x;
|
|
let right_margin = left_margin; // for symmetry
|
|
|
|
let total_width = zoom_button.origin.x + zoom_button.size.width + right_margin;
|
|
|
|
let top_margin = close_button.origin.y;
|
|
let bottom_margin = top_margin; // Usually symmetric
|
|
let total_height = top_margin + close_button.size.height + bottom_margin;
|
|
|
|
Some(Vec2::new(total_width as f32, total_height as f32))
|
|
}
|
|
|
|
fn ns_view_from_handle(handle: &AppKitWindowHandle) -> Option<&NSView> {
|
|
let ns_view_ptr = handle.ns_view.as_ptr().cast::<NSView>();
|
|
|
|
// Validate the pointer is non-null
|
|
if ns_view_ptr.is_null() {
|
|
None
|
|
} else {
|
|
// SAFETY:
|
|
// - We've verified the pointer is non-null
|
|
// - The pointer comes from the windowing system, so it should be valid
|
|
// - NSView pointers from AppKit are expected to remain valid for the window lifetime
|
|
#[expect(unsafe_code)]
|
|
unsafe {
|
|
ns_view_ptr.as_ref()
|
|
}
|
|
}
|
|
}
|