This module provides a menu widget that is similar to the ones
commonly found in most graphical interface toolkits. Menus carry
entries that can be activated with the mouse and keyboard and invoke
event handlers that you specify when creating the menus and entries.
General Information
Menus carry a sequence of items, expressed by the MenuItem type.
Items can be:
entries - named menu items that can be activated with the
keyboard or mouse
submenus - entries that contain nested menus
separators - horizontal lines dividing up groups of other
entries
gaps - vertical space between items
Menu entries can be either enabled or disabled; their status in
this regard is determined by invoking a function of type s -> Bool
at rendering and event-handling time.
Menus and submenus support both mouse and keyboard interaction. See
handleMenuEvent for details.
Menus have an orientation that can be changed with
setMenuOrientation to suit different writing systems. This affects
how entries and submenus are rendered and how left/right arrow keys
navigate submenus.
Use Cases
This module provides a fully general Menu type and a few
specialized menu types for common menu use cases:
SimpleMenu: a menu with entries that have EventM handlers.
Create one of these with simpleMenu. This is a good starting point.
DispatchingMenu: a menu whose entries correspond to abstract key
events bound to keys by a KeyDispatcher. Create one of these with
menuWithDispatcher. This is a good choice when you already have a
KeyDispatcher set up and would like menu entries to be triggered
by the rebindable keys that trigger the dispatcher's handlers.
Menu: the fully general type for menus. Create one of these with
menu.
Depending on the type of menu you're creating, different item
constructors may apply. See the MenuItem type aliases since their
naming convention follows that of the menu types.
See the MenuDemo and MenuKeybindingsDemo demonstration programs
for complete working examples of using this API.
Adding Menus to An Application
To use this module in an application:
Choose a menu type that you want to work with such as SimpleMenu.
For each menu that you want to host, add an application state field
and lens for a value of the menu's type, and add a constructor
to the application's resource name type, with an argument of
type MenuRegion. Add lenses to the application state type with
makeLenses.
Populate the application's initial state with the menus.
Handle incoming events first with handleMenuEvent, and when
handleMenuEvent returns False, pass unhandled events on to the
existing application event handler.
As desired, add entries to the application's AttrMap for the
attributes used in this module.
Use MenuBar if you want to host more than one
menu in a group.
Handling Events
Menu events are handled with handleMenuEvent, and any unhandled
events should be deferred to the application's event handler.
To support mouse events, each menu must be identified by a unique
resource name; this is done by providing a resource name constructor
when creating each menu. The application's name type must provide a
constructor of type MenuRegion -> n to uniquely identify the menu
and its constituent parts. For example, if the application's resource
name type is as follows,
data Name = Editor1 | Editor2
It would need to be modified so that a new data constructor (e.g.
FileMenu) could be given to the menu constuctors:
data Name = Editor1 | Editor2 | FileMenu MenuRegion
Menus and their items are parameterized on three types:
s: the application state type used in the App type,
n: the application resource name type used in the application's
Widget type, and
k: the type of data carried and handled by the menu's event
handler when a menu entry has been activated.
Menus contain a sequence of items of type MenuItem. See the
documentation above and the constructors for both menus and menu
items to create menus.
A menu is either open, in which case its contents are being shown
in a floating layer above the application's UI and it is responding
to events that manipulate the menu's selected entry, or it is
closed, in which case its contents are not shown and it is not
responding to events other than mouse clicks on its title. The menu's
open/closed state is affected by calls to openMenu, closeMenu,
and mouse click events on the menu's title.
At any given time, a menu may or may not have a currently-selected
entry. See handleMenuEvent for details on how keyboard and mouse
events influence the choice and behavior of the selected entry. When
an entry is selected, it can be activated by an Enter keypress or
a mouse click. When activated, its event data is used to invoke the
menu's event handler.
To support mouse events, each menu must be identified by a unique
resource name; this is done by providing a resource name constructor
when creating each menu. The application's name type must provide a
constructor of type MenuRegion -> n to uniquely identify the menu
and its constituent parts.
A menu carries an event handler that will be invoked by
handleMenuEvent whenever a menu entry is selected.
Create a menu entry with the specified label and event data.
When the entry is activated, its event data will be passed to the
event handler of the enclosing menu.
By default, this entry has no custom renderer so its appearance is
determined by the default entry renderer of the enclosing menu.
To change either of these behaviors, use setEntryRenderer or
setDefaultEntryRenderer.
By default, this entry is always enabled regardless of the
application state. To change this, use setEnabledWith.
This is the fully general entry constructor. For more specific use
cases, see the other MenuItem constructors in this module.
A title rendering function that highlights the specified character
with menuTitleKeyHighlightAttr if it appears in the title,
case-insensitively. Use with setTitleRenderer.
Set this menu entry's rendering function, overriding the menu's
default rendering behavior for this entry. This is equivalent to id
for non-entry items.
A specialization of Menu that has EventM handlers in each menu
entry that are evaluated whenever the entries are activated. Create
one of these with simpleMenu.
A specialization of Menu whose entries are associated with
specific keys or abstract key events handled by a KeyDispatcher.
Create one of these with menuWithDispatcher.
A trigger to be executed when an entry with this trigger
is activated. This is exposed for completeness only; use
menuEntryForKey, menuEntryForAction, and menuEntryForEvent to
work with this data type indirectly.
Create a Menu whose entries are activated by specific triggers,
including specified key bindings or abstract key events associated
with a KeyDispatcher. This uses entryWithKeybinding as its
default entry renderer to show available keybindings for entries
associated with key events.
Create a menu entry that is activated by the specified key binding,
irrespective of the enclosing menu's KeyDispatcher configuration.
This entry will show the specified keybinding in its text.
Create a menu entry that generates the specified abstract key event
when activated, thus triggering the enclosing menu's KeyDispatcher
handler for that event. This entry will show the first known
keybinding for the specified abstract key event, if any.
Create a menu entry that invokes the specified EventM action when
activated. Use this for entries that are not invoked by specific keys
or associated with abstract key events.
An entry rendering function usable with setDefaultEntryRenderer
and setEntryRenderer that renders a menu entry with the first known
available keybinding for its abstract event, as configured in the
specified KeyDispatcher.
Handle an event for this menu and return True, or return False
if the event was not handled (e.g. because the event was not a menu
title mouse click or because the menu was not open to receive the
event).
Events handled include:
Mouse clicks on the menu title will toggle whether the menu is
open.
If a submenu entry is selected, arrow keys will open and close it
depending on the menu orientation.
Mouse clicks on submenu entries will open their submenus.
Esc will close the menu if no submenus are open; otherwise it
will close the last open submenu.
If no entry is selected, the Down arrow key will select the first
entry and the Up arrow key will select the last entry.
If an entry is selected, the Down arrow key will select the next
entry and the Up arrow key will select the previous entry.
If the selected entry is a submenu and the submenu is open, events
will be delegated to the submenu until it closes.
In all other cases, this will attempt to defer to the menu's selected
entry or submenu to handle the event. This returns True if the
event was one of the above and was handled, True if the event was
not one of the above but was handled by the menu's selected entry, or
False otherwise.
A return value of True indicates that the event should not be
handled by the application because it was destined for the menu; a
return value of False indicates that the event should be handled by
the application because it did not affect the menu or its entries in
their current state for any reason. Consequently, a common pattern
when using this function will look something like this:
myApplicationEventHandler :: BrickEvent n e -> EventM n s ()
myApplicationEventHandler e = do
handled <- handleMenuEvent myMenuLens e
when (not handled) $ do
-- Go on to handle the event in the rest of the application
If the menu is closed, only its title is rendered. If the menu is
open, its title is rendered with its contents shown as a floating
layer vertically positioned below the title.
When menu contents are shown, they are rendered in a border, and
separators are rendered with hBorder. Use withBorderStyle to
change how such borders are drawn, e.g.,
drawUi :: s -> Widget n
drawUi s =
withBorderStyle unicodeRounded $
renderMenu s (s^.myMenu)