294 lines
8.3 KiB
Lua
294 lines
8.3 KiB
Lua
|
local mp = require 'mp'
|
||
|
local scroll_list = {
|
||
|
global_style = [[]],
|
||
|
header_style = [[{\q2\fs35\c&00ccff&}]],
|
||
|
list_style = [[{\q2\fs25\c&Hffffff&}]],
|
||
|
wrapper_style = [[{\c&00ccff&\fs16}]],
|
||
|
cursor_style = [[{\c&00ccff&}]],
|
||
|
selected_style = [[{\c&Hfce788&}]],
|
||
|
|
||
|
cursor = [[➤\h]],
|
||
|
indent = [[\h\h\h\h]],
|
||
|
|
||
|
num_entries = 16,
|
||
|
wrap = false,
|
||
|
empty_text = "no entries"
|
||
|
}
|
||
|
|
||
|
--formats strings for ass handling
|
||
|
--this function is based on a similar function from https://github.com/mpv-player/mpv/blob/master/player/lua/console.lua#L110
|
||
|
function scroll_list.ass_escape(str, replace_newline)
|
||
|
if replace_newline == true then replace_newline = "\\\239\187\191n" end
|
||
|
|
||
|
--escape the invalid single characters
|
||
|
str = str:gsub('[\\{}\n]', {
|
||
|
-- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if
|
||
|
-- it isn't followed by a recognised character, so add a zero-width
|
||
|
-- non-breaking space
|
||
|
['\\'] = '\\\239\187\191',
|
||
|
['{'] = '\\{',
|
||
|
['}'] = '\\}',
|
||
|
-- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
|
||
|
-- consecutive newlines
|
||
|
['\n'] = '\239\187\191\\N',
|
||
|
})
|
||
|
|
||
|
-- Turn leading spaces into hard spaces to prevent ASS from stripping them
|
||
|
str = str:gsub('\\N ', '\\N\\h')
|
||
|
str = str:gsub('^ ', '\\h')
|
||
|
|
||
|
if replace_newline then
|
||
|
str = str:gsub("\\N", replace_newline)
|
||
|
end
|
||
|
return str
|
||
|
end
|
||
|
|
||
|
--format and return the header string
|
||
|
function scroll_list:format_header_string(str)
|
||
|
return str
|
||
|
end
|
||
|
|
||
|
--appends the entered text to the overlay
|
||
|
function scroll_list:append(text)
|
||
|
if text == nil then return end
|
||
|
self.ass.data = self.ass.data .. text
|
||
|
end
|
||
|
|
||
|
--appends a newline character to the osd
|
||
|
function scroll_list:newline()
|
||
|
self.ass.data = self.ass.data .. '\\N'
|
||
|
end
|
||
|
|
||
|
--re-parses the list into an ass string
|
||
|
--if the list is closed then it flags an update on the next open
|
||
|
function scroll_list:update()
|
||
|
if self.hidden then self.flag_update = true
|
||
|
else self:update_ass() end
|
||
|
end
|
||
|
|
||
|
--prints the header to the overlay
|
||
|
function scroll_list:format_header()
|
||
|
self:append(self.header_style)
|
||
|
self:append(self:format_header_string(self.header))
|
||
|
self:newline()
|
||
|
end
|
||
|
|
||
|
--formats each line of the list and prints it to the overlay
|
||
|
function scroll_list:format_line(index, item)
|
||
|
self:append(self.list_style)
|
||
|
|
||
|
if index == self.selected then self:append(self.cursor_style..self.cursor..self.selected_style)
|
||
|
else self:append(self.indent) end
|
||
|
|
||
|
self:append(item.style)
|
||
|
self:append(item.ass)
|
||
|
self:newline()
|
||
|
end
|
||
|
|
||
|
--refreshes the ass text using the contents of the list
|
||
|
function scroll_list:update_ass()
|
||
|
self.ass.data = self.global_style
|
||
|
self:format_header()
|
||
|
|
||
|
if #self.list < 1 then
|
||
|
self:append(self.empty_text)
|
||
|
self.ass:update()
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local start = 1
|
||
|
local finish = start+self.num_entries-1
|
||
|
|
||
|
--handling cursor positioning
|
||
|
local mid = math.ceil(self.num_entries/2)+1
|
||
|
if self.selected+mid > finish then
|
||
|
local offset = self.selected - finish + mid
|
||
|
|
||
|
--if we've overshot the end of the list then undo some of the offset
|
||
|
if finish + offset > #self.list then
|
||
|
offset = offset - ((finish+offset) - #self.list)
|
||
|
end
|
||
|
|
||
|
start = start + offset
|
||
|
finish = finish + offset
|
||
|
end
|
||
|
|
||
|
--making sure that we don't overstep the boundaries
|
||
|
if start < 1 then start = 1 end
|
||
|
local overflow = finish < #self.list
|
||
|
--this is necessary when the number of items in the dir is less than the max
|
||
|
if not overflow then finish = #self.list end
|
||
|
|
||
|
--adding a header to show there are items above in the list
|
||
|
if start > 1 then self:append(self.wrapper_style..(start-1)..' item(s) above\\N\\N') end
|
||
|
|
||
|
for i=start, finish do
|
||
|
self:format_line(i, self.list[i])
|
||
|
end
|
||
|
|
||
|
if overflow then self:append('\\N'..self.wrapper_style..#self.list-finish..' item(s) remaining') end
|
||
|
self.ass:update()
|
||
|
end
|
||
|
|
||
|
--moves the selector down the list
|
||
|
function scroll_list:scroll_down()
|
||
|
if self.selected < #self.list then
|
||
|
self.selected = self.selected + 1
|
||
|
self:update_ass()
|
||
|
elseif self.wrap then
|
||
|
self.selected = 1
|
||
|
self:update_ass()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--moves the selector up the list
|
||
|
function scroll_list:scroll_up()
|
||
|
if self.selected > 1 then
|
||
|
self.selected = self.selected - 1
|
||
|
self:update_ass()
|
||
|
elseif self.wrap then
|
||
|
self.selected = #self.list
|
||
|
self:update_ass()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--moves the selector to the list next page
|
||
|
function scroll_list:move_pagedown()
|
||
|
if #self.list > self.num_entries then
|
||
|
self.selected = self.selected + self.num_entries
|
||
|
if self.selected > #self.list then self.selected = #self.list end
|
||
|
self:update_ass()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--moves the selector to the list previous page
|
||
|
function scroll_list:move_pageup()
|
||
|
if #self.list > self.num_entries then
|
||
|
self.selected = self.selected - self.num_entries
|
||
|
if self.selected < 1 then self.selected = 1 end
|
||
|
self:update_ass()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--moves the selector to the list begin
|
||
|
function scroll_list:move_begin()
|
||
|
if #self.list > 1 then
|
||
|
self.selected = 1
|
||
|
self:update_ass()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--moves the selector to the list end
|
||
|
function scroll_list:move_end()
|
||
|
if #self.list > 1 then
|
||
|
self.selected = #self.list
|
||
|
self:update_ass()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--adds the forced keybinds
|
||
|
function scroll_list:add_keybinds()
|
||
|
for _,v in ipairs(self.keybinds) do
|
||
|
mp.add_forced_key_binding(v[1], 'dynamic/'..self.ass.id..'/'..v[2], v[3], v[4])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--removes the forced keybinds
|
||
|
function scroll_list:remove_keybinds()
|
||
|
for _,v in ipairs(self.keybinds) do
|
||
|
mp.remove_key_binding('dynamic/'..self.ass.id..'/'..v[2])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--opens the list and sets the hidden flag
|
||
|
function scroll_list:open_list()
|
||
|
self.hidden = false
|
||
|
if not self.flag_update then self.ass:update()
|
||
|
else self.flag_update = false ; self:update_ass() end
|
||
|
end
|
||
|
|
||
|
--closes the list and sets the hidden flag
|
||
|
function scroll_list:close_list()
|
||
|
self.hidden = true
|
||
|
self.ass:remove()
|
||
|
end
|
||
|
|
||
|
--modifiable function that opens the list
|
||
|
function scroll_list:open()
|
||
|
if self.hidden then self:add_keybinds() end
|
||
|
self:open_list()
|
||
|
end
|
||
|
|
||
|
--modifiable function that closes the list
|
||
|
function scroll_list:close()
|
||
|
self:remove_keybinds()
|
||
|
self:close_list()
|
||
|
end
|
||
|
|
||
|
--toggles the list
|
||
|
function scroll_list:toggle()
|
||
|
if self.hidden then self:open()
|
||
|
else self:close() end
|
||
|
end
|
||
|
|
||
|
--clears the list in-place
|
||
|
function scroll_list:clear()
|
||
|
local i = 1
|
||
|
while self.list[i] do
|
||
|
self.list[i] = nil
|
||
|
i = i + 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--added alias for ipairs(list.list) for lua 5.1
|
||
|
function scroll_list:ipairs()
|
||
|
return ipairs(self.list)
|
||
|
end
|
||
|
|
||
|
--append item to the end of the list
|
||
|
function scroll_list:insert(item)
|
||
|
self.list[#self.list + 1] = item
|
||
|
end
|
||
|
|
||
|
local metatable = {
|
||
|
__index = function(t, key)
|
||
|
if scroll_list[key] ~= nil then return scroll_list[key]
|
||
|
elseif key == "__current" then return t.list[t.selected]
|
||
|
elseif type(key) == "number" then return t.list[key] end
|
||
|
end,
|
||
|
__newindex = function(t, key, value)
|
||
|
if type(key) == "number" then rawset(t.list, key, value)
|
||
|
else rawset(t, key, value) end
|
||
|
end,
|
||
|
__scroll_list = scroll_list,
|
||
|
__len = function(t) return #t.list end,
|
||
|
__ipairs = function(t) return ipairs(t.list) end
|
||
|
}
|
||
|
|
||
|
--creates a new list object
|
||
|
function scroll_list:new()
|
||
|
local vars
|
||
|
vars = {
|
||
|
ass = mp.create_osd_overlay('ass-events'),
|
||
|
hidden = true,
|
||
|
flag_update = true,
|
||
|
|
||
|
header = "header \\N ----------------------------------------------",
|
||
|
list = {},
|
||
|
selected = 1,
|
||
|
|
||
|
keybinds = {
|
||
|
{'DOWN', 'scroll_down', function() vars:scroll_down() end, {repeatable = true}},
|
||
|
{'UP', 'scroll_up', function() vars:scroll_up() end, {repeatable = true}},
|
||
|
{'PGDWN', 'move_pagedown', function() vars:move_pagedown() end, {}},
|
||
|
{'PGUP', 'move_pageup', function() vars:move_pageup() end, {}},
|
||
|
{'HOME', 'move_begin', function() vars:move_begin() end, {}},
|
||
|
{'END', 'move_end', function() vars:move_end() end, {}},
|
||
|
{'ESC', 'close_browser', function() vars:close() end, {}}
|
||
|
}
|
||
|
}
|
||
|
return setmetatable(vars, metatable)
|
||
|
end
|
||
|
|
||
|
return scroll_list:new()
|