diff --git a/README.md b/README.md index 588a49b..76f1ae7 100644 --- a/README.md +++ b/README.md @@ -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; diff --git a/scripts/uosc.lua b/scripts/uosc.lua index de35cda..5a1140d 100644 --- a/scripts/uosc.lua +++ b/scripts/uosc.lua @@ -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 diff --git a/scripts/uosc_shared/elements/Menu.lua b/scripts/uosc_shared/elements/Menu.lua index cbca6ee..afc0605 100644 --- a/scripts/uosc_shared/elements/Menu.lua +++ b/scripts/uosc_shared/elements/Menu.lua @@ -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, diff --git a/scripts/uosc_shared/elements/Timeline.lua b/scripts/uosc_shared/elements/Timeline.lua index 722778f..158267d 100644 --- a/scripts/uosc_shared/elements/Timeline.lua +++ b/scripts/uosc_shared/elements/Timeline.lua @@ -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, diff --git a/scripts/uosc_shared/lib/std.lua b/scripts/uosc_shared/lib/std.lua index 1261666..31d8b9b 100644 --- a/scripts/uosc_shared/lib/std.lua +++ b/scripts/uosc_shared/lib/std.lua @@ -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