Skip to content

Commit

Permalink
Automated Update
Browse files Browse the repository at this point in the history
  • Loading branch information
natural-harmonia-gropius authored and github-actions[bot] committed Dec 9, 2024
1 parent 1681826 commit 46e38ef
Show file tree
Hide file tree
Showing 9 changed files with 415 additions and 163 deletions.
Binary file modified portable_config/fonts/uosc_icons.ttf
Binary file not shown.
1 change: 1 addition & 0 deletions portable_config/scripts/uosc/elements/Controls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function Controls:init_options()
['loop-playlist'] = 'cycle:repeat:loop-playlist:no/inf!?' .. t('Loop playlist'),
['loop-file'] = 'cycle:repeat_one:loop-file:no/inf!?' .. t('Loop file'),
['shuffle'] = 'toggle:shuffle:shuffle?' .. t('Shuffle'),
['autoload'] = 'toggle:hdr_auto:autoload@uosc?' .. t('Autoload'),
['fullscreen'] = 'cycle:fullscreen:fullscreen:no/yes=fullscreen_exit!?' .. t('Fullscreen'),
}

Expand Down
66 changes: 66 additions & 0 deletions portable_config/scripts/uosc/elements/Element.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ function Element:init(id, props)
self.anchor_id = nil
---@type fun()[] Disposer functions called when element is destroyed.
self._disposers = {}
---@type table<string,table<string, boolean>> Namespaced active key bindings. Default namespace is `_`.
self._key_bindings = {}

if props then table_assign(self, props) end

Expand All @@ -48,6 +50,7 @@ end
function Element:destroy()
for _, disposer in ipairs(self._disposers) do disposer() end
self.destroyed = true
self:remove_key_bindings()
Elements:remove(self)
end

Expand Down Expand Up @@ -191,4 +194,67 @@ function Element:observe_mp_property(name, type_or_callback, callback_maybe)
self:register_disposer(function() mp.unobserve_property(callback) end)
end

-- Adds a keybinding for the lifetime of the element, or until removed manually.
---@param key string mpv key identifier.
---@param fnFlags fun()|string|table<fun()|string> Callback, or `{callback, flags}` tuple. Callback can be just a method name, in which case it'll be wrapped in `create_action(callback)`.
---@param namespace? string Keybinding namespace. Default is `_`.
function Element:add_key_binding(key, fnFlags, namespace)
local name = self.id .. '-' .. key
local isTuple = type(fnFlags) == 'table'
local fn = (isTuple and fnFlags[1] or fnFlags)
local flags = isTuple and fnFlags[2] or nil
namespace = namespace or '_'
local names = self._key_bindings[namespace]
if not names then
names = {}
self._key_bindings[namespace] = names
end
names[name] = true
if type(fn) == 'string' then
fn = self:create_action(fn)
end
mp.add_forced_key_binding(key, name, fn, flags)
end

-- Remove all or only keybindings belonging to a specific namespace.
---@param namespace? string Optional keybinding namespace to remove.
function Element:remove_key_bindings(namespace)
local namespaces = namespace and {namespace} or table_keys(self._key_bindings)
for _, namespace in ipairs(namespaces) do
local names = self._key_bindings[namespace]
if names then
for name, _ in pairs(names) do
mp.remove_key_binding(name)
end
self._key_bindings[namespace] = nil
end
end
end

-- Checks if there are any (at all or namespaced) keybindings for this element.
---@param namespace? string Only check this namespace.
function Element:has_keybindings(namespace)
if namespace then
return self._key_bindings[namespace] ~= nil
else
return #table_keys(self._key_bindings) > 0
end
end

-- Check if element is not destroyed or otherwise disabled.
-- Intended to be overridden by inheriting elements to add more checks.
function Element:is_alive() return not self.destroyed end

-- Wraps a function into a callback that won't run if element is destroyed or otherwise disabled.
---@param fn fun(...)|string Function or a name of a method on this class to call.
function Element:create_action(fn)
if type(fn) == 'string' then
local method = fn
fn = function(...) self[method](self, ...) end
end
return function(...)
if self:is_alive() then fn(...) end
end
end

return Element
154 changes: 64 additions & 90 deletions portable_config/scripts/uosc/elements/Menu.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
local Element = require('elements/Element')

---@alias MenuAction {name: string; icon: string; label?: string;}
---@alias MenuAction {name: string; icon: string; label?: string; filter_hidden?: boolean;}

