diff --git a/Cargo.lock b/Cargo.lock index ade3a5a..1815ac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1508,6 +1508,8 @@ dependencies = [ "image", "log", "muda", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", "pollster 1.0.1", "rfd", "serde_json", diff --git a/apps/gridcraft/Cargo.toml b/apps/gridcraft/Cargo.toml index c867d24..cd8311b 100644 --- a/apps/gridcraft/Cargo.toml +++ b/apps/gridcraft/Cargo.toml @@ -42,3 +42,13 @@ muda = { version = "0.21", default-features = false } # by PdfCraft and PhotoCraft) registers the handler (Objective-C interop needs unsafe, which our # workspace forbids) behind a safe API. Pinned exactly: review the diff before bumping. fmv-macos-events = "=0.1.0" + +# tests/mac_menu_appkit.rs: reads AppKit's menu bar back. Already in Cargo.lock (muda, winit); +# pinned exactly to the same versions. +[target.'cfg(target_os = "macos")'.dev-dependencies] +objc2 = "=0.6.5" +objc2-app-kit = { version = "=0.3.2", default-features = false, features = ["std", "NSApplication", "NSMenu", "NSMenuItem", "NSResponder"] } + +[[test]] +name = "mac_menu_appkit" +harness = false diff --git a/apps/gridcraft/src/native_menu.rs b/apps/gridcraft/src/native_menu.rs index b127543..1469242 100644 --- a/apps/gridcraft/src/native_menu.rs +++ b/apps/gridcraft/src/native_menu.rs @@ -215,6 +215,7 @@ impl NativeMenu { ]); let _ = menu.append(&app_menu); let mut theme_items = Vec::new(); + let mut window_menu = None; for (title, entries) in tree() { let sub = Submenu::new(title, true); for (label, id, acc) in entries { @@ -237,8 +238,17 @@ impl NativeMenu { let _ = sub.append(&item); } let _ = menu.append(&sub); + if title == "Window" { + window_menu = Some(sub); + } } menu.init_for_nsapp(); + // NSApp's windowsMenu is where macOS adds Move & Resize, Fill and Center, the targets of the + // system tiling shortcuts (Fn+Ctrl+arrows, Fn+F). Only once the menu is NSApp's: muda looks + // the submenu up in the installed main menu. + if let Some(window) = window_menu { + window.set_as_windows_menu_for_nsapp(); + } let (tx, rx) = channel(); let ctx = ctx.clone(); MenuEvent::set_event_handler(Some(move |e: MenuEvent| { diff --git a/apps/gridcraft/tests/mac_menu_appkit.rs b/apps/gridcraft/tests/mac_menu_appkit.rs new file mode 100644 index 0000000..e713f66 --- /dev/null +++ b/apps/gridcraft/tests/mac_menu_appkit.rs @@ -0,0 +1,34 @@ +//! The macOS menu bar as AppKit sees it, on the main thread (`harness = false`): install it through +//! the real muda backend (`src/native_menu.rs`) and read NSApp back. The Window menu must be NSApp's +//! `windowsMenu`: that is where macOS adds Move & Resize, Fill and Center, the targets of the system +//! window tiling shortcuts (Fn+Ctrl+arrows, Fn+F), and lists the open windows (#716). + +// The app's own module, built into this test (cargo builds harness = false tests with cfg(test), so its +// unit tests compile here too without a harness to run them). +#[cfg(target_os = "macos")] +#[path = "../src/native_menu.rs"] +#[allow(unused_imports, dead_code)] +mod native_menu; + +#[cfg(target_os = "macos")] +fn main() { + use objc2::MainThreadMarker; + use objc2_app_kit::NSApplication; + + let mtm = MainThreadMarker::new().expect("harness = false runs on the main thread"); + let nsapp = NSApplication::sharedApplication(mtm); + nsapp.finishLaunching(); + + let ctx = egui::Context::default(); + let _menu = native_menu::NativeMenu::install(&ctx); + + let bar = nsapp.mainMenu().expect("a main menu"); + let tops: Vec = (0..bar.numberOfItems()).filter_map(|i| bar.itemAtIndex(i)?.submenu()).map(|m| m.title().to_string()).collect(); + assert_eq!(tops, ["GridCraft", "File", "Edit", "View", "Insert", "Format", "Tools", "Data", "Window", "Help"]); + assert_eq!(nsapp.windowsMenu().map(|m| m.title().to_string()).as_deref(), Some("Window"), "AppKit knows the Window menu"); + + println!("mac_menu_appkit: ok"); +} + +#[cfg(not(target_os = "macos"))] +fn main() {}