From 04550e4886837b43a99eaa26034eaef724bfab33 Mon Sep 17 00:00:00 2001 From: aminwhat Date: Mon, 1 Sep 2025 19:24:38 +0330 Subject: [PATCH 1/4] first try to add the native macos menu bar --- bevy_widgets/bevy_menu_bar/Cargo.toml | 5 ++ bevy_widgets/bevy_menu_bar/src/lib.rs | 116 +++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/bevy_widgets/bevy_menu_bar/Cargo.toml b/bevy_widgets/bevy_menu_bar/Cargo.toml index e2f2de62..34012595 100644 --- a/bevy_widgets/bevy_menu_bar/Cargo.toml +++ b/bevy_widgets/bevy_menu_bar/Cargo.toml @@ -6,6 +6,11 @@ edition = "2024" [dependencies] bevy.workspace = true bevy_editor_styles.workspace = true +cocoa = "0.26.1" +objc-rs = "0.2.8" +objc2 = "0.6.2" +objc2-app-kit = "0.3.1" +objc2-foundation = "0.3.1" [lints] workspace = true diff --git a/bevy_widgets/bevy_menu_bar/src/lib.rs b/bevy_widgets/bevy_menu_bar/src/lib.rs index a98864ee..f11a2d5a 100644 --- a/bevy_widgets/bevy_menu_bar/src/lib.rs +++ b/bevy_widgets/bevy_menu_bar/src/lib.rs @@ -17,7 +17,17 @@ pub struct MenuBarPlugin; impl Plugin for MenuBarPlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "assets/logo/bevy_logo.png"); - app.add_systems(Startup, menu_setup.in_set(MenuBarSet)); + + #[cfg(target_os = "macos")] + { + println!("Spawning the MacOs Menu Bar Native"); + app.add_systems(Startup, setup_native_macos_menu); + } + + #[cfg(not(target_os = "macos"))] + { + app.add_systems(Startup, menu_setup.in_set(MenuBarSet)); + } } } @@ -42,7 +52,7 @@ pub enum TopBarItem { Help, } -/// The setup system for the menu bar. +/// The setup system for the menu bar (Not Macos). fn menu_setup( mut commands: Commands, root: Query>, @@ -326,3 +336,105 @@ fn menu_setup( commands.spawn(hover_over_observer); commands.spawn(click_observer); } + +/// The setup system for the menu bar (macOS native). +#[allow(unsafe_code)] +#[cfg(target_os = "macos")] +fn setup_native_macos_menu() { + use objc2::sel; + use objc2::{ + MainThreadMarker, + rc::{Retained, autoreleasepool}, + }; + use objc2_app_kit::{NSApp, NSMenu, NSMenuItem}; + use objc2_foundation::NSString; + + unsafe { + autoreleasepool(|pool| { + let main_thread = MainThreadMarker::new_unchecked(); + + let app = NSApp(main_thread); + + // Main menu bar + let menubar = Retained::::autorelease(NSMenu::new(main_thread), pool); + + // === File menu === + let file_menu_item = + Retained::::autorelease(NSMenuItem::new(main_thread), pool); + menubar.addItem(&file_menu_item); + + let file_menu = Retained::::autorelease(NSMenu::new(main_thread), pool); + file_menu.setTitle(NSString::from_str("File").as_ref()); + file_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Quit").as_ref(), + sel!(terminate:).into(), + NSString::from_str("q").as_ref(), + ); + file_menu_item.setSubmenu(Some(&file_menu)); + + // === Edit menu === + let edit_menu_item = + Retained::::autorelease(NSMenuItem::new(main_thread), pool); + menubar.addItem(&edit_menu_item); + + let edit_menu = Retained::::autorelease(NSMenu::new(main_thread), pool); + edit_menu.setTitle(NSString::from_str("Edit").as_ref()); + edit_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Undo").as_ref(), + sel!(undo:).into(), + NSString::from_str("z").as_ref(), + ); + edit_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Redo").as_ref(), + sel!(redo:).into(), + NSString::from_str("Z").as_ref(), + ); + edit_menu_item.setSubmenu(Some(&edit_menu)); + + // === Build menu === + let build_menu_item = + Retained::::autorelease(NSMenuItem::new(main_thread), pool); + menubar.addItem(&build_menu_item); + + let build_menu = Retained::::autorelease(NSMenu::new(main_thread), pool); + build_menu.setTitle(NSString::from_str("Build").as_ref()); + build_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Run").as_ref(), + None, // no action yet + NSString::from_str("r").as_ref(), + ); + build_menu_item.setSubmenu(Some(&build_menu)); + + // === Window menu === + let window_menu_item = + Retained::::autorelease(NSMenuItem::new(main_thread), pool); + menubar.addItem(&window_menu_item); + + let window_menu = Retained::::autorelease(NSMenu::new(main_thread), pool); + window_menu.setTitle(NSString::from_str("Window").as_ref()); + window_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Minimize").as_ref(), + sel!(performMiniaturize:).into(), + NSString::from_str("m").as_ref(), + ); + window_menu_item.setSubmenu(Some(&window_menu)); + + // === Help menu === + let help_menu_item = + Retained::::autorelease(NSMenuItem::new(main_thread), pool); + menubar.addItem(&help_menu_item); + + let help_menu = Retained::::autorelease(NSMenu::new(main_thread), pool); + help_menu.setTitle(NSString::from_str("Help").as_ref()); + help_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("About").as_ref(), + None, + NSString::new().as_ref(), + ); + help_menu_item.setSubmenu(Some(&help_menu)); + + // Attach to app + app.setMainMenu(Some(&menubar)); + }); + } +} From 36c34aee1b6c3b9e093987d61341ef7fb847af60 Mon Sep 17 00:00:00 2001 From: aminwhat Date: Mon, 1 Sep 2025 19:50:06 +0330 Subject: [PATCH 2/4] working... --- bevy_widgets/bevy_menu_bar/src/lib.rs | 194 ++++++++++++++------------ 1 file changed, 101 insertions(+), 93 deletions(-) diff --git a/bevy_widgets/bevy_menu_bar/src/lib.rs b/bevy_widgets/bevy_menu_bar/src/lib.rs index f11a2d5a..26c652e0 100644 --- a/bevy_widgets/bevy_menu_bar/src/lib.rs +++ b/bevy_widgets/bevy_menu_bar/src/lib.rs @@ -4,6 +4,8 @@ //! such as "File", "Edit", "View", etc. use bevy::{asset::embedded_asset, prelude::*}; +#[cfg(target_os = "macos")] +use objc2::MainThreadMarker; use bevy_editor_styles::Theme; @@ -20,8 +22,8 @@ impl Plugin for MenuBarPlugin { #[cfg(target_os = "macos")] { - println!("Spawning the MacOs Menu Bar Native"); - app.add_systems(Startup, setup_native_macos_menu); + app.insert_non_send_resource(MainThreadMarker::new().unwrap()); + app.add_systems(PostStartup, setup_native_macos_menu); } #[cfg(not(target_os = "macos"))] @@ -340,101 +342,107 @@ fn menu_setup( /// The setup system for the menu bar (macOS native). #[allow(unsafe_code)] #[cfg(target_os = "macos")] -fn setup_native_macos_menu() { +fn setup_native_macos_menu(_marker: NonSend) { use objc2::sel; - use objc2::{ - MainThreadMarker, - rc::{Retained, autoreleasepool}, - }; use objc2_app_kit::{NSApp, NSMenu, NSMenuItem}; use objc2_foundation::NSString; unsafe { - autoreleasepool(|pool| { - let main_thread = MainThreadMarker::new_unchecked(); - - let app = NSApp(main_thread); - - // Main menu bar - let menubar = Retained::::autorelease(NSMenu::new(main_thread), pool); - - // === File menu === - let file_menu_item = - Retained::::autorelease(NSMenuItem::new(main_thread), pool); - menubar.addItem(&file_menu_item); - - let file_menu = Retained::::autorelease(NSMenu::new(main_thread), pool); - file_menu.setTitle(NSString::from_str("File").as_ref()); - file_menu.addItemWithTitle_action_keyEquivalent( - NSString::from_str("Quit").as_ref(), - sel!(terminate:).into(), - NSString::from_str("q").as_ref(), - ); - file_menu_item.setSubmenu(Some(&file_menu)); - - // === Edit menu === - let edit_menu_item = - Retained::::autorelease(NSMenuItem::new(main_thread), pool); - menubar.addItem(&edit_menu_item); - - let edit_menu = Retained::::autorelease(NSMenu::new(main_thread), pool); - edit_menu.setTitle(NSString::from_str("Edit").as_ref()); - edit_menu.addItemWithTitle_action_keyEquivalent( - NSString::from_str("Undo").as_ref(), - sel!(undo:).into(), - NSString::from_str("z").as_ref(), - ); - edit_menu.addItemWithTitle_action_keyEquivalent( - NSString::from_str("Redo").as_ref(), - sel!(redo:).into(), - NSString::from_str("Z").as_ref(), - ); - edit_menu_item.setSubmenu(Some(&edit_menu)); - - // === Build menu === - let build_menu_item = - Retained::::autorelease(NSMenuItem::new(main_thread), pool); - menubar.addItem(&build_menu_item); - - let build_menu = Retained::::autorelease(NSMenu::new(main_thread), pool); - build_menu.setTitle(NSString::from_str("Build").as_ref()); - build_menu.addItemWithTitle_action_keyEquivalent( - NSString::from_str("Run").as_ref(), - None, // no action yet - NSString::from_str("r").as_ref(), - ); - build_menu_item.setSubmenu(Some(&build_menu)); - - // === Window menu === - let window_menu_item = - Retained::::autorelease(NSMenuItem::new(main_thread), pool); - menubar.addItem(&window_menu_item); - - let window_menu = Retained::::autorelease(NSMenu::new(main_thread), pool); - window_menu.setTitle(NSString::from_str("Window").as_ref()); - window_menu.addItemWithTitle_action_keyEquivalent( - NSString::from_str("Minimize").as_ref(), - sel!(performMiniaturize:).into(), - NSString::from_str("m").as_ref(), - ); - window_menu_item.setSubmenu(Some(&window_menu)); - - // === Help menu === - let help_menu_item = - Retained::::autorelease(NSMenuItem::new(main_thread), pool); - menubar.addItem(&help_menu_item); - - let help_menu = Retained::::autorelease(NSMenu::new(main_thread), pool); - help_menu.setTitle(NSString::from_str("Help").as_ref()); - help_menu.addItemWithTitle_action_keyEquivalent( - NSString::from_str("About").as_ref(), - None, - NSString::new().as_ref(), - ); - help_menu_item.setSubmenu(Some(&help_menu)); - - // Attach to app - app.setMainMenu(Some(&menubar)); - }); + let main_thread = + MainThreadMarker::new().expect("setup_native_macos_menu must run on the main thread"); + + let app = NSApp(main_thread); + + // Ensure the app is activated so menus are visible + app.activate(); + + // Main menu bar + let menubar = NSMenu::new(main_thread); + + // === App Menu === + let app_menu_item = NSMenuItem::new(main_thread); + menubar.addItem(&app_menu_item); + + let app_menu = NSMenu::new(main_thread); + app_menu.setTitle(NSString::from_str("App").as_ref()); + app_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Quit").as_ref(), + sel!(terminate:).into(), + NSString::from_str("q").as_ref(), + ); + app_menu_item.setSubmenu(Some(&app_menu)); + + // === File menu === + let file_menu_item = NSMenuItem::new(main_thread); + menubar.addItem(&file_menu_item); + + let file_menu = NSMenu::new(main_thread); + file_menu.setTitle(NSString::from_str("File").as_ref()); + file_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Save").as_ref(), + None, + NSString::from_str("s").as_ref(), + ); + file_menu_item.setSubmenu(Some(&file_menu)); + + // === Edit menu === + let edit_menu_item = NSMenuItem::new(main_thread); + menubar.addItem(&edit_menu_item); + + let edit_menu = NSMenu::new(main_thread); + edit_menu.setTitle(NSString::from_str("Edit").as_ref()); + edit_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Undo").as_ref(), + sel!(undo:).into(), + NSString::from_str("z").as_ref(), + ); + edit_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Redo").as_ref(), + sel!(redo:).into(), + NSString::from_str("Z").as_ref(), + ); + edit_menu_item.setSubmenu(Some(&edit_menu)); + + // === Build menu === + let build_menu_item = NSMenuItem::new(main_thread); + menubar.addItem(&build_menu_item); + + let build_menu = NSMenu::new(main_thread); + build_menu.setTitle(NSString::from_str("Build").as_ref()); + build_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Run").as_ref(), + None, // no action yet + NSString::from_str("r").as_ref(), + ); + build_menu_item.setSubmenu(Some(&build_menu)); + + // === Window menu === + let window_menu_item = NSMenuItem::new(main_thread); + menubar.addItem(&window_menu_item); + + let window_menu = NSMenu::new(main_thread); + window_menu.setTitle(NSString::from_str("Window").as_ref()); + window_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("Minimize").as_ref(), + sel!(performMiniaturize:).into(), + NSString::from_str("m").as_ref(), + ); + window_menu_item.setSubmenu(Some(&window_menu)); + + // === Help menu === + let help_menu_item = NSMenuItem::new(main_thread); + menubar.addItem(&help_menu_item); + + let help_menu = NSMenu::new(main_thread); + help_menu.setTitle(NSString::from_str("Help").as_ref()); + help_menu.addItemWithTitle_action_keyEquivalent( + NSString::from_str("About").as_ref(), + None, + NSString::new().as_ref(), + ); + help_menu_item.setSubmenu(Some(&help_menu)); + + // Attach to app + app.setMainMenu(Some(&menubar)); } } From 3342848ffc5fb7ba1f8d50658e288a93f025b6c2 Mon Sep 17 00:00:00 2001 From: aminwhat Date: Mon, 1 Sep 2025 20:09:48 +0330 Subject: [PATCH 3/4] move the macos native dependencies in the workspace --- Cargo.toml | 6 ++++++ bevy_widgets/bevy_menu_bar/Cargo.toml | 11 ++++++----- bevy_widgets/bevy_menu_bar/src/lib.rs | 6 +++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5479b4a1..cabc80f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,3 +84,9 @@ bevy_undo = { path = "crates/bevy_undo" } bevy_infinite_grid = { path = "crates/bevy_infinite_grid" } bevy_editor_cam = { path = "crates/bevy_editor_cam" } bevy_clipboard = { path = "crates/bevy_clipboard" } + +# MacOS Native Dependencies +objc-rs = "0.2.8" +objc2 = "0.6.2" +objc2-app-kit = "0.3.1" +objc2-foundation = "0.3.1" diff --git a/bevy_widgets/bevy_menu_bar/Cargo.toml b/bevy_widgets/bevy_menu_bar/Cargo.toml index 34012595..df82f186 100644 --- a/bevy_widgets/bevy_menu_bar/Cargo.toml +++ b/bevy_widgets/bevy_menu_bar/Cargo.toml @@ -6,11 +6,12 @@ edition = "2024" [dependencies] bevy.workspace = true bevy_editor_styles.workspace = true -cocoa = "0.26.1" -objc-rs = "0.2.8" -objc2 = "0.6.2" -objc2-app-kit = "0.3.1" -objc2-foundation = "0.3.1" + +objc-rs.workspace = true +objc2.workspace = true +objc2-app-kit.workspace = true +objc2-foundation.workspace = true + [lints] workspace = true diff --git a/bevy_widgets/bevy_menu_bar/src/lib.rs b/bevy_widgets/bevy_menu_bar/src/lib.rs index 26c652e0..9bd3320d 100644 --- a/bevy_widgets/bevy_menu_bar/src/lib.rs +++ b/bevy_widgets/bevy_menu_bar/src/lib.rs @@ -4,11 +4,11 @@ //! such as "File", "Edit", "View", etc. use bevy::{asset::embedded_asset, prelude::*}; +use bevy_editor_styles::Theme; + #[cfg(target_os = "macos")] use objc2::MainThreadMarker; -use bevy_editor_styles::Theme; - /// The root node for the menu bar. #[derive(Component)] pub struct MenuBarNode; @@ -23,7 +23,7 @@ impl Plugin for MenuBarPlugin { #[cfg(target_os = "macos")] { app.insert_non_send_resource(MainThreadMarker::new().unwrap()); - app.add_systems(PostStartup, setup_native_macos_menu); + app.add_systems(Startup, setup_native_macos_menu); } #[cfg(not(target_os = "macos"))] From b7eb89384dc0cd1c15c43fdc6765f3a16925e797 Mon Sep 17 00:00:00 2001 From: aminwhat Date: Mon, 8 Sep 2025 20:29:44 +0330 Subject: [PATCH 4/4] fix: put the native macos deps in the bevy menu bar behind a cfg for platform specific deps --- bevy_widgets/bevy_menu_bar/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/bevy_widgets/bevy_menu_bar/Cargo.toml b/bevy_widgets/bevy_menu_bar/Cargo.toml index df82f186..add883d7 100644 --- a/bevy_widgets/bevy_menu_bar/Cargo.toml +++ b/bevy_widgets/bevy_menu_bar/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" bevy.workspace = true bevy_editor_styles.workspace = true +[target.'cfg(target_os = "macos")'.dependencies] objc-rs.workspace = true objc2.workspace = true objc2-app-kit.workspace = true