-- Menu data structure accepted by `Menu:open(menu)`.
---@alias MenuData {id?: string; type?: string; title?: string; hint?: string; footnote: string; search_style?: 'on_demand' | 'palette' | 'disabled'; item_actions?: MenuAction[]; item_actions_place?: 'inside' | 'outside'; callback?: string[]; keep_open?: boolean; bold?: boolean; italic?: boolean; muted?: boolean; separator?: boolean; align?: 'left'|'center'|'right'; items?: MenuDataChild[]; selected_index?: integer; on_search?: string|string[]; on_paste?: string|string[]; on_move?: string|string[]; on_close?: string|string[]; search_debounce?: number|string; search_submenus?: boolean; search_suggestion?: string}
Expand Down Expand Up @@ -32,7 +32,7 @@ local Menu = class(Element)
---@param callback MenuCallback
---@param opts? MenuOptions
function Menu:open(data, callback, opts)
local open_menu = self:is_open()
local open_menu = Menu:is_open()
if open_menu then
open_menu.is_being_replaced = true
open_menu:close(true)
Expand Down Expand Up @@ -61,12 +61,20 @@ function Menu:close(immediate, callback)
end

local function close()
Elements:remove('menu')
menu.is_closing, menu.root, menu.current, menu.all, menu.by_id = false, nil, nil, {}, {}
menu:disable_key_bindings()
local on_close = menu.root.on_close -- removed in menu:destroy()
Elements:remove('menu') -- calls menu:destroy() under the hood
Elements:update_proximities()
cursor:queue_autohide()

-- Call :close() callback
if callback then callback() end

-- Call callbacks/events defined on menu config
local close_event = {type = 'close'}
if not on_close or menu:command_or_event(on_close, {}, close_event) ~= 'event' then
menu.callback(close_event)
end

request_render()
end

Expand Down Expand Up @@ -117,11 +125,9 @@ function Menu:init(data, callback, opts)
self.all = nil
---@type table<string, MenuStack> Map of submenus by their ids, such as `'Tools > Aspect ratio'`.
self.by_id = {}
self.key_bindings = {}
self.key_bindings_search = {} -- temporary key bindings for search
self.type_to_search = options.menu_type_to_search
self.is_being_replaced = false
self.is_closing, self.is_closed = false, false
self.is_closing = false
self.drag_last_y = nil
self.is_dragging = false

Expand All @@ -141,22 +147,14 @@ end

function Menu:destroy()
Element.destroy(self)
self:disable_key_bindings()
self.is_closed = true
self.is_closing = false
if not self.is_being_replaced then Elements:maybe('curtain', 'unregister', self.id) end
if utils.shared_script_property_set then
utils.shared_script_property_set('uosc-menu-type', nil)
end
mp.set_property_native('user-data/uosc/menu/type', nil)
end

function Menu:request_close()
local callback = self.root.on_close
if not callback or self:command_or_event(callback, {}, {type = 'close'}) ~= 'event' then
self:close()
end
end

---@param data MenuData
function Menu:update(data)
local new_root = {is_root = true, submenu_path = {}}
Expand Down Expand Up @@ -606,7 +604,7 @@ function Menu:slide_in_menu(id, x)
end

function Menu:back()
if self.is_closed then return end
if not self:is_alive() then return end

local current = self.current
local parent = current.parent_menu
Expand Down Expand Up @@ -654,6 +652,7 @@ end

---@param index integer
function Menu:move_selected_item_to(index)
if self.current.search then return end -- Moving filtered items is an undefined behavior
local callback = self.current.on_move
local from, items_count = self.current.selected_index, self.current.items and #self.current.items or 0
if callback and from and from ~= index and index >= 1 and index <= items_count then
Expand Down Expand Up @@ -684,7 +683,7 @@ function Menu:handle_cursor_down()
self.drag_last_y = cursor.y
self.current.fling = nil
else
self:request_close()
self:close()
end
end

Expand Down Expand Up @@ -994,40 +993,25 @@ function Menu:search_clear_query(menu_id)
end

function Menu:search_enable_key_bindings()
if #self.key_bindings_search ~= 0 then return end
if self:has_keybindings('search') then return end
local flags = {repeatable = true, complex = true}
self:search_add_key_binding('any_unicode', 'menu-search', self:create_key_handler('search_text_input'), flags)
self:add_key_binding('any_unicode', {self:create_key_handler('search_text_input'), flags}, 'search')
-- KP0 to KP9 and KP_DEC are not included in any_unicode
-- despite typically producing characters, they don't have a info.key_text
self:search_add_key_binding('kp_dec', 'menu-search-kp-dec', self:create_key_handler('search_text_input'), flags)
self:add_key_binding('kp_dec', {self:create_key_handler('search_text_input'), flags}, 'search')
for i = 0, 9 do
self:search_add_key_binding('kp' .. i, 'menu-search-kp' .. i, self:create_key_handler('search_text_input'), flags)
self:add_key_binding('kp' .. i, {self:create_key_handler('search_text_input'), flags}, 'search')
end
end

function Menu:search_ensure_key_bindings()
if self.current.search or (self.type_to_search and self.current.search_style ~= 'disabled') then
self:search_enable_key_bindings()
else
self:search_disable_key_bindings()
self:remove_key_bindings('search')
end
end

function Menu:search_disable_key_bindings()
for _, name in ipairs(self.key_bindings_search) do mp.remove_key_binding(name) end
self.key_bindings_search = {}
end

