-
Notifications
You must be signed in to change notification settings - Fork 14
Description
First the motivation: After implementing keybindings and context menus in spec, I started to be increasingly unhappy with it since both are ways to define actions to be applied to a widget, but one is “visible” and the other is not, and it just implements a keybinding (keybinding that needs to be displayed in the context menu). So internally we had : a menu that was displaying keybindings but not reacting to them. And then at other side, the keybindings.
The repeated pattern that emerged is that you always were doing something like this :
mypresenter
contextMenu: [ self commanderActionGroup asMenuPresenter ];
contextKeybindings: self commanderActionGroup asKMCategory.
This was annoying because it looks (is) inefficient and the fact the implementation was split is prone to errors (you could declare keybindings in a menu that are not later in the real bindings and vice-versa).
So I unified both, by using commander as the glue, but creating an API so people is not forced to create commander classes for each menu.
The new vocabulary is simple:
Presenters now understand actionGroup: message that recives a SpCommandGroup from commander. Then the rest is resolved by commander: title, description, tooltip, icon and shortcut.
To make the mechanism easy to use and not require the creation of a class for each action, there are several utilities :
Instantiable generic groups and actions
There is a SpActionGroup class and a SpAction class that can be used directly in the presenter, e.g.
myPresenter
actionGroup: (SpActionGroup new
add: (SpAction new
name: 'Load';
description: 'Load a STON product description.';
iconName: #smallOpen;
action: [ self doLoad ];
yourself);
add: (SpAction new
name: 'Reset';
description: 'Reset current product (cleans the destination).';
iconName: #smallDelete;
shortcutKey: $x ctrl;
action: [ self doReset ];
actionEnabled: [ self isResetEnabled ];
yourself);
add: (SpAction new
name: 'Open';
description: 'Open product directory (if exists).';
shortcutKey: $o ctrl;
beShortcutOnly;
action: [ self doOpen ];
yourself);
yourself)
There are a couple things to notice here:
beShortcutOnly defines if the action will be displayed in the context menu or not (there are few reasons why you would do that, but you can 😉)
there is vocabulary to execute a block action:, but also to ask if that action should be enabled or not actionEnabled:.
If you think this is too verbose, there are several convenience methods (class and instance) to make this easier.
Scripting mode
I also added a family of methods to make this more scriptable, so you can write the same example as before in this way:
myPresenter
actionGroupWith: [ :group | group
addActionWith: [ :act | act
name: 'Load';
description: 'Load a STON product description.';
iconName: #smallOpen;
action: [ self doLoad ] ];
addActionWith: [ :act | act
name: 'Reset';
description: 'Reset current product (cleans the destination).';
iconName: #smallDelete;
shortcutKey: $x ctrl;
action: [ self doReset ];
actionEnabled: [ self isResetEnabled ] ];
addShortcutWith: [ :act | act
shortcutKey: $o ctrl;
action: [ self doOpen ] ] ]
Commander integration
Since this is just commander with a spice, you can also do:
myPresenter
actionGroup: self myCommanderGroup
Drawbacks
Of course, this is not absent of drawbacks,
the most important of this is that right now, there is no way the context menu can be calculated on the fly while displaying it.
This is implementable, but I do not think is desirable: Every Human Interfase Guideline in earth says context needs to be always visible, with the not-valid options disabled. This helps people to know what they can do, even if they cannot do it that moment 🙂
This creates a dependency with Commander. I do not think this is a problem, Commander is important in the UI environment. Also, the fact that we have SpActionGroup and SpAction as entry points means we can cut-off Commander when we want.
Status
This is not yet operational in Spec-Morphic (not hard to implement, just not done yet), but completely functional in Spec-Gtk4