Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
80b767e966 | |||
8998e63123 | |||
![]() |
d25f3e2e99 | ||
![]() |
3f21df1a9d | ||
![]() |
776ca17f66 | ||
![]() |
d1e9f9c4eb | ||
![]() |
6606f3e11f | ||
![]() |
68b5b2aaed | ||
![]() |
f857d468e1 | ||
![]() |
bc7b1a12bc | ||
![]() |
cad0d174d7 | ||
![]() |
71cdeda7b3 |
@@ -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):
|
On Linux and macOS these terminal commands can be used to install or update uosc (if wget and unzip are installed):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkdir -pv ~/.config/mpv/script-opts/
|
config_dir="${XDG_CONFIG_HOME:-~/.config}"
|
||||||
rm -rf ~/.config/mpv/scripts/uosc_shared
|
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
|
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
|
rm -fv /tmp/uosc.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -47,7 +47,7 @@ timeline_cache=yes
|
|||||||
# `cycle:{default_icon}:{prop}[@{owner}]:{value1}[={icon1}][!]/{valueN}[={iconN}][!]`
|
# `cycle:{default_icon}:{prop}[@{owner}]:{value1}[={icon1}][!]/{valueN}[={iconN}][!]`
|
||||||
# - button that cycles mpv property between values, each optionally having different icon and active flag
|
# - 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
|
# - 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
|
# `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
|
# `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
|
# - 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
|
# Border of text and icons when drawn directly on top of video
|
||||||
text_border=1.2
|
text_border=1.2
|
||||||
# Use a faster estimation method instead of accurate measurement
|
# 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
|
text_width_estimation=yes
|
||||||
# Execute command for background clicks shorter than this number of milliseconds, 0 to disable
|
# 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
|
# Execution always waits for `input-doubleclick-time` to filter out double-clicks
|
||||||
|
@@ -106,8 +106,7 @@ function Menu:init(data, callback, opts)
|
|||||||
self.key_bindings = {}
|
self.key_bindings = {}
|
||||||
self.is_being_replaced = false
|
self.is_being_replaced = false
|
||||||
self.is_closing, self.is_closed = false, false
|
self.is_closing, self.is_closed = false, false
|
||||||
---@type {y: integer, time: number}[]
|
self.drag_last_y = nil
|
||||||
self.drag_data = nil
|
|
||||||
self.is_dragging = false
|
self.is_dragging = false
|
||||||
|
|
||||||
self:update(data)
|
self:update(data)
|
||||||
@@ -545,47 +544,37 @@ function Menu:on_prop_fullormaxed() self:update_content_dimensions() end
|
|||||||
|
|
||||||
function Menu:handle_cursor_down()
|
function Menu:handle_cursor_down()
|
||||||
if self.proximity_raw == 0 then
|
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
|
self.current.fling = nil
|
||||||
else
|
else
|
||||||
self:close()
|
self:close()
|
||||||
end
|
end
|
||||||
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()
|
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})
|
self:open_selected_item({preselect_first_item = false, keep_open = self.modifiers and self.modifiers.shift})
|
||||||
end
|
end
|
||||||
if self.is_dragging then
|
if self.is_dragging then
|
||||||
local distance = self:fling_distance()
|
local distance = cursor.get_velocity().y / -3
|
||||||
if math.abs(distance) > 50 then
|
if math.abs(distance) > 50 then
|
||||||
self.current.fling = {
|
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,
|
easing = ease_out_quart, duration = 0.5, update_cursor = true,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.is_dragging = false
|
self.is_dragging = false
|
||||||
self.drag_data = nil
|
self.drag_last_y = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function Menu:on_global_mouse_move()
|
function Menu:on_global_mouse_move()
|
||||||
self.mouse_nav = true
|
self.mouse_nav = true
|
||||||
if self.drag_data then
|
if self.drag_last_y then
|
||||||
self.is_dragging = self.is_dragging or math.abs(cursor.y - self.drag_data[1].y) >= 10
|
self.is_dragging = self.is_dragging or math.abs(cursor.y - self.drag_last_y) >= 10
|
||||||
local distance = self.drag_data[#self.drag_data].y - cursor.y
|
local distance = self.drag_last_y - cursor.y
|
||||||
if distance ~= 0 then self:set_scroll_by(distance) end
|
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
|
end
|
||||||
request_render()
|
request_render()
|
||||||
end
|
end
|
||||||
@@ -662,26 +651,34 @@ function Menu:disable_key_bindings()
|
|||||||
self.key_bindings = {}
|
self.key_bindings = {}
|
||||||
end
|
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
|
---@param modifiers Modifiers
|
||||||
function Menu:create_modified_mbtn_left_handler(modifiers)
|
function Menu:create_modified_mbtn_left_handler(modifiers)
|
||||||
return function()
|
return self:create_action(function()
|
||||||
self.mouse_nav = true
|
self.mouse_nav = true
|
||||||
self.modifiers = modifiers
|
self.modifiers = modifiers
|
||||||
self:handle_cursor_down()
|
self:handle_cursor_down()
|
||||||
self:handle_cursor_up()
|
self:handle_cursor_up()
|
||||||
self.modifiers = nil
|
self.modifiers = nil
|
||||||
end
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@param modifiers? Modifiers
|
---@param modifiers? Modifiers
|
||||||
function Menu:create_key_action(name, modifiers)
|
function Menu:create_key_action(name, modifiers)
|
||||||
return function()
|
return self:create_action(function()
|
||||||
self.mouse_nav = false
|
self.mouse_nav = false
|
||||||
self.modifiers = modifiers
|
self.modifiers = modifiers
|
||||||
self:maybe(name)
|
self:maybe(name)
|
||||||
self.modifiers = nil
|
self.modifiers = nil
|
||||||
end
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Menu:render()
|
function Menu:render()
|
||||||
@@ -694,11 +691,11 @@ function Menu:render()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
cursor.on_primary_down = function() self:handle_cursor_down() end
|
cursor.on_primary_down = self:create_action(function() self:handle_cursor_down() end)
|
||||||
cursor.on_primary_up = function() self:handle_cursor_up() end
|
cursor.on_primary_up = self:create_action(function() self:handle_cursor_up() end)
|
||||||
if self.proximity_raw == 0 then
|
if self.proximity_raw == 0 then
|
||||||
cursor.on_wheel_down = function() self:handle_wheel_down() end
|
cursor.on_wheel_down = self:create_action(function() self:handle_wheel_down() end)
|
||||||
cursor.on_wheel_up = function() self:handle_wheel_up() end
|
cursor.on_wheel_up = self:create_action(function() self:handle_wheel_up() end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local ass = assdraw.ass_new()
|
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})
|
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
|
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
|
end
|
||||||
|
|
||||||
-- Draw submenu if selected
|
-- Draw submenu if selected
|
||||||
@@ -737,7 +734,9 @@ function Menu:render()
|
|||||||
submenu_rect = draw_menu(current_item, menu_rect.bx + menu_gap, 1)
|
submenu_rect = draw_menu(current_item, menu_rect.bx + menu_gap, 1)
|
||||||
submenu_is_hovered = get_point_to_rectangle_proximity(cursor, submenu_rect) == 0
|
submenu_is_hovered = get_point_to_rectangle_proximity(cursor, submenu_rect) == 0
|
||||||
if submenu_is_hovered then
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -17,10 +17,6 @@ function Timeline:init()
|
|||||||
self.is_hovered = false
|
self.is_hovered = false
|
||||||
self.has_thumbnail = 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
|
-- Release any dragging when file gets unloaded
|
||||||
mp.register_event('end-file', function() self.pressed = false end)
|
mp.register_event('end-file', function() self.pressed = false end)
|
||||||
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_prop_fullormaxed() self:update_dimensions() end
|
||||||
function Timeline:on_display() self:update_dimensions() end
|
function Timeline:on_display() self:update_dimensions() end
|
||||||
function Timeline:handle_cursor_up()
|
function Timeline:handle_cursor_up()
|
||||||
self.seek_timer:kill()
|
|
||||||
if self.pressed then
|
if self.pressed then
|
||||||
mp.set_property_native('pause', self.pressed.pause)
|
mp.set_property_native('pause', self.pressed.pause)
|
||||||
self.pressed = false
|
self.pressed = false
|
||||||
@@ -127,10 +122,8 @@ function Timeline:on_global_mouse_move()
|
|||||||
if self.pressed then
|
if self.pressed then
|
||||||
self.pressed.distance = self.pressed.distance + get_point_to_point_proximity(self.pressed.last, cursor)
|
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
|
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:set_from_cursor(true)
|
||||||
self.seek_timer:kill()
|
|
||||||
self.seek_timer:resume()
|
|
||||||
else self:set_from_cursor() end
|
else self:set_from_cursor() end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
msg = require('mp.msg')
|
||||||
local Element = require('elements/Element')
|
local Element = require('elements/Element')
|
||||||
|
|
||||||
--[[ MuteButton ]]
|
--[[ MuteButton ]]
|
||||||
@@ -73,7 +74,9 @@ function VolumeSlider:render()
|
|||||||
|
|
||||||
if width <= 0 or height <= 0 or visibility <= 0 then return end
|
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
|
if self.proximity_raw == 0 then
|
||||||
|
msg.trace("VolumeSlider: setting on_primary_down")
|
||||||
cursor.on_primary_down = function()
|
cursor.on_primary_down = function()
|
||||||
self.pressed = true
|
self.pressed = true
|
||||||
self:set_from_cursor()
|
self:set_from_cursor()
|
||||||
|
59
scripts/uosc/intl/ru.json
Normal file
59
scripts/uosc/intl/ru.json
Normal 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": "плейлист или файл"
|
||||||
|
}
|
@@ -202,3 +202,59 @@ function Class:init() end
|
|||||||
function Class:destroy() end
|
function Class:destroy() end
|
||||||
|
|
||||||
function class(parent) return setmetatable({}, {__index = parent or Class}) 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
|
||||||
|
@@ -1,50 +1,26 @@
|
|||||||
--[[ UI specific utilities that might or might not depend on its state or options ]]
|
--[[ 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.
|
--- In place sorting of filenames
|
||||||
sort_filenames = (function()
|
---@param filenames string[]
|
||||||
local symbol_order
|
function sort_filenames(filenames)
|
||||||
local default_order
|
-- alphanum sorting for humans in Lua
|
||||||
|
|
||||||
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
|
|
||||||
-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua
|
-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua
|
||||||
local function pad_number(n, d)
|
local function padnum(n, d)
|
||||||
return #d > 0 and ("%03d%s%.12f"):format(#n, n, tonumber(d) / (10 ^ #d))
|
return #d > 0 and ('%03d%s%.12f'):format(#n, n, tonumber(d) / (10 ^ #d))
|
||||||
or ("%03d%s"):format(#n, n)
|
or ('%03d%s'):format(#n, n)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- In place sorting of filenames
|
local tuples = {}
|
||||||
---@param filenames string[]
|
for i, f in ipairs(filenames) do
|
||||||
return function(filenames)
|
tuples[i] = { f:lower():gsub('0*(%d+)%.?(%d*)', padnum), f }
|
||||||
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
|
|
||||||
end
|
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.
|
-- Creates in-between frames to animate value from `from` to `to` numbers.
|
||||||
---@param from number
|
---@param from number
|
||||||
@@ -319,7 +295,7 @@ end
|
|||||||
-- Check if path is a protocol, such as `http://...`.
|
-- Check if path is a protocol, such as `http://...`.
|
||||||
---@param path string
|
---@param path string
|
||||||
function is_protocol(path)
|
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
|
end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
@@ -646,6 +622,7 @@ end
|
|||||||
|
|
||||||
function render()
|
function render()
|
||||||
if not display.initialized then return end
|
if not display.initialized then return end
|
||||||
|
msg.trace("render")
|
||||||
state.render_last_time = mp.get_time()
|
state.render_last_time = mp.get_time()
|
||||||
|
|
||||||
cursor.reset_handlers()
|
cursor.reset_handlers()
|
||||||
|
@@ -316,8 +316,7 @@ cursor = {
|
|||||||
on_wheel_down = nil,
|
on_wheel_down = nil,
|
||||||
on_wheel_up = nil,
|
on_wheel_up = nil,
|
||||||
allow_dragging = false,
|
allow_dragging = false,
|
||||||
history = {}, -- {x, y}[] history
|
history = CircularBuffer:new(10),
|
||||||
history_size = 10,
|
|
||||||
-- Called at the beginning of each render
|
-- Called at the beginning of each render
|
||||||
reset_handlers = function()
|
reset_handlers = function()
|
||||||
cursor.on_primary_down, cursor.on_primary_up = nil, nil
|
cursor.on_primary_down, cursor.on_primary_up = nil, nil
|
||||||
@@ -328,7 +327,7 @@ cursor = {
|
|||||||
mbtn_left_enabled = nil,
|
mbtn_left_enabled = nil,
|
||||||
wheel_enabled = nil,
|
wheel_enabled = nil,
|
||||||
decide_keybinds = function()
|
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
|
local enable_wheel = (cursor.on_wheel_down or cursor.on_wheel_up) ~= nil
|
||||||
if enable_mbtn_left ~= cursor.mbtn_left_enabled then
|
if enable_mbtn_left ~= cursor.mbtn_left_enabled then
|
||||||
local flags = cursor.allow_dragging and 'allow-vo-dragging' or nil
|
local flags = cursor.allow_dragging and 'allow-vo-dragging' or nil
|
||||||
@@ -340,9 +339,80 @@ cursor = {
|
|||||||
cursor.wheel_enabled = enable_wheel
|
cursor.wheel_enabled = enable_wheel
|
||||||
end
|
end
|
||||||
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
|
-- Cursor auto-hiding after period of inactivity
|
||||||
autohide = function()
|
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,
|
end,
|
||||||
autohide_timer = (function()
|
autohide_timer = (function()
|
||||||
local timer = mp.add_timeout(mp.get_property_native('cursor-autohide') / 1000, function() cursor.autohide() end)
|
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()
|
cursor.autohide_timer:resume()
|
||||||
end
|
end
|
||||||
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.
|
-- Returns `nil` if cursor is not moving towards the rectangle.
|
||||||
direction_to_rectangle_distance = function(rect)
|
direction_to_rectangle_distance = function(rect)
|
||||||
if cursor.hidden or not cursor.history[1] then
|
local prev = cursor.find_history_sample()
|
||||||
return false
|
if not prev then return false end
|
||||||
end
|
local end_x, end_y = cursor.x + (cursor.x - prev.x) * 1e10, cursor.y + (cursor.y - prev.y) * 1e10
|
||||||
|
|
||||||
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
|
|
||||||
return get_ray_to_rectangle_distance(cursor.x, cursor.y, end_x, end_y, rect)
|
return get_ray_to_rectangle_distance(cursor.x, cursor.y, end_x, end_y, rect)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
@@ -464,7 +531,7 @@ function update_fullormaxed()
|
|||||||
state.fullormaxed = state.fullscreen or state.maximized
|
state.fullormaxed = state.fullscreen or state.maximized
|
||||||
update_display_dimensions()
|
update_display_dimensions()
|
||||||
Elements:trigger('prop_fullormaxed', state.fullormaxed)
|
Elements:trigger('prop_fullormaxed', state.fullormaxed)
|
||||||
update_cursor_position(INFINITY, INFINITY)
|
cursor.leave()
|
||||||
end
|
end
|
||||||
|
|
||||||
function update_human_times()
|
function update_human_times()
|
||||||
@@ -541,58 +608,6 @@ function set_state(name, value)
|
|||||||
Elements:trigger('prop_' .. name, value)
|
Elements:trigger('prop_' .. name, value)
|
||||||
end
|
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()
|
function handle_file_end()
|
||||||
local resume = false
|
local resume = false
|
||||||
if not state.loop_file then
|
if not state.loop_file then
|
||||||
@@ -668,12 +683,15 @@ end
|
|||||||
|
|
||||||
function handle_mouse_pos(_, mouse)
|
function handle_mouse_pos(_, mouse)
|
||||||
if not mouse then return end
|
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
|
if cursor.hover_raw and not mouse.hover then
|
||||||
handle_mouse_leave()
|
cursor.leave()
|
||||||
else
|
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
|
end
|
||||||
cursor.hover_raw = mouse.hover
|
|
||||||
end
|
end
|
||||||
mp.observe_property('mouse-pos', 'native', handle_mouse_pos)
|
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)
|
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 ]]
|
--[[ KEY BINDS ]]
|
||||||
|
|
||||||
|
cursor.decide_keybinds()
|
||||||
|
|
||||||
-- Pointer related binding groups
|
-- Pointer related binding groups
|
||||||
function make_cursor_handler(event, cb)
|
function make_cursor_handler(event, cb)
|
||||||
return function(...)
|
return function(...)
|
||||||
call_maybe(cursor[event], ...)
|
msg.trace("UOSC EVENT:", event)
|
||||||
call_maybe(cb, ...)
|
call_maybe(cb, ...)
|
||||||
|
msg.trace("calling cursor[event]", cursor[event])
|
||||||
|
call_maybe(cursor[event], ...)
|
||||||
cursor.queue_autohide() -- refresh cursor autohide timer
|
cursor.queue_autohide() -- refresh cursor autohide timer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -969,7 +991,7 @@ bind_command('playlist', create_self_updating_menu_opener({
|
|||||||
serializer = function(playlist)
|
serializer = function(playlist)
|
||||||
local items = {}
|
local items = {}
|
||||||
for index, item in ipairs(playlist) do
|
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
|
local item_title = type(item.title) == 'string' and #item.title > 0 and item.title or false
|
||||||
items[index] = {
|
items[index] = {
|
||||||
title = item_title or (is_url and item.filename or serialize_path(item.filename).basename),
|
title = item_title or (is_url and item.filename or serialize_path(item.filename).basename),
|
||||||
|
Reference in New Issue
Block a user