Class rtk.NativeMenu

A utility class that opens an OS-native popup menu.

Here's an example that opens a context menu when the mouse is right-clicked anywhere in the window.

local menu = rtk.NativeMenu()
menu:set({
    -- Here is an example of passing custom user data (the url field in this case) to
    -- the handler below.
    {'Visit Website', id='website', url='https://www.reaper.fm/'},
    -- The items in the window submenu depend on the js_ReaScriptAPI, so disable
    -- the entire submenu if that's not available.
    {'Window', disabled=not rtk.has_js_reascript_api, submenu={
        -- Use dynamic discovery of the checked value by passing a function.  The custom
        -- 'win' flag is used by the menu handler later to determine if this is a simple
        -- boolean window attribute.
        {'Docked', id='docked', checked=function() return window.docked end, win=true},
        {'Pinned', id='pinned', checked=function() return window.pinned end, win=true},
        {'Borderless', id='borderless', checked=function() return window.borderless end, win=true},
        {'Translucent', id='translucent', checked=function() return window.opacity < 1.0 end},
    }},
    rtk.NativeMenu.SEPARATOR,
    {'Exit', id='exit'},
})
window.onclick = function(self, event)
    if event.button == rtk.mouse.BUTTON_RIGHT then
        menu:open_at_mouse():done(function(item)
            if not item then
                -- User clicked off of menu, nothing selected.
                return
            end
            if item.id == 'website' then
                rtk.open_url(item.url)
            elseif item.win then
                window:attr(item.id, not window[item.id])
            elseif item.id == 'translucent' then
                window:attr('opacity', window.opacity == 1.0 and 0.5 or 1.0)
            elseif item.id == 'exit' then
                rtk.quit()
            end
        end)
    end
end

Class API

Synopsis

Methods
rtk.NativeMenu()

Constructor to create a new NativeMenu

set()

Sets the menu according to the supplied table

item()

Retrieves an individual menu item table by either its user-defined id or its auto-generated index

items()

Iterator over all items

open()

Opens the popup menu at the given window coordinates

open_at_mouse()

Opens the menu at the current mouse position

open_at_widget()

Opens the menu relative to the given widget

rtk.NativeMenu(menu)

Constructor to create a new NativeMenu

Parameters
menu (table)

a menu table as defined by set()

Return Values
(rtk.NativeMenu)

the new instance

rtk.NativeMenu:set(menu)

Sets the menu according to the supplied table.

The table contains a sequence of items, where each individual menu item is itself a table in the form:

{label, id=(any), disabled=(boolean), checked=(boolean), hidden=(boolean), ...}

where:

  • label (string): the label as seen in the popup menu (required). Can be rtk.NativeMenu.SEPARATOR to display a separator. Unlike the other fields, this does not need to be explicitly named and can be positional. However, label='Foo' is allowed if preferred.
  • id (any): an arbitrary (but tostring()able) user-defined value which can be accessed via the value returned by the open() functions. Optional, but if not specified, the item can be fetched with item() using an index field that will be added to the item table.
  • checked (boolean or function): if true, a checkmark will show next to the item. Optional, and assumes false if not specified.
  • disabled (boolean or function): if true, the menu item will be visible but grayed out and cannot be selected. Optional, and assumes false if not specified.
  • hidden (boolean or function): if true, the item will not be visible at all in the popup menu. Can be used to easily toggle item visibility without having to reset the menu with set(). Optional, and assumes false if not specified.
  • ...: you may pass any other named fields in the table as user data and they will be included in the table returned by the open*() functions.

For fields above so marked, if a function is provided it is invoked at the time the popup menu is opened, which allows for dynamic evaluation of those properties.

As a convenience, the menu item element can be a string, where it's simply used as the label and all other fields above are assumed nil.

Submenus are also supported, where the item table takes the form:

{label, submenu=(table), checked=(boolean), disabled=(boolean), hidden=(boolean)}

where:

  • label (string): the label for the submenu item (required).
  • submenu (table): a table of menu items as described above
  • checked (boolean or function): as above
  • disabled (boolean or function): as above
  • hidden (boolean or function): as above

