Reputation: 7799
I have the next code to build an application menu in AppDelegate
(an AppDelegate
code was taken from the official example):
impl AppDelegate {
fn new(mtm: MainThreadMarker, menu: Option<Menu>) -> Id<Self> {
let this = mtm.alloc();
let this = this.set_ivars(Ivars {
mtm,
menu,
});
unsafe { msg_send_id![super(this), init] }
}
fn build_menu(&self, menu: &Menu, ns_menu: &Id<NSMenu>) {
let ivars = self.ivars();
for item in menu.items.iter() {
let ns_menu_item = NSMenuItem::new(ivars.mtm);
let title = NSString::from_str(&item.name);
unsafe { ns_menu_item.setTitle(&title) };
if let Some(on_click) = &item.on_click {
unsafe { ns_menu_item.setAction(Some(on_click)) };
}
if let Some(submenu) = &item.submenu {
let ns_submenu = NSMenu::new(ivars.mtm);
self.build_menu(submenu, &ns_submenu);
ns_menu_item.setSubmenu(Some(&ns_submenu));
}
ns_menu.addItem(&ns_menu_item);
}
}
fn create_menu(&self, application: Id<NSApplication>) {
let ivars = self.ivars();
if let Some(menu) = &ivars.menu {
let main_menu = NSMenu::new(ivars.mtm);
application.setMainMenu(Some(&main_menu));
self.build_menu(menu, &main_menu);
}
}
}
The problem is in this part of the code:
if let Some(on_click) = &item.on_click {
unsafe { ns_menu_item.setAction(Some(on_click)) };
}
The on_click: fn()
is a Rust function and I can't get how to pass it as an Objective-C selector using objc2.
In generated AppKit that method looks as below:
#[method(setAction:)]
pub unsafe fn setAction(&self, action: Option<Sel>);
Upvotes: 0
Views: 110
Reputation: 7799
I'm pretty sure there's a much simpler way but... I've resolved my problem using a custom NSMenuItem
class:
#[derive(Debug)]
#[allow(unused)]
struct MacMenuItemIvars {
action: Option<fn()>,
}
declare_class!(
struct MacMenuItem;
unsafe impl ClassType for MacMenuItem {
type Super = NSMenuItem;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "MenuItem";
}
impl DeclaredClass for MacMenuItem {
type Ivars = MacMenuItemIvars;
}
unsafe impl MacMenuItem {
#[method(callback)]
fn __callback(&self) {
if let Some(action) = self.ivars().action {
action();
}
}
}
unsafe impl NSObjectProtocol for MacMenuItem {}
);
impl MacMenuItem {
fn new<S>(mtm: MainThreadMarker, title: S, action: Option<fn()>) -> Id<Self>
where
S: Into<String>,
{
let this = mtm.alloc();
let this = this.set_ivars(MacMenuItemIvars {
action,
});
let item: Id<MacMenuItem> = unsafe { msg_send_id![super(this), init] };
let title: String = title.into();
let title = NSString::from_str(&title);
unsafe { item.setTitle(&title) };
if action.is_some() {
unsafe { item.setTarget(Some(&item)) };
unsafe { item.setAction(Some(sel!(callback))) };
}
item
}
}
There must be a way to achieve the same result using Objective-C blocks. However, I didn't get how.
Upvotes: 0