diff options
| author | David Vazgenovich Shakaryan <dvshakaryan@gmail.com> | 2026-01-21 12:52:15 -0800 |
|---|---|---|
| committer | David Vazgenovich Shakaryan <dvshakaryan@gmail.com> | 2026-01-21 12:52:15 -0800 |
| commit | 15198eb77727b4277d5b5d80fb3ae4b25b3c8ef8 (patch) | |
| tree | f16471159acb70601dc90d83147a1596c9b5a983 | |
| parent | 9b863cad505d01017ee980e16d072fb7cffcd029 (diff) | |
| download | mpv-iptv-menu-15198eb77727b4277d5b5d80fb3ae4b25b3c8ef8.tar.gz mpv-iptv-menu-15198eb77727b4277d5b5d80fb3ae4b25b3c8ef8.tar.xz | |
move mouse state to osd; add scrollbar hover
| -rw-r--r-- | config.lua | 9 | ||||
| -rw-r--r-- | main.lua | 86 | ||||
| -rw-r--r-- | osd.lua | 160 |
3 files changed, 156 insertions, 99 deletions
@@ -12,7 +12,8 @@ config.scroll_margin = 4 config.click_timeout = 0.5 config.click_dbl_time = 0.3 config.click_max_drift = 2 -config.menu_button_timeout = 1 + +config.btn_timeout = 1 config.bg_alpha = '44' config.colours = { @@ -34,10 +35,12 @@ config.colours = { icon_missing = 'ff0000', scrollbar_fg = '666666', scrollbar_bg = '333333', + scrollbar_fg_hover = '00cc00', + scrollbar_bg_hover = '224422', status_info = '55bbdd', status_error = 'ff3333', - menu_button_fg = '000000', - menu_button_bg = '00cc00', + menu_btn_fg = '000000', + menu_btn_bg = '00cc00', } local script_dir = mp.get_script_directory() @@ -16,7 +16,7 @@ local script_name = mp.get_script_name() local state = _state.new() local binding_state = {mappings = {}, active = {}} -local mouse_state = {click = {}} +local click_state = {} local osc_visibility local downloader = _downloader.new({limit = 5}) @@ -1000,16 +1000,14 @@ end -- second click event, we can implement double-click detection ourselves for -- more control and to bypass some mpv inconsistencies. local function handle_mouse_click(ev, id) - local ms = mouse_state - if ev.canceled then - ms.click = {} + click_state = {} return end local time = mp.get_time() - local clk = ms.click - local mpos = ms.pos + local clk = click_state + local mpos = osd.mpos if ev.event == 'down' then clk.ct, clk.ck = time, ev.key_name @@ -1024,14 +1022,14 @@ local function handle_mouse_click(ev, id) if time - clk.ct > config.click_timeout or clk.ck ~= ev.key_name or clk.ci ~= id or mouse_has_drifted(mpos.x, mpos.y, clk.cx, clk.cy) then - ms.click = {} + click_state = {} return end local dbl = clk.pt and clk.ct - clk.pt <= config.click_dbl_time and clk.pk == ev.key_name and clk.pi == id and not mouse_has_drifted(clk.cx, clk.cy, clk.px, clk.py) - ms.click = dbl and {} or + click_state = dbl and {} or { pt = clk.ct, pk = ev.key_name, px = clk.cx, py = clk.cy, pi = id, @@ -1040,20 +1038,19 @@ local function handle_mouse_click(ev, id) end local function process_mouse_click(ev) - if not mouse_state.pos then + local ms = osd.mstate + if not ms then return end local area - local val = osd:mouse_scrollbar_ratio(mouse_state.pos) - if val then + local val + if ms.target == 'scrollbar' then area = 'scrollbar' - else - local line = osd:mouse_menu_line(mouse_state.pos) - if line then - area = 'menu' - val = line - (osd.lines - osd:menu_lines(state)) - end + val = ms.ratio + elseif ms.target == 'menu' then + area = 'menu' + val = ms.option_line end local key, dbl = handle_mouse_click(ev, area == 'menu' and val or nil) @@ -1322,13 +1319,10 @@ local function unbind_click() mp.command_native({'disable-section', 'click-nodrag'}) end -local menu_button_timer -menu_button_timer = mp.add_periodic_timer(0.1, function() - if mouse_state.pos and - mp.get_time() - mouse_state.pos_time > - config.menu_button_timeout then - osd:show_menu_button(false) - menu_button_timer:kill() +local btn_timer; btn_timer = mp.add_periodic_timer(0.1, function() + if osd.mpos and mp.get_time() - osd.mpos_time > config.btn_timeout then + osd:show_menu_btn(false) + btn_timer:kill() end end, true) -- disabled mp.observe_property('mouse-pos', 'native', function(_, mpos) @@ -1336,49 +1330,40 @@ mp.observe_property('mouse-pos', 'native', function(_, mpos) return end - local ms = mouse_state - - -- mpv sends (0, 0) on startup if the mouse hasn't moved yet. don't - -- trigger display of the button until the mouse has actually moved. - if not ms.pos and mpos.x == 0 and mpos.y == 0 then + local ps = osd.mstate or {} + local ms = osd:set_mpos(mpos) + if not ms then return end - ms.pos = mpos - ms.pos_time = mp.get_time() - -- mpv normally sends a cancel event when the mouse drifts before a -- click is released, but only for a first click and not for the second -- click of what mpv internally considers a double-click. this -- replicates that drift detection behaviour, allowing for consistency -- on double-clicks. - local clk = ms.click + local clk = click_state if clk.ct and mouse_has_drifted(mpos.x, mpos.y, clk.cx, clk.cy) then - ms.click = {} + click_state = {} end - local over_button_area = osd:mouse_over_button_area(mpos) - if not over_button_area ~= not ms.over_button_area then - ms.over_button_area = over_button_area - osd:show_menu_button(over_button_area) - elseif over_button_area and osd.menu_button.hidden then - osd:show_menu_button(true) + if not ms.over_btn_area ~= not ps.over_btn_area then + osd:show_menu_btn(ms.over_btn_area) + elseif ms.over_btn_area and osd.menu_btn.hidden then + osd:show_menu_btn(true) end - local over_menu_button = osd:mouse_over_menu_button(mpos) - if not over_menu_button ~= not ms.over_menu_button then - ms.over_menu_button = over_menu_button - if over_menu_button then + if (ms.target == 'menu_btn') ~= (ps.target == 'menu_btn') then + if ms.target == 'menu_btn' then bind_click(toggle_menu) else unbind_click() end end - if over_button_area and not over_menu_button then - menu_button_timer:resume() + if ms.over_btn_area and ms.target ~= 'menu_btn' then + btn_timer:resume() else - menu_button_timer:kill() + btn_timer:kill() end end) @@ -1397,10 +1382,11 @@ mp.observe_property('osd-dimensions', 'native', function(_, val) -- and we do nothing, a click would trigger that element regardless of -- mouse position. since we cannot get the new position, we instead -- wipe mouse state on resize, clearing it until the next move. - osd:show_menu_button(false) - mouse_state = {click = {}} + osd:show_menu_btn(false) + osd:set_mpos(nil) + click_state = {} unbind_click() - menu_button_timer:kill() + btn_timer:kill() end) mp.register_event('start-file', function() @@ -43,7 +43,7 @@ function osd.new(init) local t = setmetatable({ fg = mp.create_osd_overlay('ass-events'), bg = mp.create_osd_overlay('ass-events'), - menu_button = mp.create_osd_overlay('ass-events'), + menu_btn = mp.create_osd_overlay('ass-events'), width = 0, height = 0, scale = 1, @@ -51,8 +51,8 @@ function osd.new(init) padding = math.floor((720 - (lines * config.font_size)) / 2), }, mt) t.bg.z = -1 - t.menu_button.z = 1 - t.menu_button.hidden = true + t.menu_btn.z = 1 + t.menu_btn.hidden = true for k, v in pairs(init or {}) do t[k] = v @@ -93,15 +93,15 @@ function mt:resize(w, h) } coords.x2 = coords.x1 + sz coords.y2 = coords.y1 + sz - self.menu_button_coords = coords - self.menu_button.data = + self.menu_btn_coords = coords + self.menu_btn.data = '{\\pos(' .. coords.x1 .. ',' .. coords.y1 .. ')}' .. - colours.menu_button_bg .. draw_rect(0, 0, sz, sz) .. '\n' .. + colours.menu_btn_bg .. draw_rect(0, 0, sz, sz) .. '\n' .. '{\\q2\\fs' .. sz .. '\\bord0\\an5\\pos(' .. coords.x1 + sz/2 .. ',' .. coords.y1 + sz/2 ..')}' .. - colours.menu_button_fg .. '≡' - self.menu_button:update() + colours.menu_btn_fg .. '≡' + self.menu_btn:update() end function mt:set_status(msg, level, no_dirty) @@ -414,12 +414,11 @@ function mt:set_img(path, menu_res) return true, true end -function mt:draw_scrollbar(state) +function mt:calc_scrollbar(state) local menu = state:menu() local opts = #menu.options local lines = self:menu_lines(state) if opts <= lines then - self.scrollbar_coords = nil return end @@ -428,20 +427,45 @@ function mt:draw_scrollbar(state) local w = self.padding - 4 local hh = math.max(config.font_size / 2, h * lines / opts) local pos = (h - hh) * (menu.view_top - 1) / (opts - lines) - self.scrollbar_coords = { + + return { x1 = 2, y1 = top, x2 = 2 + w, y2 = top + h, + w = w, + h = h, + pos = pos, + hh = hh, } - return ( - -- bg - colours.scrollbar_bg .. - '{\\pos(2,' .. top .. ')}' .. draw_rect(0, 0, w, h) .. '\n' .. - -- fg - colours.scrollbar_fg .. '{\\bord0}' .. - '{\\pos(2,' .. top + pos .. ')}' .. draw_rect(0, 0, w, hh)) +end + +function mt:render_bg() + self.bg.data = + '{\\pos(0,0)\\alpha&H' .. config.bg_alpha .. '&\\c&H&}' .. + draw_rect(0, 0, 7680, 720) + + local sb = self.out.scrollbar + if sb then + local hover = self.mstate and self.mstate.target == 'scrollbar' + local fg = hover and colours.scrollbar_fg_hover or + colours.scrollbar_fg + local bg = hover and colours.scrollbar_bg_hover or + colours.scrollbar_bg + + self.bg.data = self.bg.data .. '\n' .. + -- bg + bg .. + '{\\pos(2,' .. sb.y1 .. ')}' .. + draw_rect(0, 0, sb.w, sb.h) .. '\n' .. + -- fg + fg .. '{\\bord0}' .. + '{\\pos(2,' .. sb.y1 + sb.pos .. ')}' .. + draw_rect(0, 0, sb.w, sb.hh) + end + + self.bg:update() end -- this takes the data generated by redraw() and related functions, prepares it @@ -486,7 +510,7 @@ function mt:render() -- need it when update() is called to toggle visibility of the last -- rendered data. self.fg.compute_bounds = false - self.bg:update() + self:render_bg() if self.out.img_path then local upd, new = self:set_img(self.out.img_path, res) if upd then @@ -565,18 +589,9 @@ function mt:redraw(state) titles = out_titles, options = out_options, max = out_max, + scrollbar = self:calc_scrollbar(state), } - -- 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) - local sb = self:draw_scrollbar(state) - if sb then - self.bg.data = self.bg.data .. '\n' .. sb - end - if img and self.img_path_func then self.out.img_path = self.img_path_func(img, function(path) -- these are set by render(), which we always call @@ -587,6 +602,9 @@ function mt:redraw(state) end) end + -- menu changes could result in hover changes + self:update_mstate() + self:render() self.is_dirty = false end @@ -628,7 +646,7 @@ end function mt:mouse_scrollbar_ratio(mpos) local x = mpos.x / self.scale local y = mpos.y / self.scale - local coords = self.scrollbar_coords + local coords = self.out.scrollbar if not coords or x < coords.x1 or x > coords.x2 or y < coords.y1 or y > coords.y2 then @@ -639,39 +657,89 @@ function mt:mouse_scrollbar_ratio(mpos) return math.max(0, math.min(1, ratio)) end -function mt:mouse_over_button_area(mpos) - if not mpos.hover then - return false - end - +function mt:mouse_over_btn_area(mpos) local y = mpos.y / self.scale return y < self.padding + (2 * config.font_size) end -function mt:mouse_over_menu_button(mpos) - if not mpos.hover then - return false - end - +function mt:mouse_over_menu_btn(mpos) local x = mpos.x / self.scale local y = mpos.y / self.scale - local coords = self.menu_button_coords + local coords = self.menu_btn_coords return coords and x > coords.x1 and x < coords.x2 and y > coords.y1 and y < coords.y2 end -function mt:show_menu_button(bool) - if self.menu_button.hidden ~= bool then +function mt:update_mstate() + local t = {} + local val + + if not self.mpos or not self.mpos.hover then + goto update + end + + val = self:mouse_over_menu_btn(self.mpos) + if val then + t.target = 'menu_btn' + goto common + end + + val = self:mouse_scrollbar_ratio(self.mpos) + if val then + t.target = 'scrollbar' + t.ratio = val + goto common + end + + val = self:mouse_menu_line(self.mpos) + if val then + t.target = 'menu' + t.line = val + t.option_line = val - #self.out.titles - 1 + goto common + end + + ::common:: + + t.over_btn_area = self:mouse_over_btn_area(self.mpos) + + ::update:: + + local ms = self.mstate or {} + self.mstate = t + + if (t.target == 'scrollbar') ~= (ms.target == 'scrollbar') then + self:render_bg() + end + + return t +end + +function mt:set_mpos(mpos) + -- mpv sends (0, 0) on startup if the mouse hasn't moved yet. don't + -- trigger display of the button until the mouse has actually moved. + if not self.mpos and mpos and mpos.x == 0 and mpos.y == 0 then + return + end + + self.mpos = mpos + self.mpos_time = mp.get_time() + + return self:update_mstate() +end + +function mt:show_menu_btn(bool) + if not self.menu_btn.hidden ~= not bool then return end - self.menu_button.hidden = not bool - self.menu_button:update() + self.menu_btn.hidden = not bool + self.menu_btn:update() end function mt:measure_width(str) - local e = self.menu_button + local e = self.menu_btn local data = e.data local hidden = e.hidden e.hidden = true |
