-
Notifications
You must be signed in to change notification settings - Fork 75
Menu API
A message other scripts can send to open a uosc menu serialized as JSON. You can optionally pass a submenu_id
to pre-open a submenu. The ID is the submenu title chain leading to the submenu concatenated with >
, for example Tools > Aspect ratio
.
Menu data structure (pseudo types):
MenuBase {
title?: string;
items: Item[];
selected_index?: integer;
keep_open?: boolean;
id?: string; // Default IDs look like `{root} > Submenu title`. You can overwrite it with this.
on_search?: 'callback' | string | string[]; // If command, query & menu_id added as last params.
on_paste?: 'callback' | string | string[]; // If command, value & menu_id added as last params.
on_move?: 'callback' | string | string[]; // If command, from_index, to_index, and menu_id added as last params.
search_style?: 'on_demand' | 'palette' | 'disabled'; // default: on_demand
search_debounce?: 'submit' | number; // default: 0
search_suggestion?: string;
search_submenus?: boolean;
actions?: Action[];
}
Menu extends MenuBase {
type?: string;
on_close?: 'callback' | string | string[];
callback?: string[];
}
Submenu extends MenuBase {
hint?: string;
bold?: boolean;
italic?: boolean;
align?: 'left'|'center'|'right';
muted?: boolean;
separator?: boolean;
}
Item = Command | Submenu;
Command {
title?: string;
hint?: string;
icon?: string;
value: string | string[];
active?: integer;
selectable?: boolean;
bold?: boolean;
italic?: boolean;
align?: 'left'|'center'|'right';
muted?: boolean;
separator?: boolean;
keep_open?: boolean;
actions?: Action[];
}
Action {
name: string;
icon: string;
tooltip?: string;
}
It's not necessary to define selected_index
as it'll default to the first active
item, or 1st item in the list.
When Command.value
is a string, it'll be passed to mp.command(value)
. If it's a table (array) of strings, it'll be used as mp.commandv(table.unpack(value))
. The same goes for Menu.on_close
and on_search
. on_search
additionally appends the current search string as the last parameter.
Menu.type
is used to refer to this menu in update-menu
and close-menu
.
While the menu is open this value will be available in user-data/uosc/menu/type
and the shared-script-properties
entry uosc-menu-type
. If no type was provided, those will be set to 'undefined'
.
search_style
can be:
-
on_demand
(default) - Search input pops up when user starts typing, or presses/
orctrl+f
, depending on user configuration. It disappears onshift+backspace
, or when input text is cleared. -
palette
- Search input is always visible and can't be disabled. In this mode, menutitle
is used as input placeholder when no text has been entered yet. -
disabled
- Menu can't be searched.
search_debounce
controls how soon the search happens after the last character was entered in milliseconds. Entering new character resets the timer. Defaults to 300
. It can also have a special value 'submit'
, which triggers a search only after ctrl+enter
was pressed.
search_submenus
makes uosc's internal search handler (when no on_search
callback is defined) look into submenus as well, effectively flattening the menu for the duration of the search. This property is inherited by all submenus.
search_suggestion
fills menu search with initial query string. Useful for example when you want to implement something like subtitle downloader, you'd set it to current file name.
item.icon
property accepts icon names. You can pick one from here: Google Material Icons
There is also a special icon name spinner
which will display a rotating spinner. Along with a no-op command on an item and keep_open=true
, this can be used to display placeholder menus/items that are still loading.
on_paste
is triggered when user pastes a string while menu is opened. Works the same as on_search
.
When keep_open
is true
, activating the item will not close the menu. This property can be defined on both menus and items, and is inherited from parent to child if child doesn't overwrite it.
actions
adds buttons to items in the menu. It can be on each item if each needs different buttons, or just on a menu to add same buttons to all items. If user presses the button instead of the item, its name will be available on callback's event.action
property, otherwise it's nil
. You can only get this data by using the callback
interface documented below.
callback
enables gradual takeover of the menu lifecycle. Most event will be sent to be handled by your callback, and the rest (close
, search
, move
) can be configured to do so as well (documented below). Value are params to be used in script-message-to
command before event data to reach your message handler (mp.commandv('script-message-to', callback[1], ...callback[n], event_data)
). Assuming callback
handler is defined, here is a table of what events it receives and under what conditions:
Event | Condition | Note |
---|---|---|
activate |
always | Any enter or primary click with any modifier combination is turned into activate event. You need to send close-menu message to close the menu. |
move |
on_move='callback' |
Condition informs us that you're handling this event, so we can adjust selected item index. (ctrl+up/down/pgup/pgdwn/home/end) |
remove |
always | Request for item to be removed from list. (ctrl+del) |
search |
on_search='callback' |
Condition informs us that you're handling this event, so we redirect our search handling to callback. (ctrl+enter) |
key |
always | Only keys that don't already have a function are sent. You'll get enter (and its modifier combinations) only if no item is selected and user presses enter key, otherwise it turns into activate event.Due to environment limitations, we don't listen to every shortcut possible. You can see which ones are bound by reading the Menu:enable_key_bindings() function source code in src/uosc/elements/Menu.lua . |
paste |
on_paste='callback' |
Condition informs us that you're handling this event, so we don't start search on paste, but redirect it to callback. (ctrl+v) |
back |
always | Fired when user tries to navigate back in submenu structure when there's no parent menu. (backspace) |
close |
on_close='callback' |
When handled by callback, menu won't close on its own, you have to do it manually by sending close-menu message. (esc, or primary mouse button on background) |
event_data
is a json encoded string with one of these interfaces (pseudo types):
EventActivate {
type: 'activate';
index: number;
value: any;
action?: string;
keep_open?: boolean; // Inherited from item or its menu prop.
modifiers: string;
menu_id: string;
}
EventMove {type: 'move'; from_index: number; to_index: number; menu_id: string;}
EventRemove {type: 'remove'; index: number; value: any; menu_id: string;}
EventSearch {type: 'search'; query: string; menu_id: string;}
EventKey {
type: 'key';
id: string; // e.g.: `alt+ctrl+enter` always lowercase, modifiers in alphabetical order, key last
key: string; // e.g.: `enter`. Note: mouse primary is normalized to `enter`.
modifiers: string; // e.g.: `alt+ctrl` lowercase & in alphabetical order
alt: boolean;
ctrl: boolean;
shift: boolean;
menu_id: string;
selected_item?: {index: number; value: any; action?: string;}
}
EventPaste {type: 'paste'; value: string; menu_id: string; selected_item?: {index: number; value: any; action?: string;}}
EventBack {type: 'back';}
EventClose {type: 'close';}
Example:
local utils = require('mp.utils')
local menu = {
type = 'menu_type',
title = 'Custom menu',
items = {
{title = 'Foo', hint = 'foo', value = 'quit'},
{title = 'Bar', hint = 'bar', value = 'quit', active = true},
}
}
mp.commandv('script-message-to', 'uosc', 'open-menu', utils.format_json(menu))
Callback and item actions example:
local utils = require('mp.utils')
local menu = {
type = 'menu_type',
title = 'Custom menu',
callback = {mp.get_script_name(), 'menu-event'},
actions = {
{name = 'thumbs_up', icon = 'thumbs_up', title = 'Tooltip text'},
},
items = {
{title = 'Foo', hint = 'foo', value = 'foo'},
{title = 'Bar', hint = 'bar', value = 'bar', active = true},
}
}
-- Open menu
mp.commandv('script-message-to', 'uosc', 'open-menu', utils.format_json(menu))
-- Handle events
mp.register_script_message('menu-event', function(json)
local event = utils.parse_json(json)
if event.type == 'activate' then
print(data.value) -- 'foo' | 'bar'
print(data.action) -- nil | 'thumbs_up'
end
end)
Updates currently opened menu with the same type
.
The difference between this and open-menu
is that if the same type menu is already open, open-menu
will reset the menu as if it was newly opened, while update-menu
will update it's data.
update-menu
, along with {menu/item}.keep_open
property and item.command
that sends a message back can be used to create a self updating menu with some limited UI. Example:
local utils = require('mp.utils')
local script_name = mp.get_script_name()
local state = {
checkbox = 'no',
radio = 'bar'
}
function command(str)
return string.format('script-message-to %s %s', script_name, str)
end
function create_menu_data()
return {
type = 'test_menu',
title = 'Test menu',
keep_open = true,
items = {
{
title = 'Checkbox',
icon = state.checkbox == 'yes' and 'check_box' or 'check_box_outline_blank',
value = command('set-state checkbox ' .. (state.checkbox == 'yes' and 'no' or 'yes'))
},
{
title = 'Radio',
hint = state.radio,
items = {
{
title = 'Foo',
icon = state.radio == 'foo' and 'radio_button_checked' or 'radio_button_unchecked',
value = command('set-state radio foo')
},
{
title = 'Bar',
icon = state.radio == 'bar' and 'radio_button_checked' or 'radio_button_unchecked',
value = command('set-state radio bar')
},
{
title = 'Baz',
icon = state.radio == 'baz' and 'radio_button_checked' or 'radio_button_unchecked',
value = command('set-state radio baz')
},
},
},
{
title = 'Submit',
icon = 'check',
value = command('submit'),
keep_open = false
},
}
}
end
mp.add_forced_key_binding('t', 'test_menu', function()
local json = utils.format_json(create_menu_data())
mp.commandv('script-message-to', 'uosc', 'open-menu', json)
end)
mp.register_script_message('set-state', function(prop, value)
state[prop] = value
-- Update currently opened menu
local json = utils.format_json(create_menu_data())
mp.commandv('script-message-to', 'uosc', 'update-menu', json)
end)
mp.register_script_message('submit', function(prop, value)
-- Do something with state
end)
Closes the menu. If the optional parameter type
is provided, then the menu only
closes if it matches Menu.type
of the currently open menu.