Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to pass a Rust function as a selector? #614

Open
ghostman2013 opened this issue May 6, 2024 · 5 comments
Open

How to pass a Rust function as a selector? #614

ghostman2013 opened this issue May 6, 2024 · 5 comments
Labels
A-framework Affects the framework crates and the translator for them question Further information is requested

Comments

@ghostman2013
Copy link

ghostman2013 commented May 6, 2024

Hello!

I use a little modified code that's based on examples from objc2-app-kit crate sources:

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);
        }
    }
}

Just a trivial AppDelegate with menu builder. However, I hit troubles at this step:

if let Some(on_click) = &item.on_click {
    unsafe { ns_menu_item.setAction(Some(on_click)) };
}

The on_click: fn() is a typical Rust function but I can't get how to pass it as a selector, to NSMenuItem called it on click.

Unfortunately, I couldn't find any examples. Could you explain, please, how I can make it?

@madsmtm
Copy link
Owner

madsmtm commented May 6, 2024

This is a common pattern used in Apple's frameworks, see the documentation on Target-Action, and it's a bit of a pain to handle when you're used to Rust's closures.

There's a similar issue here: #585

Try to have a look at that, in short you have to make a method on your delegate with a selector like onClick:, place it on the menu item with .setAction(Some(sel!(onClick:))) and .setTarget(Some(self)), and then you have to run your click handler in onClick:.

I know that's not a real explanation, but I don't have time to write up a full example right now, will do so later.

@ghostman2013
Copy link
Author

ghostman2013 commented May 10, 2024

This is a common pattern used in Apple's frameworks, see the documentation on Target-Action, and it's a bit of a pain to handle when you're used to Rust's closures.

There's a similar issue here: #585

Try to have a look at that, in short you have to make a method on your delegate with a selector like onClick:, place it on the menu item with .setAction(Some(sel!(onClick:))) and .setTarget(Some(self)), and then you have to run your click handler in onClick:.

I know that's not a real explanation, but I don't have time to write up a full example right now, will do so later.

No, you have explained it very clearly. Thank you! I got how to make.

@madsmtm madsmtm added question Further information is requested A-framework Affects the framework crates and the translator for them labels May 24, 2024
@Korne127
Copy link

Korne127 commented Jun 24, 2024

Since you said in rust-windowing/winit#1751 that the object containing the selector methods doesn't need to be an NSApplicationDelegate, I tried following this with a custom class like this

pub fn create_menu() {
    let mtm = MainThreadMarker::new().unwrap();
    let menu_bar_handler = MenuBarHandler::new(mtm);

    let app = NSApplication::sharedApplication(mtm);
    let menu_bar = NSMenu::new(mtm);
    app.setMainMenu(Some(&menu_bar));

    let app_menu_header = NSMenuItem::new(mtm);
    let app_menu = NSMenu::new(mtm);

    let quit_item = unsafe {
        NSMenuItem::initWithTitle_action_keyEquivalent(
            mtm.alloc(),
            ns_string!("Test"),
            Some(sel!(testaction:)),
            ns_string!("t"),
        )
    };
    unsafe { quit_item.setTarget(Some(&menu_bar_handler)) };

    app_menu.addItem(&quit_item);
    app_menu_header.setSubmenu(Some(&app_menu));
    menu_bar.addItem(&app_menu_header);
}

declare_class!(
    struct MenuBarHandler;

    unsafe impl ClassType for MenuBarHandler {
        type Super = NSObject;
        type Mutability = mutability::MainThreadOnly;
        const NAME: &'static str = "MenuBarHandler";
    }

    impl DeclaredClass for MenuBarHandler {}

    unsafe impl MenuBarHandler {
        #[method(testaction:)]
        fn open(&self, _: &NSNotification) {
            println!("This is a test");
        }
    }
);

impl MenuBarHandler {
    fn new(mtm: MainThreadMarker) -> Retained<Self> {
        unsafe { msg_send_id![mtm.alloc(), init] }
    }
}

However, while the menu bar appears as expected, the Test item is grated out, which means that the selector isn't linked correctly to the method. Can you tell me what I'm doing wrong?

@madsmtm
Copy link
Owner

madsmtm commented Jun 24, 2024

See the explanation in #585 (comment), the issue is that you don't store menu_bar_handler anywhere, so it's deallocated at the end of create_menu.

@Korne127
Copy link

Thank you for the reply! Yes, that was the issue.
I guess it's not bad to have an (otherwise) working example here in case other people stumble on this too though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-framework Affects the framework crates and the translator for them question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants