feat: menu items now accept selectable and align options

Allows creating placeholder items like "Empty" that can't be selected or clicked.
This commit is contained in:
tomasklaen 2023-05-15 11:22:53 +02:00
parent 9c544bf565
commit 128c76c3ba
5 changed files with 43 additions and 16 deletions

View File

@ -428,6 +428,8 @@ Command {
value: string | string[];
bold?: boolean;
italic?: boolean;
align?: 'left'|'center'|'right';
selectable?: boolean;
muted?: boolean;
active?: integer;
keep_open?: boolean;

View File

@ -617,7 +617,7 @@ end
function select_current_chapter()
local current_chapter
if state.time and state.chapters then
_, current_chapter = itable_find(state.chapters, function(c) return state.time >= c.time end, true)
_, current_chapter = itable_find(state.chapters, function(c) return state.time >= c.time end, #state.chapters, 1)
end
set_state('current_chapter', current_chapter)
end

View File

@ -3,13 +3,13 @@ local Element = require('uosc_shared/elements/Element')
-- Menu data structure accepted by `Menu:open(menu)`.
---@alias MenuData {type?: string; title?: string; hint?: string; keep_open?: boolean; separator?: boolean; items?: MenuDataItem[]; selected_index?: integer;}
---@alias MenuDataItem MenuDataValue|MenuData
---@alias MenuDataValue {title?: string; hint?: string; icon?: string; value: any; bold?: boolean; italic?: boolean; muted?: boolean; active?: boolean; keep_open?: boolean; separator?: boolean;}
---@alias MenuDataValue {title?: string; hint?: string; icon?: string; value: any; bold?: boolean; italic?: boolean; muted?: boolean; active?: boolean; keep_open?: boolean; separator?: boolean; selectable?: boolean; align?: 'left'|'center'|'right'}
---@alias MenuOptions {mouse_nav?: boolean; on_open?: fun(); on_close?: fun(); on_back?: fun(); on_move_item?: fun(from_index: integer, to_index: integer, submenu_path: integer[]); on_delete_item?: fun(index: integer, submenu_path: integer[])}
-- Internal data structure created from `Menu`.
---@alias MenuStack {id?: string; type?: string; title?: string; hint?: string; selected_index?: number; keep_open?: boolean; separator?: boolean; items: MenuStackItem[]; parent_menu?: MenuStack; submenu_path: integer[]; active?: boolean; width: number; height: number; top: number; scroll_y: number; scroll_height: number; title_width: number; hint_width: number; max_width: number; is_root?: boolean; fling?: Fling}
---@alias MenuStackItem MenuStackValue|MenuStack
---@alias MenuStackValue {title?: string; hint?: string; icon?: string; value: any; active?: boolean; bold?: boolean; italic?: boolean; muted?: boolean; keep_open?: boolean; separator?: boolean; title_width: number; hint_width: number}
---@alias MenuStackValue {title?: string; hint?: string; icon?: string; value: any; active?: boolean; bold?: boolean; italic?: boolean; muted?: boolean; keep_open?: boolean; separator?: boolean; selectable?: boolean; align?: 'left'|'center'|'right'; title_width: number; hint_width: number}
---@alias Fling {y: number, distance: number, time: number, easing: fun(x: number), duration: number, update_cursor?: boolean}
---@alias Modifiers {shift?: boolean, ctrl?: boolean, alt?: boolean}
@ -156,7 +156,9 @@ function Menu:update(data)
-- Update items
local first_active_index = nil
menu.items = {{title = t('Empty'), value = 'ignore', italic = 'true', muted = 'true'}}
menu.items = {
{title = t('Empty'), value = 'ignore', italic = 'true', muted = 'true', selectable = false, align = 'center'}
}
for i, item_data in ipairs(menu_data.items or {}) do
if item_data.active and not first_active_index then first_active_index = i end
@ -164,6 +166,7 @@ function Menu:update(data)
local item = {}
table_assign(item, item_data, {
'title', 'icon', 'hint', 'active', 'bold', 'italic', 'muted', 'value', 'keep_open', 'separator',
'selectable', 'align'
})
if item.keep_open == nil then item.keep_open = menu.keep_open end
@ -267,8 +270,10 @@ function Menu:reset_navigation()
self:scroll_to(menu.scroll_y) -- clamps scroll_y to scroll limits
if self.mouse_nav then
self:select_item_below_cursor()
elseif menu.items and #menu.items > 0 then
self:select_index(itable_find(menu.items, function(item) return item.selectable ~= false end))
else
self:select_index((menu.items and #menu.items > 0) and clamp(1, menu.selected_index or 1, #menu.items) or nil)
self:select_index(nil)
end
-- Walk up the parent menu chain and activate items that lead to current menu
@ -445,15 +450,21 @@ end
---@param menu? MenuStack
function Menu:prev(menu)
menu = menu or self.current
menu.selected_index = math.max(menu.selected_index and menu.selected_index - 1 or #menu.items, 1)
self:scroll_to_index(menu.selected_index, menu, true)
local initial_index = menu.selected_index and menu.selected_index - 1 or #menu.items
if initial_index > 0 then
menu.selected_index = itable_find(menu.items, function(item) return item.selectable ~= false end, initial_index, 1)
self:scroll_to_index(menu.selected_index, menu, true)
end
end
---@param menu? MenuStack
function Menu:next(menu)
menu = menu or self.current
menu.selected_index = math.min(menu.selected_index and menu.selected_index + 1 or 1, #menu.items)
self:scroll_to_index(menu.selected_index, menu, true)
local initial_index = menu.selected_index and menu.selected_index + 1 or 1
if initial_index <= #menu.items then
menu.selected_index = itable_find(menu.items, function(item) return item.selectable ~= false end, initial_index)
self:scroll_to_index(menu.selected_index, menu, true)
end
end
function Menu:back()
@ -498,7 +509,10 @@ end
function Menu:open_selected_item_soft() self:open_selected_item({keep_open = true}) end
function Menu:open_selected_item_preselect() self:open_selected_item({preselect_submenu_item = true}) end
function Menu:select_item_below_cursor() self.current.selected_index = self:get_item_index_below_cursor() end
function Menu:select_item_below_cursor()
local index = self:get_item_index_below_cursor()
self.current.selected_index = index and self.current.items[index].selectable ~= false and index or nil
end
---@param index integer
function Menu:move_selected_item_to(index)
@ -787,7 +801,13 @@ function Menu:render()
item.ass_safe_title = item.ass_safe_title or ass_escape(item.title)
local clip = '\\clip(' .. ax .. ',' .. math.max(item_ay, ay) .. ','
.. title_cut_x .. ',' .. math.min(item_by, by) .. ')'
ass:txt(content_ax, item_center_y, 4, item.ass_safe_title, {
local title_x, align = content_ax, 4
if item.align == 'right' then
title_x, align = title_cut_x, 6
elseif item.align == 'center' then
title_x, align = content_ax + (title_cut_x - content_ax) / 2, 5
end
ass:txt(title_x, item_center_y, align, item.ass_safe_title, {
size = self.font_size, color = font_color, italic = item.italic, bold = item.bold, wrap = 2,
opacity = text_opacity * (item.muted and 0.5 or 1), clip = clip,
shadow = 1, shadow_color = shadow_color,

View File

@ -411,7 +411,7 @@ function Timeline:render()
-- Chapter title
if #state.chapters > 0 then
local _, chapter = itable_find(state.chapters, function(c) return hovered_seconds >= c.time end, true)
local _, chapter = itable_find(state.chapters, function(c) return hovered_seconds >= c.time end, #state.chapters, 1)
if chapter and not chapter.is_end_only then
ass:tooltip(tooltip_anchor, chapter.title_wrapped, {
size = self.font_size, offset = 10, responsive = false, bold = true,

View File

@ -77,13 +77,18 @@ end
---@param itable table
---@param compare fun(value: any, index: number)
---@param from_end? boolean Search from the end of the table.
---@param from? number Where to start search, defaults to `1`.
---@param to? number Where to end search, defaults to `#itable`.
---@return number|nil index
---@return any|nil value
function itable_find(itable, compare, from_end)
local from, to, step = from_end and #itable or 1, from_end and 1 or #itable, from_end and -1 or 1
function itable_find(itable, compare, from, to)
from, to = from or 1, to or #itable
if from == 0 or from > #itable or to == 0 or to > #itable then return end
local step = from < to and 1 or -1
for index = from, to, step do
if compare(itable[index], index) then return index, itable[index] end
if index > 0 and index <= #itable and compare(itable[index], index) then
return index, itable[index]
end
end
end