revery-ui / revery

:zap: Native, high-performance, cross-platform desktop apps - built with Reason!
https://www.outrunlabs.com/revery/
MIT License
8.06k stars 196 forks source link

feat(macOS): native menus #1024

Closed zbaylin closed 3 years ago

zbaylin commented 3 years ago

This wraps the NSMenu* API in the FFI, and tries to give a generic API from the Caml-end.

Example code:

  let menubar = Native.Menu.getMenuBarHandle();
  let submenu = Native.Menu.create("Zach's Menu!");
  let subsubmenu = Native.Menu.create("Zach's Submenu!");
  let subsubmenuItem =
    Native.Menu.Item.create(~title="Zach's Submenu Item!", ~onClick=() => ());
  let subsubmenuItem2 =
    Native.Menu.Item.create(~title="Zach's Submenu Item?", ~onClick=() => ());
  let submenuItem =
    Native.Menu.Item.create(~title="Zach's Menu Item!", ~onClick=() =>
      Native.Menu.removeSubmenu(~parent=submenu, ~child=subsubmenu)
    );

  Native.Menu.addItem(submenu, submenuItem);
  Native.Menu.addItem(subsubmenu, subsubmenuItem);
  Native.Menu.insertItemAt(subsubmenu, subsubmenuItem2, 0);

  Native.Menu.addSubmenu(~parent=menubar, ~child=submenu);
  Native.Menu.insertSubmenuAt(~parent=submenu, ~child=subsubmenu, ~idx=0);
bryphe commented 3 years ago

This is awesome, @zbaylin ! Will be a huge step forward for Revery / Onivim to have an application menu on OSX.

I had a few questions / comments around memory management - I'm always nervous about that since it can give us surprise crashes (or memory leaks).

bryphe commented 3 years ago

Just tried this API out! It's looking great @zbaylin - nice work!

I added a Native menu example that lets me clear the menu bar and add a full set (like we'll do for Onivim when the menu state changes):

2020-12-02 11 43 13

I think the only thing we're missing is a way to specify the keyEquivalent - right now, all the menu items are hard-coded to Command+P. A quick and easy way would be to just pass an optional/nullable string to the item creation - this would let us implement Command+ and Command+Shift+ shortcuts.

Longer term - we might want to add a KeyEquivalent module - which would let us add other marks (like the option key): https://stackoverflow.com/questions/6229848/cocoa-setting-the-key-equivalent. Under the hood, it seems like it's just a string with some helpers for appending special characters to represent option and other interesting keys (backspace, arrow keys, etc) - something like:

module KeyEquivalent: {
   type t;
   let ofString: string => t;
};

And then the Item module could have:

module Item: {
   type t;
   ...
   let create: (~title: string, ~onClick: unit => unit, ~shortcutKey: option(KeyEquivalent.t) => t;
};
zbaylin commented 3 years ago

@bryphe I just added a KeyEquivalent module that uses an API like almost exactly what you described. Let me know what you think!