From b3665cc09ea0363beafc05a867c057e5c122985a Mon Sep 17 00:00:00 2001 From: David Vazgenovich Shakaryan Date: Sun, 18 Jan 2026 00:00:24 -0800 Subject: 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. --- osd.lua | 147 +++++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 108 insertions(+), 39 deletions(-) (limited to 'osd.lua') 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 -- cgit v1.2.3-70-g09d2