1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 14:49:06 -04:00

Add eframe::WindowChromeMetrics (macOS only) (#8015)

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!
This commit is contained in:
Emil Ernerfeldt
2026-03-25 10:54:17 +01:00
committed by GitHub
parent bfbf23b4fb
commit 0887b54c93
3 changed files with 82 additions and 0 deletions

View File

@@ -190,6 +190,9 @@ pub use web::{WebLogger, WebRunner};
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
mod native;
#[cfg(target_os = "macos")]
pub use native::macos::WindowChromeMetrics;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub use native::run::EframeWinitApplication;

View File

@@ -0,0 +1,76 @@
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()
}
}
}

View File

@@ -3,6 +3,9 @@ mod epi_integration;
mod event_loop_context;
pub mod run;
#[cfg(target_os = "macos")]
pub(crate) mod macos;
/// File storage which can be used by native backends.
#[cfg(feature = "persistence")]
pub mod file_storage;