briskml/brisk

Experimental Menu/Toolbar handling API

wokalski opened this issue · 2 comments

I came up with an idea for declarative handling of Menus and Toolbars. The cool thing is that it's completely user space by using hooks, yet provides a very declarative API.

let make = (...) => component(hooks => {
  // Menu would be global menu but similarly we could pass/access a global menu object
  // if there are cases for multiple menus in apps
  let hooks = Menu.render(~menu=SharedMenus.file, [
      Item.make("Export", ~handler=saveHandler),
      Item.make("Export as...", ~handler=saveAsHandler),
    ], ~priority=100 /* arbitrary integer, we might provide some built ins too */,  hooks);
  ...
})

Behind the scenes it would be an effect that adds/updates the items in a useEffect handler on render and call imperative add/remove there.

Two unanswered questions

  • There's a more complex case when we want to have some greyed out elements, how do we model that?
  • What's the negotiation model if we have some kind of global identity for common elements like File -> Save

Cool idea @wokalski ! I'm glad you're thinking about this.

What would happen if multiple components call Menu.render - would it be OK for them to call different ~menu's? I guess this is related to your second bullet point.

For Revery - we might want to render this natively in some platforms (ie, OSX), but have custom rendering for others (Windows) - I guess we would need some sort of pluggable back-end for this.

An alternate model I was thinking about this is a JSX-like approach, like:

App.setApplicationMenu(app, (...some sort of state / context) => {
            <Menu>
                 <Submenu label="Edit">
                      <MenuItem role="undo" onClick={doUndo()} />
                      <MenuItem role="redo" onClick={doRedo()} />
                      <Submenu label="Clipboard">
                        <MenuItem role="copy" />
                        <MenuItem role="paste" enabled=false />
                      </Submenu>
                  </Submenu>
             </Menu>
});

I think I would prefer a functional-style API like this after working with Electron's model (which is clunky! https://electronjs.org/docs/api/menu#new-menu).

We could potentially use a separate reconciler for this - and it could dispatch imperative menu update commands to the backing native API.

@bryphe yes, consider rendering an additive operation not a setter. (something else would probably unmount or maybe we could also put some of the menu items behind a reactive condition, i.e. an If effect.

I'd like to avoid setters. Whether we use JSX or not is secondary, I could've used JSX in my first example but kinda went with normal functions 😄

Menu.render would be kind of like returning from render but whatever it "renders" would escape the view hierarchy.