12 Commits

Author SHA1 Message Date
80b767e966 more debug logging 2023-08-30 05:51:52 +00:00
8998e63123 fix: touchscreen interactions don't require extra presses
previously activating a control meant tapping it once to enable
proximity, then tapping it again to do the actual interaction.
now a single tap will do that.

previously the following glitch was present:
- interact with a slider
- release touch on that slider
- touch a different control
->input would be received by the old control

now the input is received by the correct control
2023-08-30 05:51:51 +00:00
Christoph Heinrich
d25f3e2e99 refactor: use new velocity measurement for menu flings 2023-08-25 09:13:45 +02:00
Christoph Heinrich
3f21df1a9d refactor: use cicular buffer for cursor.history
Circular buffers are more efficient then moving the entries around.

Cursor velocity is now calculated on the fly, with from the current
cursor position and time as well a cursor position that is either
the youngest sample >100ms old, or the oldest sample in the buffer,
which is 10 elements in size. Restricting the sample selection based on
time prevents the filter window from becomming too long in case of
setups with low cursor update frequency, while still bring more
responsive velocity measurements to systems with higher cursor update
frequencies.

Removed the timer for exact seeking after fast seeks, as fast/exact
seeks can now be controlled with cursor speed and thus there is no need
for this anymore.
2023-08-25 09:13:45 +02:00
tomasklaen
776ca17f66 feat: fast seek in timeline based on cursor velocity 2023-08-25 09:13:45 +02:00
Tomas Klaen
d1e9f9c4eb fix: stale (closed) menus triggering input events (#612) 2023-08-23 18:13:43 +02:00
christoph-heinrich
6606f3e11f fix: remove redundant symbol order from sorting algorithm (#608)
We were already using the same sorting algorithm as autoload.lua,
but with the addition of a custom symbol order. However that symbol
order was only applied to the first character, so when sorting absolute
paths it doesn't actually make a difference.

Using the exact same algorithm for autoload.lua and uosc also makes it
easier to apply any future improvements from one to the other.
2023-08-21 18:05:01 +02:00
tomasklaen
68b5b2aaed refactor: improved protocol matching patterns
ref #605
2023-08-16 12:52:07 +02:00
tomasklaen
f857d468e1 fix: error in matching pattern caused some files to be recognized as magnet links
closes #605
2023-08-16 10:07:36 +02:00
Andrei Shevchuk
bc7b1a12bc feat: add russian translation (#597) 2023-08-02 09:13:07 +02:00
/ˈɛvən/
cad0d174d7 fix: make the install procedure on linux use XDG_CONFIG_HOME (#595) 2023-08-01 09:51:40 +02:00
Felix Yan
71cdeda7b3 chore: correct typos in uosc.conf (#594) 2023-08-01 09:47:27 +02:00
9 changed files with 265 additions and 155 deletions

View File

@@ -43,10 +43,11 @@ Most notable features:
On Linux and macOS these terminal commands can be used to install or update uosc (if wget and unzip are installed):
```sh
mkdir -pv ~/.config/mpv/script-opts/
rm -rf ~/.config/mpv/scripts/uosc_shared
config_dir="${XDG_CONFIG_HOME:-~/.config}"
mkdir -pv "$config_dir"/mpv/script-opts/
rm -rf "$config_dir"/mpv/scripts/uosc_shared
wget -P /tmp/ https://github.com/tomasklaen/uosc/releases/latest/download/uosc.zip
unzip -od ~/.config/mpv/ /tmp/uosc.zip
unzip -od "$config_dir"/mpv/ /tmp/uosc.zip
rm -fv /tmp/uosc.zip
```

View File

@@ -47,7 +47,7 @@ timeline_cache=yes
# `cycle:{default_icon}:{prop}[@{owner}]:{value1}[={icon1}][!]/{valueN}[={iconN}][!]`
# - button that cycles mpv property between values, each optionally having different icon and active flag
# - presence of `!` at the end will style the button as active
# - `{owner}` is the name of a scrip that manages this property if any
# - `{owner}` is the name of a script that manages this property if any
# `gap[:{scale}]` - display an empty gap, {scale} - factor of controls_size, default: 0.3
# `space` - fills all available space between previous and next item, useful to align items to the right
# - multiple spaces divide the available space among themselves, which can be used for centering
@@ -157,7 +157,7 @@ font_scale=1
# Border of text and icons when drawn directly on top of video
text_border=1.2
# Use a faster estimation method instead of accurate measurement
# setting this to `no` might have a noticable impact on performance, especially in large menus.
# setting this to `no` might have a noticeable impact on performance, especially in large menus.
text_width_estimation=yes
# Execute command for background clicks shorter than this number of milliseconds, 0 to disable
# Execution always waits for `input-doubleclick-time` to filter out double-clicks

View File

@@ -106,8 +106,7 @@ function Menu:init(data, callback, opts)
self.key_bindings = {}
self.is_being_replaced = false
self.is_closing, self.is_closed = false, false
---@type {y: integer, time: number}[]
self.drag_data = nil
self.drag_last_y = nil
self.is_dragging = false
self:update(data)
@@ -545,47 +544,37 @@ function Menu:on_prop_fullormaxed() self:update_content_dimensions() end
function Menu:handle_cursor_down()
if self.proximity_raw == 0 then
self.drag_data = {{y = cursor.y, time = mp.get_time()}}
self.drag_last_y = cursor.y
self.current.fling = nil
else
self:close()
end
end
function Menu:fling_distance()
local first, last = self.drag_data[1], self.drag_data[#self.drag_data]
if mp.get_time() - last.time > 0.05 then return 0 end
for i = #self.drag_data - 1, 1, -1 do
local drag = self.drag_data[i]
if last.time - drag.time > 0.03 then return ((drag.y - last.y) / ((last.time - drag.time) / 0.03)) * 10 end
end
return #self.drag_data < 2 and 0 or ((first.y - last.y) / ((first.time - last.time) / 0.03)) * 10
end
function Menu:handle_cursor_up()
if self.proximity_raw == 0 and self.drag_data and not self.is_dragging then
if self.proximity_raw == 0 and self.drag_last_y and not self.is_dragging then
self:open_selected_item({preselect_first_item = false, keep_open = self.modifiers and self.modifiers.shift})
end
if self.is_dragging then
local distance = self:fling_distance()
local distance = cursor.get_velocity().y / -3
if math.abs(distance) > 50 then
self.current.fling = {
y = self.current.scroll_y, distance = distance, time = self.drag_data[#self.drag_data].time,
y = self.current.scroll_y, distance = distance, time = cursor.history:head().time,
easing = ease_out_quart, duration = 0.5, update_cursor = true,
}
end
end
self.is_dragging = false
self.drag_data = nil
self.drag_last_y = nil
end
function Menu:on_global_mouse_move()
self.mouse_nav = true
if self.drag_data then
self.is_dragging = self.is_dragging or math.abs(cursor.y - self.drag_data[1].y) >= 10
local distance = self.drag_data[#self.drag_data].y - cursor.y
if self.drag_last_y then
self.is_dragging = self.is_dragging or math.abs(cursor.y - self.drag_last_y) >= 10
local distance = self.drag_last_y - cursor.y
if distance ~= 0 then self:set_scroll_by(distance) end
self.drag_data[#self.drag_data + 1] = {y = cursor.y, time = mp.get_time()}
if self.is_dragging then self.drag_last_y = cursor.y end
end
request_render()
end
@@ -662,26 +651,34 @@ function Menu:disable_key_bindings()
self.key_bindings = {}
end
-- Wraps a function so that it won't run if menu is closing or closed.
---@param fn function()
function Menu:create_action(fn)
return function()
if not self.is_closing and not self.is_closed then fn() end
end
end
---@param modifiers Modifiers
function Menu:create_modified_mbtn_left_handler(modifiers)
return function()
return self:create_action(function()
self.mouse_nav = true
self.modifiers = modifiers
self:handle_cursor_down()
self:handle_cursor_up()
self.modifiers = nil
end
end)
end
---@param name string
---@param modifiers? Modifiers
function Menu:create_key_action(name, modifiers)
return function()
return self:create_action(function()
self.mouse_nav = false
self.modifiers = modifiers
self:maybe(name)
self.modifiers = nil
end
end)
end
function Menu:render()
@@ -694,11 +691,11 @@ function Menu:render()
end
end
cursor.on_primary_down = function() self:handle_cursor_down() end
cursor.on_primary_up = function() self:handle_cursor_up() end
cursor.on_primary_down = self:create_action(function() self:handle_cursor_down() end)
cursor.on_primary_up = self:create_action(function() self:handle_cursor_up() end)
if self.proximity_raw == 0 then
cursor.on_wheel_down = function() self:handle_wheel_down() end
cursor.on_wheel_up = function() self:handle_wheel_up() end
cursor.on_wheel_down = self:create_action(function() self:handle_wheel_down() end)
cursor.on_wheel_up = self:create_action(function() self:handle_wheel_up() end)
end
local ass = assdraw.ass_new()
@@ -727,7 +724,7 @@ function Menu:render()
ass:rect(menu_rect.ax, menu_rect.ay, menu_rect.bx, menu_rect.by, {color = bg, opacity = menu_opacity, radius = 4})
if is_parent and get_point_to_rectangle_proximity(cursor, menu_rect) == 0 then
cursor.on_primary_down = function() self:slide_in_menu(menu, x) end
cursor.on_primary_down = self:create_action(function() self:slide_in_menu(menu, x) end)
end
-- Draw submenu if selected
@@ -737,7 +734,9 @@ function Menu:render()
submenu_rect = draw_menu(current_item, menu_rect.bx + menu_gap, 1)
submenu_is_hovered = get_point_to_rectangle_proximity(cursor, submenu_rect) == 0
if submenu_is_hovered then
cursor.on_primary_down = function() self:open_selected_item({preselect_first_item = false}) end
cursor.on_primary_down = self:create_action(function()
self:open_selected_item({preselect_first_item = false})
end)
end
end

View File

@@ -17,10 +17,6 @@ function Timeline:init()
self.is_hovered = false
self.has_thumbnail = false
-- Delayed seeking timer
self.seek_timer = mp.add_timeout(0.05, function() self:set_from_cursor() end)
self.seek_timer:kill()
-- Release any dragging when file gets unloaded
mp.register_event('end-file', function() self.pressed = false end)
end
@@ -113,7 +109,6 @@ function Timeline:on_prop_border() self:update_dimensions() end
function Timeline:on_prop_fullormaxed() self:update_dimensions() end
function Timeline:on_display() self:update_dimensions() end
function Timeline:handle_cursor_up()
self.seek_timer:kill()
if self.pressed then
mp.set_property_native('pause', self.pressed.pause)
self.pressed = false
@@ -127,10 +122,8 @@ function Timeline:on_global_mouse_move()
if self.pressed then
self.pressed.distance = self.pressed.distance + get_point_to_point_proximity(self.pressed.last, cursor)
self.pressed.last.x, self.pressed.last.y = cursor.x, cursor.y
if self.width / state.duration < 10 then
if state.is_video and math.abs(cursor.get_velocity().x) / self.width * state.duration > 30 then
self:set_from_cursor(true)
self.seek_timer:kill()
self.seek_timer:resume()
else self:set_from_cursor() end
end
end

View File

@@ -1,3 +1,4 @@
msg = require('mp.msg')
local Element = require('elements/Element')
--[[ MuteButton ]]
@@ -73,7 +74,9 @@ function VolumeSlider:render()
if width <= 0 or height <= 0 or visibility <= 0 then return end
msg.trace("VolumeSlider: proximity_raw:", self.proximity_raw)
if self.proximity_raw == 0 then
msg.trace("VolumeSlider: setting on_primary_down")
cursor.on_primary_down = function()
self.pressed = true
self:set_from_cursor()

59
scripts/uosc/intl/ru.json Normal file
View File

@@ -0,0 +1,59 @@
{
"Aspect ratio": "Соотношение сторон",
"Audio": "Аудио",
"Audio device": "Аудиоустройство",
"Audio devices": "Аудиоустройства",
"Audio tracks": "Аудиодорожки",
"Autoselect device": "Автовыбор устройства",
"Chapter %s": "Глава %s",
"Chapters": "Главы",
"Default": "По умолчанию",
"Default %s": "По умолчанию %s",
"Delete file & Next": "Удалить файл и след.",
"Delete file & Prev": "Удалить файл и пред.",
"Delete file & Quit": "Удалить файл и выйти",
"Disabled": "Отключено",
"Drives": "Диски",
"Edition": "Редакция",
"Edition %s": "Редакция %s",
"Editions": "Редакции",
"Empty": "Пусто",
"First": "Первый",
"Fullscreen": "Полный экран",
"Last": "Последний",
"Load": "Загрузить",
"Load audio": "Загрузить аудио",
"Load subtitles": "Загрузить субтитры",
"Load video": "Загрузить видео",
"Loop file": "Повторять файл",
"Loop playlist": "Повторять плейлист",
"Menu": "Меню",
"Navigation": "Навигация",
"Next": "Следующий",
"No file": "Нет файла",
"Open config folder": "Открыть папку конфигурации",
"Open file": "Открыть файл",
"Playlist": "Плейлист",
"Playlist/Files": "Плейлист / файлы",
"Prev": "Предыдущий",
"Previous": "Предыдущий",
"Quit": "Выйти",
"Screenshot": "Скриншот",
"Show in directory": "Показать в папке",
"Shuffle": "Перемешать",
"Stream quality": "Качество потока",
"Subtitles": "Субтитры",
"Track": "Дорожка",
"Track %s": "Дорожка %s",
"Utils": "Инструменты",
"Video": "Видео",
"%s channels": "%s канала/-ов",
"%s channel": "%s канал",
"default": "по умолчанию",
"drive": "диск",
"external": "внешняя",
"forced": "форсированная",
"open file": "открыть файл",
"parent dir": "родительская папка",
"playlist or file": "плейлист или файл"
}

View File

@@ -202,3 +202,59 @@ function Class:init() end
function Class:destroy() end
function class(parent) return setmetatable({}, {__index = parent or Class}) end
---@class CircularBuffer<T> : Class
CircularBuffer = class()
function CircularBuffer:new(max_size) return Class.new(self, max_size) --[[@as CircularBuffer]] end
function CircularBuffer:init(max_size)
self.max_size = max_size
self.size = 0
self.pos = 0
self.data = {}
end
function CircularBuffer:insert(item)
self.pos = self.pos % self.max_size + 1
self.data[self.pos] = item
if self.size < self.max_size then self.size = self.size + 1 end
end
function CircularBuffer:get(i)
return i <= self.size and self.data[(self.pos + i - 1) % self.size + 1] or nil
end
local function iter(self, i)
if i == self.size then return nil end
i = i + 1
return i, self:get(i)
end
function CircularBuffer:iter()
return iter, self, 0
end
local function iter_rev(self, i)
if i == 1 then return nil end
i = i - 1
return i, self:get(i)
end
function CircularBuffer:iter_rev()
return iter_rev, self, self.size + 1
end
function CircularBuffer:head()
return self.data[self.pos]
end
function CircularBuffer:tail()
if self.size < 1 then return nil end
return self.data[self.pos % self.size + 1]
end
function CircularBuffer:clear()
for i = self.size, 1, -1 do self.data[i] = nil end
self.size = 0
self.pos = 0
end

View File

@@ -1,50 +1,26 @@
--[[ UI specific utilities that might or might not depend on its state or options ]]
msg = require('mp.msg')
-- Sorting comparator close to (but not exactly) how file explorers sort files.
sort_filenames = (function()
local symbol_order
local default_order
if state.platform == 'windows' then
symbol_order = {
['!'] = 1, ['#'] = 2, ['$'] = 3, ['%'] = 4, ['&'] = 5, ['('] = 6, [')'] = 6, [','] = 7,
['.'] = 8, ["'"] = 9, ['-'] = 10, [';'] = 11, ['@'] = 12, ['['] = 13, [']'] = 13, ['^'] = 14,
['_'] = 15, ['`'] = 16, ['{'] = 17, ['}'] = 17, ['~'] = 18, ['+'] = 19, ['='] = 20,
}
default_order = 21
else
symbol_order = {
['`'] = 1, ['^'] = 2, ['~'] = 3, ['='] = 4, ['_'] = 5, ['-'] = 6, [','] = 7, [';'] = 8,
['!'] = 9, ["'"] = 10, ['('] = 11, [')'] = 11, ['['] = 12, [']'] = 12, ['{'] = 13, ['}'] = 14,
['@'] = 15, ['$'] = 16, ['*'] = 17, ['&'] = 18, ['%'] = 19, ['+'] = 20, ['.'] = 22, ['#'] = 23,
}
default_order = 21
end
-- Alphanumeric sorting for humans in Lua
--- In place sorting of filenames
---@param filenames string[]
function sort_filenames(filenames)
-- alphanum sorting for humans in Lua
-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua
local function pad_number(n, d)
return #d > 0 and ("%03d%s%.12f"):format(#n, n, tonumber(d) / (10 ^ #d))
or ("%03d%s"):format(#n, n)
local function padnum(n, d)
return #d > 0 and ('%03d%s%.12f'):format(#n, n, tonumber(d) / (10 ^ #d))
or ('%03d%s'):format(#n, n)
end
--- In place sorting of filenames
---@param filenames string[]
return function(filenames)
local tuples = {}
for i, filename in ipairs(filenames) do
local first_char = filename:sub(1, 1)
local order = symbol_order[first_char] or default_order
local formatted = filename:lower():gsub('0*(%d+)%.?(%d*)', pad_number)
tuples[i] = {order, formatted, filename}
end
table.sort(tuples, function(a, b)
if a[1] ~= b[1] then return a[1] < b[1] end
return a[2] == b[2] and #b[3] < #a[3] or a[2] < b[2]
end)
for i, tuple in ipairs(tuples) do filenames[i] = tuple[3] end
local tuples = {}
for i, f in ipairs(filenames) do
tuples[i] = { f:lower():gsub('0*(%d+)%.?(%d*)', padnum), f }
end
end)()
table.sort(tuples, function(a, b)
return a[1] == b[1] and #b[2] < #a[2] or a[1] < b[1]
end)
for i, tuple in ipairs(tuples) do filenames[i] = tuple[2] end
return filenames
end
-- Creates in-between frames to animate value from `from` to `to` numbers.
---@param from number
@@ -319,7 +295,7 @@ end
-- Check if path is a protocol, such as `http://...`.
---@param path string
function is_protocol(path)
return type(path) == 'string' and (path:find('^%a[%a%d-_]+://') ~= nil or path:find('^%a[%a%d-_]+:\\?') ~= nil)
return type(path) == 'string' and (path:find('^%a[%w.+-]-://') ~= nil or path:find('^%a[%w.+-]-:%?') ~= nil)
end
---@param path string
@@ -646,6 +622,7 @@ end
function render()
if not display.initialized then return end
msg.trace("render")
state.render_last_time = mp.get_time()
cursor.reset_handlers()

View File

@@ -316,8 +316,7 @@ cursor = {
on_wheel_down = nil,
on_wheel_up = nil,
allow_dragging = false,
history = {}, -- {x, y}[] history
history_size = 10,
history = CircularBuffer:new(10),
-- Called at the beginning of each render
reset_handlers = function()
cursor.on_primary_down, cursor.on_primary_up = nil, nil
@@ -328,7 +327,7 @@ cursor = {
mbtn_left_enabled = nil,
wheel_enabled = nil,
decide_keybinds = function()
local enable_mbtn_left = (cursor.on_primary_down or cursor.on_primary_up) ~= nil
local enable_mbtn_left = true -- (cursor.on_primary_down or cursor.on_primary_up) ~= nil
local enable_wheel = (cursor.on_wheel_down or cursor.on_wheel_up) ~= nil
if enable_mbtn_left ~= cursor.mbtn_left_enabled then
local flags = cursor.allow_dragging and 'allow-vo-dragging' or nil
@@ -340,9 +339,80 @@ cursor = {
cursor.wheel_enabled = enable_wheel
end
end,
find_history_sample = function()
local time = mp.get_time()
for _, e in cursor.history:iter_rev() do
if time - e.time > 0.1 then
return e
end
end
return cursor.history:tail()
end,
get_velocity = function()
local snap = cursor.find_history_sample()
if snap then
local x, y, time = cursor.x - snap.x, cursor.y - snap.y, mp.get_time()
local time_diff = time - snap.time
if time_diff > 0.001 then
return { x = x / time_diff, y = y / time_diff }
end
end
return { x = 0, y = 0 }
end,
move = function(x, y)
local old_x, old_y = cursor.x, cursor.y
-- mpv reports initial mouse position on linux as (0, 0), which always
-- displays the top bar, so we hardcode cursor position as infinity until
-- we receive a first real mouse move event with coordinates other than 0,0.
if not state.first_real_mouse_move_received then
if x > 0 and y > 0 and x < INFINITY and y < INFINITY then state.first_real_mouse_move_received = true
else x, y = INFINITY, INFINITY end
end
-- Add 0.5 to be in the middle of the pixel
cursor.x, cursor.y = (x + 0.5) / display.scale_x, (y + 0.5) / display.scale_y
if old_x ~= cursor.x or old_y ~= cursor.y then
is_leave = cursor.x == INFINITY or cursor.y == INFINITY
is_enter = cursor.hidden and not is_leave;
if is_enter then
cursor.hidden = false
cursor.history:clear()
Elements:trigger('global_mouse_enter')
end
Elements:update_proximities()
if is_leave then
cursor.hidden = true
cursor.history:clear()
-- Slowly fadeout elements that are currently visible
for _, element_name in ipairs({'timeline', 'volume', 'top_bar'}) do
local element = Elements[element_name]
if element and element.proximity > 0 then
element:tween_property('forced_visibility', element:get_visibility(), 0, function()
element.forced_visibility = nil
end)
end
end
Elements:trigger('global_mouse_leave')
elseif not is_enter then
-- Update history
cursor.history:insert({x = cursor.x, y = cursor.y, time = mp.get_time()})
end
Elements:proximity_trigger('mouse_move')
cursor.queue_autohide()
end
render()
end,
leave = function () cursor.move(INFINITY, INFINITY) end,
-- Cursor auto-hiding after period of inactivity
autohide = function()
if not cursor.on_primary_up and not Menu:is_open() then handle_mouse_leave() end
if not cursor.on_primary_up and not Menu:is_open() then cursor.leave() end
end,
autohide_timer = (function()
local timer = mp.add_timeout(mp.get_property_native('cursor-autohide') / 1000, function() cursor.autohide() end)
@@ -355,15 +425,12 @@ cursor = {
cursor.autohide_timer:resume()
end
end,
-- Calculates distance in which cursor reaches rectangle if it continues moving in the same path.
-- Calculates distance in which cursor reaches rectangle if it continues moving on the same path.
-- Returns `nil` if cursor is not moving towards the rectangle.
direction_to_rectangle_distance = function(rect)
if cursor.hidden or not cursor.history[1] then
return false
end
local prev_x, prev_y = cursor.history[1][1], cursor.history[1][2]
local end_x, end_y = cursor.x + (cursor.x - prev_x) * 1e10, cursor.y + (cursor.y - prev_y) * 1e10
local prev = cursor.find_history_sample()
if not prev then return false end
local end_x, end_y = cursor.x + (cursor.x - prev.x) * 1e10, cursor.y + (cursor.y - prev.y) * 1e10
return get_ray_to_rectangle_distance(cursor.x, cursor.y, end_x, end_y, rect)
end
}
@@ -464,7 +531,7 @@ function update_fullormaxed()
state.fullormaxed = state.fullscreen or state.maximized
update_display_dimensions()
Elements:trigger('prop_fullormaxed', state.fullormaxed)
update_cursor_position(INFINITY, INFINITY)
cursor.leave()
end
function update_human_times()
@@ -541,58 +608,6 @@ function set_state(name, value)
Elements:trigger('prop_' .. name, value)
end
function update_cursor_position(x, y)
local old_x, old_y = cursor.x, cursor.y
-- mpv reports initial mouse position on linux as (0, 0), which always
-- displays the top bar, so we hardcode cursor position as infinity until
-- we receive a first real mouse move event with coordinates other than 0,0.
if not state.first_real_mouse_move_received then
if x > 0 and y > 0 then state.first_real_mouse_move_received = true
else x, y = INFINITY, INFINITY end
end
-- Add 0.5 to be in the middle of the pixel
cursor.x, cursor.y = (x + 0.5) / display.scale_x, (y + 0.5) / display.scale_y
if old_x ~= cursor.x or old_y ~= cursor.y then
Elements:update_proximities()
if cursor.x == INFINITY or cursor.y == INFINITY then
cursor.hidden, cursor.history = true, {}
Elements:trigger('global_mouse_leave')
elseif cursor.hidden then
cursor.hidden, cursor.history = false, {}
Elements:trigger('global_mouse_enter')
else
-- Update cursor history
for i = 1, cursor.history_size - 1, 1 do
cursor.history[i] = cursor.history[i + 1]
end
cursor.history[cursor.history_size] = {x, y}
end
Elements:proximity_trigger('mouse_move')
cursor.queue_autohide()
end
request_render()
end
function handle_mouse_leave()
-- Slowly fadeout elements that are currently visible
for _, element_name in ipairs({'timeline', 'volume', 'top_bar'}) do
local element = Elements[element_name]
if element and element.proximity > 0 then
element:tween_property('forced_visibility', element:get_visibility(), 0, function()
element.forced_visibility = nil
end)
end
end
update_cursor_position(INFINITY, INFINITY)
end
function handle_file_end()
local resume = false
if not state.loop_file then
@@ -668,12 +683,15 @@ end
function handle_mouse_pos(_, mouse)
if not mouse then return end
msg.trace("handle_mouse_pos: x:", mouse.x, ", y:", mouse.y, ", hover:", mouse.hover, ", hover_raw:", cursor.hover_raw, "first_real_received:", state.first_real_mouse_move_received)
if cursor.hover_raw and not mouse.hover then
handle_mouse_leave()
cursor.leave()
else
update_cursor_position(mouse.x, mouse.y)
cursor.move(mouse.x, mouse.y)
end
if state.first_real_mouse_move_received then
cursor.hover_raw = mouse.hover
end
cursor.hover_raw = mouse.hover
end
mp.observe_property('mouse-pos', 'native', handle_mouse_pos)
mp.observe_property('osc', 'bool', function(name, value) if value == true then mp.set_property('osc', 'no') end end)
@@ -869,11 +887,15 @@ mp.observe_property('core-idle', 'native', create_state_setter('core_idle'))
--[[ KEY BINDS ]]
cursor.decide_keybinds()
-- Pointer related binding groups
function make_cursor_handler(event, cb)
return function(...)
call_maybe(cursor[event], ...)
msg.trace("UOSC EVENT:", event)
call_maybe(cb, ...)
msg.trace("calling cursor[event]", cursor[event])
call_maybe(cursor[event], ...)
cursor.queue_autohide() -- refresh cursor autohide timer
end
end
@@ -969,7 +991,7 @@ bind_command('playlist', create_self_updating_menu_opener({
serializer = function(playlist)
local items = {}
for index, item in ipairs(playlist) do
local is_url = item.filename:find('://')
local is_url = is_protocol(item.filename)
local item_title = type(item.title) == 'string' and #item.title > 0 and item.title or false
items[index] = {
title = item_title or (is_url and item.filename or serialize_path(item.filename).basename),