diff options
| author | David Vazgenovich Shakaryan <dvshakaryan@gmail.com> | 2026-01-07 15:22:39 -0800 |
|---|---|---|
| committer | David Vazgenovich Shakaryan <dvshakaryan@gmail.com> | 2026-01-07 15:22:39 -0800 |
| commit | 2dc4809f5cba14838def5abea3220793ae0d8e4a (patch) | |
| tree | a0a2b746e37d99413e3d3d033553d15ce0bf6529 | |
| parent | c442abc24ebbddaf8d9fab36375128e06547d65e (diff) | |
| download | mpv-iptv-menu-2dc4809f5cba14838def5abea3220793ae0d8e4a.tar.gz mpv-iptv-menu-2dc4809f5cba14838def5abea3220793ae0d8e4a.tar.xz | |
move generic menu operations to metatable
| -rw-r--r-- | main.lua | 139 | ||||
| -rw-r--r-- | state.lua | 114 |
2 files changed, 135 insertions, 118 deletions
@@ -101,28 +101,7 @@ local function save_favourites() end local function set_cursor(pos, opts) - local menu = state:menu() - local lines = osd_menu_lines() - - local pos = math.max(1, math.min(pos, #menu.options)) - local top = menu.view_top - if opts and opts.centre then - top = pos - math.floor((osd_menu_lines() - 1) / 2) - elseif opts and opts.keep_offset then - top = top + pos - menu.cursor - end - - -- move view to keep selected option visible - if pos < top then - top = pos - elseif pos > top + lines - 1 then - top = pos - lines + 1 - end - - top = math.max(1, math.min(top, #menu.options - lines + 1)) - - menu.cursor = pos - menu.view_top = top + state:menu():set_cursor(pos, osd_menu_lines(), opts) if not (opts and opts.skip_redraw) then update_osd() @@ -408,29 +387,6 @@ local function push_group_menu(group) update_osd() end -local update_search_matches - -local function set_menu_sort(menu, bool) - if not menu.sorted == not bool then - return - end - - local key = menu.type == 'search' and 'search_options' or 'options' - if bool then - menu['orig_' .. key] = menu[key] - menu[key] = util.copy_table(menu[key]) - sort_options(menu[key]) - else - menu[key] = menu['orig_' .. key] - menu['orig_' .. key] = nil - end - - if menu.type == 'search' then - update_search_matches() - end - menu.sorted = bool -end - -- refresh options when navigating up the stack to a previous favourites menu. -- existing menu options are never removed, even if unfavourited. the new order -- is always respected, while still preserving the relative order of existing @@ -440,7 +396,7 @@ local function refresh_favourites_menu() local opt = menu.options[menu.cursor] local sorted = menu.sorted if sorted then - set_menu_sort(menu, false) + menu.set_sort(false) end local options = group_menu_options(catalogue:get(menu.group_id)) @@ -474,7 +430,7 @@ local function refresh_favourites_menu() menu.options = res if sorted then - set_menu_sort(menu, true) + menu.set_sort(true, sort_options) end if opt then cursor_to_object(opt.id) @@ -850,56 +806,17 @@ local function search_menu_options(options) return ret end -function update_search_matches() - local menu = state:menu() - - if #menu.search_text == 0 then - menu.options = menu.search_options - update_osd() - return - end - - -- no utf8 :( - local case_sensitive = not not menu.search_text:find('%u') - - local options = {} - for _, v in ipairs(menu.search_options) do - local matches = {} - - local name = v.name - if not case_sensitive then - name = name:lower() - end - - local i, j = 0, 0 - while true do - i, j = name:find(menu.search_text, j + 1, true) - if not i then - break - end - matches[#matches+1] = {start = i, stop = j} - end - - if #matches > 0 then - local t = util.copy_table(v) - t.matches = matches - options[#options+1] = t - end - end - - menu.options = options -end - local function search_input_char(event) if event.event ~= 'down' and event.event ~= 'repeat' then return end local menu = state:menu() - menu.search_text = menu.search_text:sub(1, menu.search_cursor - 1) .. - event.key_text .. menu.search_text:sub(menu.search_cursor) - menu.search_cursor = menu.search_cursor + #event.key_text - update_search_matches() + menu:set_search_text( + menu.search_text:sub(1, menu.search_cursor - 1) .. + event.key_text .. + menu.search_text:sub(menu.search_cursor)) + menu:set_search_cursor(menu.search_cursor + #event.key_text) update_osd() end @@ -910,10 +827,10 @@ local function search_input_bs() end local pos = util.utf8_seek(menu.search_text, menu.search_cursor, -1) - menu.search_text = menu.search_text:sub(1, pos - 1) .. - menu.search_text:sub(menu.search_cursor) - menu.search_cursor = pos - update_search_matches() + menu:set_search_text( + menu.search_text:sub(1, pos - 1) .. + menu.search_text:sub(menu.search_cursor)) + menu:set_search_cursor(pos) update_osd() end @@ -923,22 +840,17 @@ local function search_input_del() return end - menu.search_text = menu.search_text:sub(1, menu.search_cursor - 1) .. + menu:set_search_text( + menu.search_text:sub(1, menu.search_cursor - 1) .. menu.search_text:sub(util.utf8_seek( - menu.search_text, menu.search_cursor, 1)) - update_search_matches() + menu.search_text, menu.search_cursor, 1))) update_osd() end local function set_search_cursor(pos) - local menu = state:menu() - local pos = math.max(1, math.min(#menu.search_text + 1, pos)) - if pos == menu.search_cursor then - return + if state:menu():set_search_cursor(pos) then + update_osd() end - - menu.search_cursor = pos - update_osd() end local function search_cursor_left() @@ -977,19 +889,15 @@ local function start_search() menu.title = title menu.search_active = true - menu.search_cursor = #menu.search_text + 1 - menu.cursor = 1 - menu.view_top = 1 + menu:set_search_cursor(#menu.search_text + 1) + menu:set_cursor(1, osd_menu_lines()) else - state:push_menu({ + menu = state:push_menu({ title = title, type = 'search', + options = search_menu_options(menu.options), search_active = true, - search_options = search_menu_options(menu.options), - search_text = '', - search_cursor = 1, }) - update_search_matches() end update_osd() @@ -1010,10 +918,9 @@ local function cancel_search() -- cancelling resumed search restores previous state if menu.prev_search_text then - menu.search_text = menu.prev_search_text + menu:set_search_text(menu.prev_search_text) menu.cursor = menu.prev_cursor menu.view_top = menu.prev_view_top - update_search_matches() end_search() return end @@ -1030,7 +937,7 @@ local function toggle_menu_sort() return end - set_menu_sort(menu, not menu.sorted) + menu:set_sort(not menu.sorted, sort_options) update_osd() end @@ -1,9 +1,14 @@ -- Copyright 2025 David Vazgenovich Shakaryan +local util = require('util') + local state = {} local mt = {} mt.__index = mt +local menu_mt = {} +menu_mt.__index = menu_mt + function state.new() return setmetatable({ menus = {}, @@ -19,18 +24,28 @@ function mt:menu() end function mt:push_menu(t) - local menu = { + local menu = setmetatable({ options = {}, cursor = 1, view_top = 1, - } + }, menu_mt) for k, v in pairs(t) do menu[k] = v end + if menu.type == 'search' then + menu.search_options = menu.options + menu.search_text = menu.search_text or '' + menu.search_cursor = menu.search_cursor or + #menu.search_text + 1 + menu:update_search_matches() + end + self.depth = self.depth + 1 self.menus[self.depth] = menu + + return menu end -- returns index if found @@ -67,4 +82,99 @@ function mt:insert_favourite_before_next_in_menu(id) self:add_favourite(id) end +function menu_mt:set_cursor(pos, lines, opts) + local pos = math.max(1, math.min(pos, #self.options)) + local top = self.view_top + if opts and opts.centre then + top = pos - math.floor((lines - 1) / 2) + elseif opts and opts.keep_offset then + top = top + pos - self.cursor + end + + -- move view to keep selected option visible + if pos < top then + top = pos + elseif pos > top + lines - 1 then + top = pos - lines + 1 + end + + top = math.max(1, math.min(top, #self.options - lines + 1)) + + self.cursor = pos + self.view_top = top +end + +function menu_mt:set_sort(bool, f) + if not self.sorted == not bool then + return + end + + local key = self.type == 'search' and 'search_options' or 'options' + if bool then + self['orig_' .. key] = self[key] + self[key] = util.copy_table(self[key]) + f(self[key]) + else + self[key] = self['orig_' .. key] + self['orig_' .. key] = nil + end + + if self.type == 'search' then + self:update_search_matches() + end + self.sorted = bool +end + +function menu_mt:set_search_cursor(pos) + local pos = math.max(1, math.min(#self.search_text + 1, pos)) + if pos == self.search_cursor then + return false + end + + self.search_cursor = pos + return true +end + +function menu_mt:set_search_text(str) + self.search_text = str + self:update_search_matches() +end + +function menu_mt:update_search_matches() + if #self.search_text == 0 then + self.options = self.search_options + return + end + + -- no utf8 :( + local case_sensitive = not not self.search_text:find('%u') + + local options = {} + for _, v in ipairs(self.search_options) do + local matches = {} + + local name = v.name + if not case_sensitive then + name = name:lower() + end + + local i, j = 0, 0 + while true do + i, j = name:find(self.search_text, j + 1, true) + if not i then + break + end + matches[#matches+1] = {start = i, stop = j} + end + + if #matches > 0 then + local t = util.copy_table(v) + t.matches = matches + options[#options+1] = t + end + end + + self.options = options +end + return state |
