Skip to content

Implement mouse support (point & click) #66

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
39848af
mouse-mode: added experimental mouse based selector function
CogentRedTester May 8, 2021
d87a235
mouse-mode: add support for custom scrolling (#22)
CogentRedTester Sep 16, 2022
fe3db26
mouse-mode: add default keybinds (#22)
CogentRedTester Sep 16, 2022
9c1dd6d
mouse-mode: don't try to calculate selection in empty lists
CogentRedTester Sep 16, 2022
f75573d
mouse-mode: double right click jumps to root
CogentRedTester Sep 16, 2022
b4de77d
mouse-mode: support different header font sizes
CogentRedTester Sep 18, 2022
668ff82
fix broken wrapping with new scroll logic
CogentRedTester Sep 18, 2022
47bcdd1
mouse-mode: experimental new alignment logic
CogentRedTester Sep 18, 2022
7b2229a
fix bugs when opening directories with new scrolling logic
CogentRedTester Sep 18, 2022
aee15b9
mouse-mode: osd-offset is properly scaled
CogentRedTester Sep 18, 2022
e08e878
mouse-mode: reset scroll-offset when opening directory
CogentRedTester Sep 18, 2022
084cb87
undo the reserved space for the top wrapper
CogentRedTester Sep 18, 2022
0505d8e
Merge branch 'master' into mouse-support
CogentRedTester Nov 16, 2024
21ff231
fix incorrect module name from merge
CogentRedTester Nov 16, 2024
f75eb0a
mouse_mode: update cursor position calculation for scaling_factor opts
CogentRedTester Nov 16, 2024
7cf372b
fix scroll offset calculation bugs
CogentRedTester Nov 16, 2024
57e9ec1
fix offset and keyind code bugs
CogentRedTester Dec 20, 2024
140a9c9
consider format strings when calculating header height
CogentRedTester Dec 20, 2024
7243eec
Merge branch 'master' into mouse-support
CogentRedTester Mar 21, 2025
06f4b93
mouse-support: update code to use type annotations
CogentRedTester Mar 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ keybinds.setup_keybinds()

-- property observers
mp.observe_property('path', 'string', observers.current_directory)
if o.alignment == 0 then
mp.observe_property("osd-align-x", "string", observers.osd_align_x)
mp.observe_property("osd-align-y", "string", observers.osd_align_y)
end
if o.map_dvd_device then mp.observe_property('dvd-device', 'string', observers.dvd_device) end
if o.map_bd_device then mp.observe_property('bluray-device', 'string', observers.bd_device) end
if o.map_cdda_device then mp.observe_property('cdda-device', 'string', observers.cd_device) end
Expand Down
22 changes: 8 additions & 14 deletions modules/ass.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,18 @@ local function update_ass()

---@type number
local start = 1
---@type number
local finish = start+o.num_entries-1

--handling cursor positioning
local mid = math.ceil(o.num_entries/2)+1
if state.selected+mid > finish then
---@type number
local offset = state.selected - finish + mid
local finish = start + math.min(o.num_entries, #state.list) - 1

--if we've overshot the end of the list then undo some of the offset
if finish + offset > #state.list then
offset = offset - ((finish+offset) - #state.list)
--handling the offset caused by scrolling
if state.scroll_offset > 0 then
if finish + state.scroll_offset > #state.list then
state.scroll_offset = #state.list - finish
end

start = start + offset
finish = finish + offset
end

start = start + state.scroll_offset
finish = finish + state.scroll_offset

--making sure that we don't overstep the boundaries
if start < 1 then start = 1 end
local overflow = finish < #state.list
Expand Down
3 changes: 3 additions & 0 deletions modules/controls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ function controls.open()
mp.add_forced_key_binding(v[1], 'dynamic/'..v[2], v[3], v[4])
end

if o.mouse_mode then mp.observe_property('mouse-pos', 'native', cursor.update_mouse_pos) end

if o.set_shared_script_properties then utils.shared_script_property_set('file_browser-open', 'yes') end ---@diagnostic disable-line deprecated
if o.set_user_data then mp.set_property_bool('user-data/file_browser/open', true) end

Expand All @@ -47,6 +49,7 @@ function controls.close()
if o.set_shared_script_properties then utils.shared_script_property_set("file_browser-open", "no") end ---@diagnostic disable-line deprecated
if o.set_user_data then mp.set_property_bool('user-data/file_browser/open', false) end

if o.mouse_mode then mp.unobserve_property(cursor.update_mouse_pos) end
if o.toggle_idlescreen then mp.commandv('script-message', 'osc-idlescreen', 'yes', 'no_osd') end
g.state.hidden = true
ass.remove()
Expand Down
8 changes: 8 additions & 0 deletions modules/defs/mp/defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ local mp = {}
---@field error_string ''|'killed'|'init'
---@field killed_by_us boolean

---@class MPVMousePos
---@field x number
---@field y number
---@field hover boolean

---@param key string
---@param name_or_fn string|function
---@param fn? async fun()
Expand Down Expand Up @@ -145,4 +150,7 @@ function mp.set_property_number(name, value) end
---@return string? err
function mp.set_property_native(name, value) end

---@param fn function
function mp.unobserve_property(fn) end

return mp
1 change: 1 addition & 0 deletions modules/defs/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
---@class (exact) State
---@field list List
---@field selected number
---@field scroll_offset number
---@field hidden boolean
---@field flag_update boolean
---@field keybinds KeybindTupleStrict[]?
Expand Down
19 changes: 15 additions & 4 deletions modules/globals.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ assert(mp.create_osd_overlay, "Script requires minimum mpv version 0.33")
globals.ass = mp.create_osd_overlay("ass-events")
globals.ass.res_y = 720 / o.scaling_factor_base

local BASE_FONT_SIZE = 25
globals.BASE_FONT_SIZE = 25

globals.style = {
global = o.alignment == 0 and "" or ([[{\an%d}]]):format(o.alignment),

-- full line styles
header = ([[{\r\q2\b%s\fs%d\fn%s\c&H%s&}]]):format((o.font_bold_header and "1" or "0"), o.scaling_factor_header*BASE_FONT_SIZE, o.font_name_header, o.font_colour_header),
body = ([[{\r\q2\fs%d\fn%s\c&H%s&}]]):format(BASE_FONT_SIZE, o.font_name_body, o.font_colour_body),
footer_header = ([[{\r\q2\fs%d\fn%s\c&H%s&}]]):format(o.scaling_factor_wrappers*BASE_FONT_SIZE, o.font_name_wrappers, o.font_colour_wrappers),
header = ([[{\r\q2\b%s\fs%d\fn%s\c&H%s&}]]):format((o.font_bold_header and "1" or "0"), o.scaling_factor_header*globals.BASE_FONT_SIZE, o.font_name_header, o.font_colour_header),
body = ([[{\r\q2\fs%d\fn%s\c&H%s&}]]):format(globals.BASE_FONT_SIZE, o.font_name_body, o.font_colour_body),
footer_header = ([[{\r\q2\fs%d\fn%s\c&H%s&}]]):format(o.scaling_factor_wrappers*globals.BASE_FONT_SIZE, o.font_name_wrappers, o.font_colour_wrappers),

--small section styles (for colours)
multiselect = ([[{\c&H%s&}]]):format(o.font_colour_multiselect),
Expand All @@ -62,6 +62,8 @@ globals.style = {
globals.state = {
list = {},
selected = 1,
scroll_offset = 0,
osd_alignment = "",
hidden = true,
flag_update = false,
keybinds = nil,
Expand All @@ -78,6 +80,15 @@ globals.state = {
selection = {}
}

---@type 'top'|'center'|'bottom'
globals.osd_alignment = "top"

--if the alignment isn't automated then we'll store a static value
--numbers defined here: https://aegi.vmoe.info/docs/3.0/ASS_Tags/#index23h3
if o.alignment >= 7 then globals.osd_alignment = "top"
elseif o.alignment >= 4 then globals.osd_alignment = "center"
elseif o.alignment >= 1 then globals.osd_alignment = "bottom" end

---@class ParserRef
---@field id string
---@field index number?
Expand Down
19 changes: 19 additions & 0 deletions modules/keybinds.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ g.state.keybinds = {
---@type KeybindList
local top_level_keys = {}

---@param key KeybindTupleStrict
local function add_key(key)
table.insert(g.state.keybinds, key)
end

--default keybinds for the experimental mouse-mode
if o.mouse_mode then
add_key{'WHEEL_DOWN', 'mouse/scroll_down', function() cursor.wheel(1) end}
add_key{'WHEEL_UP', 'mouse/scroll_up', function() cursor.wheel(-1) end}
add_key{'MBTN_LEFT', 'mouse/down_dir', movement.down_dir}
add_key{'MBTN_RIGHT', 'mouse/up_dir', movement.up_dir}
add_key{'MBTN_RIGHT_DBL', 'mouse/goto_root', movement.goto_root}
add_key{'Shift+MBTN_LEFT', 'mouse/play_left', function() playlist.add_files('replace', false) end}
add_key{'MBTN_MID', 'mouse/play_mid', function() playlist.add_files('replace', false) end}
add_key{'Shift+MBTN_MID', 'mouse/play_append', function() playlist.add_files('append-play', false) end}
add_key{'Alt+MBTN_MID', 'mouse/play_autoload', function() playlist.add_files('replace', true) end}
end

--format the item string for either single or multiple items
---Format the item string for either single or multiple items.
---@param base_code_fn Replacer
---@param items Item[]
Expand Down
80 changes: 80 additions & 0 deletions modules/navigation/cursor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
--------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------

local mp = require 'mp'
local msg = require 'mp.msg'
local utils = require 'mp.utils'

local o = require 'modules.options'
local g = require 'modules.globals'
local fb_utils = require 'modules.utils'
local ass = require 'modules.ass'
Expand Down Expand Up @@ -64,6 +67,12 @@ function cursor.scroll(n, wrap)
end

if g.state.multiselect_start then drag_select(original_pos, g.state.selected) end

--moves the scroll window down so that the selected item is in the middle of the screen
g.state.scroll_offset = g.state.selected - (math.ceil(o.num_entries/2)-1)
if g.state.scroll_offset < 0 then
g.state.scroll_offset = 0
end
ass.update_ass()
end

Expand Down Expand Up @@ -122,4 +131,75 @@ function cursor.toggle_select_mode()
end
end

---@param str string
---@param substring string
---@return number
local function count_substrings(str, substring)
local count = 0
for match in string.gmatch(str, substring) do
count = count + 1
end
return count
end

local font_size_body = g.BASE_FONT_SIZE
local font_size_header = g.BASE_FONT_SIZE * o.scaling_factor_header
local font_size_wrappers = g.BASE_FONT_SIZE * o.scaling_factor_wrappers

local num_header_lines = count_substrings(o.format_string_header, '\\N') + 1
local num_twrapper_lines = count_substrings(o.format_string_topwrapper, '\\N') + 1
local num_bwrapper_lines = count_substrings(o.format_string_bottomwrapper, '\\N') + 1

---update the selected item based on the mouse position
---@param _? string
---@param mouse_pos? MPVMousePos
function cursor.update_mouse_pos(_, mouse_pos)
if not o.mouse_mode or g.state.hidden or #g.state.list == 0 then return end

if not mouse_pos then mouse_pos = mp.get_property_native("mouse-pos", {}) end
if not mouse_pos.hover then return end
msg.trace('received mouse pos:', utils.to_string(mouse_pos))

local scale = mp.get_property_number("osd-height", 0) / g.ass.res_y
local osd_offset = scale * mp.get_property("osd-margin-y", 22)

msg.trace('calculating mouse pos for', g.osd_alignment, 'alignment')

--calculate position when browser is aligned to the top of the screen
if g.osd_alignment == "top" then
local header_offset = osd_offset + (num_header_lines * scale * font_size_header)
if g.state.scroll_offset > 0 then header_offset = header_offset + (num_twrapper_lines * scale * font_size_wrappers) end
msg.trace('calculated header offset', header_offset)

g.state.selected = math.ceil((mouse_pos.y-header_offset) / (scale * font_size_body)) + g.state.scroll_offset

--calculate position when browser is aligned to the bottom of the screen
--this calculation is slightly off when a bottom wrapper exists,
--I do not know what causes this.
elseif g.osd_alignment == "bottom" then
mouse_pos.y = (mp.get_property_number("osd-height", 0)) - mouse_pos.y

local bottom = math.min(#g.state.list, g.state.scroll_offset + o.num_entries)
local footer_offset = (2 * scale * font_size_wrappers) + osd_offset
msg.trace('calculated footer offset', footer_offset)

g.state.selected = bottom - math.floor((mouse_pos.y - footer_offset) / (scale * font_size_body))
end

ass.update_ass()
end

---scrolls the view window when using mouse mode
---@param direction number
function cursor.wheel(direction)
g.state.scroll_offset = g.state.scroll_offset + direction
if (g.state.scroll_offset + o.num_entries) > #g.state.list then
g.state.scroll_offset = #g.state.list - o.num_entries
end
if g.state.scroll_offset < 0 then
g.state.scroll_offset = 0
end
cursor.update_mouse_pos()
end

return cursor
1 change: 1 addition & 0 deletions modules/navigation/scanning.lua
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ local function update_list(moving_adjacent, parse_properties)
msg.verbose('opening directory: ' .. g.state.directory)

g.state.selected = 1
g.state.scroll_offset = 0
g.state.selection = {}

local directory = g.state.directory
Expand Down
16 changes: 15 additions & 1 deletion modules/observers.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@

local directory_movement = require 'modules.navigation.directory-movement'
local g = require 'modules.globals'
local fb = require 'modules.apis.fb'
local fb_utils = require 'modules.utils'
local ass = require 'modules.ass'
local directory_movement = require 'modules.navigation.directory-movement'
local cursor = require 'modules.navigation.cursor'

---@class observers
local observers ={}
Expand Down Expand Up @@ -34,4 +37,15 @@ function observers.cd_device(_, device)
fb.register_directory_mapping(fb_utils.absolute_path(device), '^cdda://.*', true)
end

function observers.osd_align_x()
ass.update_ass()
end

---@param _ string
---@param alignment string
function observers.osd_align_y(_, alignment)
g.osd_alignment = alignment
cursor.update_mouse_pos() -- calls ass.update_ass() internally
end

return observers
3 changes: 3 additions & 0 deletions modules/options.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ local o = {
custom_keybinds = true,
custom_keybinds_file = "~~/script-opts/file-browser-keybinds.json",

--enable experimental mouse mode
mouse_mode = false,

--blacklist compatible files, it's recommended to use this rather than to edit the
--compatible list directly. A comma separated list of extensions without spaces
extension_blacklist = "",
Expand Down