summaryrefslogtreecommitdiff
path: root/osd.lua
diff options
context:
space:
mode:
authorDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2026-01-18 00:00:24 -0800
committerDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2026-01-18 00:00:24 -0800
commitb3665cc09ea0363beafc05a867c057e5c122985a (patch)
tree162299bf2515205c83202083016ec2337271dd26 /osd.lua
parentb3ec50d92451b99dd08b6030b08854c8cc77524b (diff)
downloadmpv-iptv-menu-b3665cc09ea0363beafc05a867c057e5c122985a.tar.gz
mpv-iptv-menu-b3665cc09ea0363beafc05a867c057e5c122985a.tar.xz
support timed status/error messages
To support this, render() has been pulled out of redraw(), allowing updating the actual OSD without having to pass in the 'state' object, avoiding complexity and unnecessary computation.
Diffstat (limited to 'osd.lua')
-rw-r--r--osd.lua147
1 files changed, 108 insertions, 39 deletions
diff --git a/osd.lua b/osd.lua
index e638ee8..bf5dfec 100644
--- a/osd.lua
+++ b/osd.lua
@@ -70,10 +70,36 @@ function mt:resize(w, h)
self.scale = h / 720
end
-function mt:set_status(msg, level)
+function mt:set_status(msg, level, no_dirty)
+ if self.status_timer then
+ self.status_timer:kill()
+ self.status_timer = nil
+ end
+
self.status_msg = msg
self.status_level = level
- self:dirty()
+
+ if not no_dirty then
+ self:dirty()
+ end
+end
+
+function mt:flash_status(msg, level, secs)
+ self:set_status(msg, level, true)
+ self:render()
+
+ self.status_timer = mp.add_timeout(secs or 3, function()
+ self.status_timer = nil
+
+ if self.status_msg == msg then
+ self:set_status(nil, nil, true)
+ self:render()
+ end
+ end)
+end
+
+function mt:flash_error(msg, secs)
+ self:flash_status(msg, 'error', secs)
end
function mt:status_line()
@@ -228,7 +254,14 @@ function mt:load_img()
self.magick_cmd = nil
self.magick_cmd_id = nil
- if not self.img or not res or res.status ~= 0 then
+ if not self.img then
+ return
+ end
+
+ if not res or res.status ~= 0 then
+ if self.img_fail_cb then
+ self.img_fail_cb()
+ end
return
end
@@ -370,9 +403,66 @@ function mt:draw_scrollbar(state)
'{\\pos(2,' .. top + pos .. ')}' .. draw_rect(0, 0, w, hh))
end
-function mt:redraw(state)
+-- this takes the data generated by redraw() and related functions, prepares it
+-- for rendering and pushes the result to the actual OSD. since it has no
+-- dependence on `state', it's simpler and more efficient to use this directly
+-- for things like displaying status messages.
+function mt:render()
local out = {}
+ for _, v in ipairs(self.out.titles) do
+ out[#out+1] = v
+ end
+
+ -- use spacer line between titles and options for status messages
+ out[#out+1] = self:status_line()
+
+ for _, v in ipairs(self.out.options) do
+ out[#out+1] = v
+ end
+
+ local buf = {}
+ -- each line uses a new ASS event with explicit positioning. otherwise,
+ -- lines with nonstandard height break things.
+ -- \q2 disables line wrapping
+ local pre = '{\\q2\\fs' .. config.font_size ..
+ '\\pos(' .. self.padding .. ',%s)}'
+ for i, v in ipairs(out) do
+ local offset = self.padding + ((i - 1) * config.font_size)
+ buf[#buf+1] = pre:format(offset) .. v
+ end
+ -- rendering `max' is needed to compute bounds only when there is an
+ -- image to display.
+ if self.out.img then
+ buf[#buf+1] = '{\\alpha&HFF&}' .. pre:format(0) ..
+ table.concat(self.out.max, '\\N')
+ end
+ self.fg.data = table.concat(buf, '\n')
+
+ self.fg.compute_bounds = not not self.out.img_path
+ local res = self.fg:update()
+ -- unset compute_bounds because it is relatively expensive and we don't
+ -- need it when update() is called to toggle visibility of the last
+ -- rendered data.
+ self.fg.compute_bounds = false
+ self.bg:update()
+ if self.out.img_path then
+ local upd, new = self:set_img(self.out.img_path, res)
+ if upd then
+ if new then
+ self:clear_img()
+ end
+ self:draw_img()
+ end
+ else
+ self:clear_img(true)
+ end
+end
+
+function mt:redraw(state)
+ local out_titles = {}
+ local out_options = {}
+
-- to determine the area left for images, we use the computed bounds of
-- the rendered menu, which shifts when the cursor is moved to/from the
-- longest line. to prevent this, this table stores the menu options
@@ -387,12 +477,9 @@ function mt:redraw(state)
local out_max = {}
for i = 1, state.depth do
- out[#out+1] = self:menu_title(state.menus[i])
+ out_titles[#out_titles+1] = self:menu_title(state.menus[i])
end
- -- use spacer line between titles and options for status messages
- out[#out+1] = self:status_line()
-
local menu = state:menu()
local img
@@ -422,7 +509,8 @@ function mt:redraw(state)
}
local str = self:option_text(opt, info) ..
self:option_path(opt, info)
- out[#out+1] = self:option_icons(opt, info) .. str
+ out_options[#out_options+1] = self:option_icons(opt, info) ..
+ str
if selected and opt.img_url then
img = opt.img_url
@@ -432,22 +520,14 @@ function mt:redraw(state)
out_max[#out_max+1] = self:option_icons(opt, info) .. str
end
- local buf = {}
- -- each line uses a new ASS event with explicit positioning. otherwise,
- -- lines with nonstandard height break things.
- -- \q2 disables line wrapping
- local pre = '{\\q2\\fs' .. config.font_size ..
- '\\pos(' .. self.padding .. ',%s)}'
- for i, v in ipairs(out) do
- local offset = self.padding + ((i - 1) * config.font_size)
- buf[#buf+1] = pre:format(offset) .. v
- end
- if img then
- buf[#buf+1] = '{\\alpha&HFF&}' .. pre:format(0) ..
- table.concat(out_max, '\\N')
- end
- self.fg.data = table.concat(buf, '\n')
+ self.out = {
+ titles = out_titles,
+ options = out_options,
+ max = out_max,
+ }
+ -- we can draw this directly from here since any changes handled by
+ -- render() will not modify this.
self.bg.data =
'{\\pos(0,0)\\alpha&H' .. config.bg_alpha .. '&\\c&H&}' ..
draw_rect(0, 0, 7680, 720)
@@ -456,28 +536,17 @@ function mt:redraw(state)
self.bg.data = self.bg.data .. '\n' .. sb
end
- self.fg.compute_bounds = not not img
- local res = self.fg:update()
- self.fg.compute_bounds = false
- self.bg:update()
if img and self.img_path_func then
- local path = self.img_path_func(img, function(path)
+ self.out.img_path = self.img_path_func(img, function(path)
+ -- these are set by render(), which we always call
+ -- before returning.
if self.img and path == self.img.path then
self:draw_img()
end
end)
-
- local upd, new = self:set_img(path, res)
- if upd then
- if new then
- self:clear_img()
- end
- self:draw_img()
- end
- else
- self:clear_img(true)
end
+ self:render()
self.is_dirty = false
end