Submenus can be aribtrarily nested.

rtk.NativeMenu:item(idx_or_id)

Retrieves an individual menu item table by either its user-defined id or its auto-generated index.

Example
local menu = rtk.NativeMenu{'Foo', {'Bar', id='bar'}, 'Baz'}
-- We assigned an id to Bar so we can fetch it by its id
local bar = menu:item('bar')
-- Baz doesn't have its own id, but its the third item in the list so we can
-- retrieve it by index.
local baz = menu:item(3)

Note that the table returned (assuming the item is found) is a shallow copy of the menu item's table you passed to set(). It will contain all the same fields you provided, but the top-level table itself is a different instance.

Parameters
idx_or_id (number or string)

either the numeric positional index of the menu item (with indices starting at 1 as usual for Lua), or its user-defined id

Return Values
(table or nil)

the menu item table as passed to set() if found (rather, a shallow copy of it), or nil otherwise

rtk.NativeMenu:items()

Iterator over all items.

For submenus, only the inner items are returned here, not the parent item that contains the submenu. If you need to access the parent item of a submenu, you'll need to ensure it has an id field defined and explicitly retrieve it with item().

Example
local menu = rtk.NativeMenu{'Foo', 'Bar', {'Baz', checked=true}}
-- Loop over items and invert the checked field for each item
for item in menu:items() do
    item.checked = not item.checked
end
menu:open_at_mouse()
Return Values
(function)

iterator function

rtk.NativeMenu:open(x, y)

Opens the popup menu at the given window coordinates.

One noteworthy detail about this method is that it returns an rtk.Future and opens the menu asynchronously. This is because REAPER's underlying function to open menus is blocking, and we want the update cycle to have completed before opening the menu to ensure any related visual state (e.g. a button being pressed) has a chance to draw before the application becomes blocked by the menu.

Example
local menu = rtk.NativeMenu{'Foo', 'Bar', 'Baz'}
menu:open(10, 10):done(function(item)
    if item then
        log.info('selected item: %s', table.tostring(item))
    end
end)
Parameters
x (number)

the x coordinate for the menu relative to the window, where 0 is far left, and negative values are allowed

y (number)

the y coordinate for the menu relative to the window, where 0 is the top, and negative values are allowed

Return Values
(rtk.Future)

a Future which is completed when the menu is closed, and which receives either the menu item table of the selected item, or nil if no item was selected

rtk.NativeMenu:open_at_mouse()

Opens the menu at the current mouse position.

This calls open() based on current mouse position.

Return Values
(rtk.Future)

a Future which is completed when the menu is closed, and which receives either the menu item table of the selected item, or nil if no item was selected

rtk.NativeMenu:open_at_widget(widget, halign, valign)

Opens the menu relative to the given widget.

This calls open() based on current widget location and the supplied alignment values (if any).

Example
local menu = rtk.NativeMenu{'Foo', 'Bar', 'Baz'}
local button = window:add(rtk.Button{"Open Menu"})
button.onclick = function()
    -- Popup will obscure the button because of top alignment.
    menu:open_at_widget(button, 'left', 'top')
end
Widget must be drawn

The widget must have been drawn or this method will fail because the client coordinates of the widget won't be known.

The widget must have been drawn or this will fail because the client coordinates wouldn't be known.

Parameters
widget (rtk.Widget)

the widget to open the menu in relation to

halign (string or nil)

'left' (default if nil) to align the left edge of the menu with the left edge of the widget, or 'right' to align the left edge of the menu with the right edge of the widget. (Aligning against the right edge of the menu or centering isn't possible because the menu dimensions are unknown due to it being a native menu.)

valign (string or nil)

'top' to align the top edge of the menu with the top edge of the widget, or 'bottom' (default if nil) to align the top edge of the menu with the bottom edge of the widget.

Return Values
(rtk.Future)

a Future which is completed when the menu is closed, and which receives either the menu item table of the selected item, or nil if no item was selected