function Menu:search_add_key_binding(key, name, fn, flags)
self.key_bindings_search[#self.key_bindings_search + 1] = name
mp.add_forced_key_binding(key, name, fn, flags)
end

function Menu:add_key_binding(key, name, fn, flags)
self.key_bindings[#self.key_bindings + 1] = name
mp.add_forced_key_binding(key, name, fn, flags)
end

function Menu:enable_key_bindings()
-- `+` at the end enables `repeatable` flag
local standalone_keys = {
Expand All @@ -1043,7 +1027,7 @@ function Menu:enable_key_bindings()
local binding = modifier and modifier .. '+' .. key or key
local shortcut = create_shortcut(normalized[key] or key, modifier)
local handler = self:create_action(function(info) self:handle_shortcut(shortcut, info) end)
self:add_key_binding(binding, 'menu-binding-' .. binding, handler, flags)
self:add_key_binding(binding, {handler, flags})
end

for i, key_mods in ipairs(standalone_keys) do
Expand Down Expand Up @@ -1117,7 +1101,7 @@ function Menu:handle_shortcut(shortcut, info)
if menu.search and menu.search_style ~= 'palette' then
self:search_cancel()
else
self:request_close()
self:close()
end
elseif id == 'left' and menu.parent_menu then
self:back()
Expand Down Expand Up @@ -1146,22 +1130,8 @@ function Menu:handle_shortcut(shortcut, info)
end
end

function Menu:disable_key_bindings()
self:search_disable_key_bindings()
for _, name in ipairs(self.key_bindings) do mp.remove_key_binding(name) end
self.key_bindings = {}
end

-- Check if menu is not closed or closing.
function Menu:is_alive() return not self.is_closing and not self.is_closed 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 self:is_alive() then fn(...) end
end
end
function Menu:is_alive() return not self.is_closing and not self.destroyed end

---@param name string
function Menu:create_key_handler(name)
Expand Down Expand Up @@ -1353,41 +1323,45 @@ function Menu:render()
for i = 1, #actions, 1 do
local action_index = #actions - (i - 1)
local action = actions[action_index]
local is_active = action_index == menu.action_index
local bx = actions_rect.ax - (i == 1 and 0 or margin)
local rect = {
ay = actions_rect.ay,
by = actions_rect.by,
ax = bx - size,
bx = bx,
}
actions_rect.ax = rect.ax

ass:rect(rect.ax, rect.ay, rect.bx, rect.by, {
radius = state.radius > 2 and state.radius - 1 or state.radius,
color = is_active and fg or bg,
border = is_active and self.gap or nil,
border_color = bg,
opacity = menu_opacity,
clip = item_clip,
})
ass:icon(rect.ax + size / 2, rect.ay + size / 2, size * 0.66, action.icon, {
color = is_active and bg or fg, opacity = menu_opacity, clip = item_clip,
})

-- Re-use rect as a hitbox by growing it so it bridges gaps to prevent flickering
rect.ay, rect.by, rect.bx = item_ay, item_ay + self.scroll_step, rect.bx + margin

-- Select action on cursor hover
if self.mouse_nav and get_point_to_rectangle_proximity(cursor, rect) == 0 then
cursor:zone('primary_click', rect, self:create_action(function(shortcut)
self:activate_selected_item(shortcut, true)
end))
blur_action_index = false
if not is_active then
menu.action_index = action_index
selected_action = actions[action_index]
request_render()
-- Hide when the action shouldn't be displayed when the item is a result of a search/filter
if not (action.filter_hidden and menu.search) then
local is_active = action_index == menu.action_index
local bx = actions_rect.ax - (i == 1 and 0 or margin)
local rect = {
ay = actions_rect.ay,
by = actions_rect.by,
ax = bx - size,
bx = bx,
}
actions_rect.ax = rect.ax

ass:rect(rect.ax, rect.ay, rect.bx, rect.by, {
radius = state.radius > 2 and state.radius - 1 or state.radius,
color = is_active and fg or bg,
border = is_active and self.gap or nil,
border_color = bg,
opacity = menu_opacity,
clip = item_clip,
})
ass:icon(rect.ax + size / 2, rect.ay + size / 2, size * 0.66, action.icon, {
color = is_active and bg or fg, opacity = menu_opacity, clip = item_clip,
})

-- Re-use rect as a hitbox by growing it so it bridges gaps to prevent flickering
rect.ay, rect.by, rect.bx = item_ay, item_ay + self.scroll_step, rect.bx + margin

-- Select action on cursor hover
if self.mouse_nav and get_point_to_rectangle_proximity(cursor, rect) == 0 then
cursor:zone('primary_click', rect, self:create_action(function(shortcut)
self:activate_selected_item(shortcut, true)
end))
blur_action_index = false
if not is_active then
menu.action_index = action_index
selected_action = actions[action_index]
request_render()
end
end
end
end
Expand Down
Loading

0 comments on commit 46e38ef

Please sign in to comment.