Skip to content
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

feat: added disable_elements option and disable-elements script message #695

Merged
merged 7 commits into from
Oct 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,16 @@ mp.commandv('script-message-to', 'uosc', 'overwrite-binding', 'stream-quality',

To cancel the overwrite and return to default behavior, just omit the `<command>` parameter.

### `disable-elements <script_id> <element_ids>`

Set what uosc elements your script wants to disable. To cancel or re-enable them, send the message again with an empty string in place of `element_ids`.

```lua
mp.commandv('script-message-to', 'uosc', 'disable-elements', mp.get_script_name(), 'timeline,volume')
```

Using `'user'` as `script_id` will overwrite user's `disable_elements` config. Elements will be enabled only when neither user, nor any script requested them to be disabled.

## Why _uosc_?

It stood for micro osc as it used to render just a couple rectangles before it grew to what it is today. And now it means a minimalist UI design direction where everything is out of your way until needed.
5 changes: 5 additions & 0 deletions script-opts/uosc.conf
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,8 @@ chapter_range_patterns=openings:オープニング;endings:エンディング
# `slang` is a keyword to inherit values from `--slang` mpv config.
# Supports paths to custom json files: `languages=~~/custom.json,slang,en`
languages=slang,en

# A comma separated list of element IDs to disable. Available IDs:
# window_border, top_bar, timeline, controls, volume,
# audio_indicator, buffering_indicator, pause_indicator
disable_elements=
3 changes: 2 additions & 1 deletion scripts/uosc/elements/BufferingIndicator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ local BufferingIndicator = class(Element)

function BufferingIndicator:new() return Class.new(self) --[[@as BufferingIndicator]] end
function BufferingIndicator:init()
Element.init(self, 'buffer_indicator')
Element.init(self, 'buffer_indicator', {render_order = 2})
self.ignores_menu = true
self.enabled = false
self:decide_enabled()
end

function BufferingIndicator:decide_enabled()
Expand Down
35 changes: 24 additions & 11 deletions scripts/uosc/elements/Controls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ local Controls = class(Element)

function Controls:new() return Class.new(self) --[[@as Controls]] end
function Controls:init()
Element.init(self, 'controls')
Element.init(self, 'controls', {render_order = 6})
---@type ControlItem[] All control elements serialized from `options.controls`.
self.controls = {}
---@type ControlItem[] Only controls that match current dispositions.
Expand All @@ -22,6 +22,11 @@ function Controls:init()
self:init_options()
end

function Controls:destroy()
self:destroy_elements()
Element.destroy(self)
end

function Controls:init_options()
-- Serialize control elements
local shorthands = {
Expand Down Expand Up @@ -109,6 +114,7 @@ function Controls:init_options()
))
else
local element = Button:new('control_' .. i, {
render_order = self.render_order,
icon = params[1],
anchor_id = 'controls',
on_click = function() mp.command(params[2]) end,
Expand Down Expand Up @@ -140,14 +146,18 @@ function Controls:init_options()
end

local element = CycleButton:new('control_' .. i, {
prop = params[2], anchor_id = 'controls', states = states, tooltip = tooltip,
render_order = self.render_order,
prop = params[2],
anchor_id = 'controls',
states = states,
tooltip = tooltip,
})
table_assign(control, {element = element, sizing = 'static', scale = 1, ratio = 1})
if badge then self:register_badge_updater(badge, element) end
end
elseif kind == 'speed' then
if not Elements.speed then
local element = Speed:new({anchor_id = 'controls'})
local element = Speed:new({anchor_id = 'controls', render_order = self.render_order})
local scale = tonumber(params[1]) or 1.3
table_assign(control, {
element = element, sizing = 'dynamic', scale = scale, ratio = 3.5, ratio_min = 2,
Expand Down Expand Up @@ -215,24 +225,23 @@ function Controls:register_badge_updater(badge, element)
end

if is_external_prop then element['on_external_prop_' .. prop] = function(_, value) handler(prop, value) end
else mp.observe_property(observable_name, 'native', handler) end
else self:observe_mp_property(observable_name, handler) end
end

function Controls:get_visibility()
return (Elements.speed and Elements.speed.dragging) and 1 or Elements.timeline:get_is_hovered()
return Elements:v('speed', 'dragging') and 1 or Elements:maybe('timeline', 'get_is_hovered')
and -1 or Element.get_visibility(self)
end

function Controls:update_dimensions()
local window_border = Elements.window_border.size
local window_border = Elements:v('window_border', 'size', 0)
local size = round(options.controls_size * state.scale)
local spacing = round(options.controls_spacing * state.scale)
local margin = round(options.controls_margin * state.scale)

-- Disable when not enough space
local available_space = display.height - Elements.window_border.size * 2
if Elements.top_bar.enabled then available_space = available_space - Elements.top_bar.size end
if Elements.timeline.enabled then available_space = available_space - Elements.timeline.size end
local available_space = display.height - window_border * 2 - Elements:v('top_bar', 'size', 0)
- Elements:v('timeline', 'size', 0)
self.enabled = available_space > size + 10

-- Reset hide/enabled flags
Expand All @@ -245,7 +254,7 @@ function Controls:update_dimensions()

-- Container
self.bx = display.width - window_border - margin
self.by = (Elements.timeline.enabled and Elements.timeline.ay or display.height - window_border) - margin
self.by = Elements:v('timeline', 'ay', display.height - window_border) - margin
self.ax, self.ay = window_border + margin, self.by - size

-- Controls
Expand Down Expand Up @@ -332,10 +341,14 @@ function Controls:on_prop_title_bar() self:update_dimensions() end
function Controls:on_prop_fullormaxed() self:update_dimensions() end
function Controls:on_timeline_enabled() self:update_dimensions() end

function Controls:on_options()
function Controls:destroy_elements()
for _, control in ipairs(self.controls) do
if control.element then control.element:destroy() end
end
end

function Controls:on_options()
self:destroy_elements()
self:init_options()
end

Expand Down
2 changes: 1 addition & 1 deletion scripts/uosc/elements/Curtain.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ local Curtain = class(Element)

function Curtain:new() return Class.new(self) --[[@as Curtain]] end
function Curtain:init()
Element.init(self, 'curtain', {ignores_menu = true})
Element.init(self, 'curtain', {ignores_menu = true, render_order = 999})
self.opacity = 0
---@type string[]
self.dependents = {}
Expand Down
19 changes: 7 additions & 12 deletions scripts/uosc/elements/CycleButton.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ function CycleButton:init(id, props)
end
end

self.handle_change = function(name, value)
if is_state_prop and type(value) == 'boolean' then value = value and 'yes' or 'no' end
local function handle_change(name, value)
value = type(value) == 'boolean' and (value and 'yes' or 'no') or tostring(value or '')
local index = itable_find(self.states, function(state) return state.value == value end)
self.current_state_index = index or 1
self.icon = self.states[self.current_state_index].icon
Expand All @@ -46,19 +46,14 @@ function CycleButton:init(id, props)
local prop_parts = split(self.prop, '@')
if #prop_parts == 2 then -- External prop with a script owner
self.prop, self.owner = prop_parts[1], prop_parts[2]
self['on_external_prop_' .. self.prop] = function(_, value) self.handle_change(self.prop, value) end
self.handle_change(self.prop, external[self.prop])
self['on_external_prop_' .. self.prop] = function(_, value) handle_change(self.prop, value) end
handle_change(self.prop, external[self.prop])
elseif is_state_prop then -- uosc's state props
self['on_prop_' .. self.prop] = function(self, value) self.handle_change(self.prop, value) end
self.handle_change(self.prop, state[self.prop])
self['on_prop_' .. self.prop] = function(self, value) handle_change(self.prop, value) end
handle_change(self.prop, state[self.prop])
else
mp.observe_property(self.prop, 'string', self.handle_change)
self:observe_mp_property(self.prop, handle_change)
end
end

function CycleButton:destroy()
Button.destroy(self)
mp.unobserve_property(self.handle_change)
end

return CycleButton
35 changes: 33 additions & 2 deletions scripts/uosc/elements/Element.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---@alias ElementProps {enabled?: boolean; ax?: number; ay?: number; bx?: number; by?: number; ignores_menu?: boolean; anchor_id?: string;}
---@alias ElementProps {enabled?: boolean; render_order?: number; ax?: number; ay?: number; bx?: number; by?: number; ignores_menu?: boolean; anchor_id?: string;}

-- Base class all elements inherit from.
---@class Element : Class
Expand All @@ -8,6 +8,7 @@ local Element = class()
---@param props? ElementProps
function Element:init(id, props)
self.id = id
self.render_order = 1
-- `false` means element won't be rendered, or receive events
self.enabled = true
-- Element coordinates
Expand All @@ -24,6 +25,8 @@ function Element:init(id, props)
self.ignores_menu = false
---@type nil|string ID of an element from which this one should inherit visibility.
self.anchor_id = nil
---@type fun()[] Disposer functions called when element is destroyed.
self._disposers = {}

if props then table_assign(self, props) end

Expand All @@ -40,6 +43,7 @@ function Element:init(id, props)
end

function Element:destroy()
for _, disposer in ipairs(self._disposers) do disposer() end
self.destroyed = true
Elements:remove(self)
end
Expand Down Expand Up @@ -70,7 +74,10 @@ function Element:is_persistent()
local persist = config[self.id .. '_persistency']
return persist and (
(persist.audio and state.is_audio)
or (persist.paused and state.pause and (not Elements.timeline.pressed or Elements.timeline.pressed.pause))
or (
persist.paused and state.pause
and (not Elements.timeline or not Elements.timeline.pressed or Elements.timeline.pressed.pause)
)
or (persist.video and state.is_video)
or (persist.image and state.is_image)
or (persist.idle and state.is_idle)
Expand Down Expand Up @@ -154,4 +161,28 @@ function Element:flash()
end
end

-- Register disposer to be called when element is destroyed.
---@param disposer fun()
function Element:register_disposer(disposer)
if not itable_index_of(self._disposers, disposer) then
tomasklaen marked this conversation as resolved.
Show resolved Hide resolved
self._disposers[#self._disposers + 1] = disposer
end
end

-- Automatically registers disposer for the passed callback.
---@param event string
---@param callback fun()
function Element:register_mp_event(event, callback)
mp.register_event(event, callback)
self:register_disposer(function() mp.unregister_event(callback) end)
end

-- Automatically registers disposer for the observer.
---@param name string
---@param callback fun(name: string, value: any)
function Element:observe_mp_property(name, callback)
mp.observe_property(name, 'native', callback)
self:register_disposer(function() mp.unobserve_property(callback) end)
end

return Element
33 changes: 26 additions & 7 deletions scripts/uosc/elements/Elements.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local Elements = {itable = {}}
local Elements = {_all = {}}

---@param element Element
function Elements:add(element)
Expand All @@ -9,9 +9,12 @@ function Elements:add(element)

if self:has(element.id) then Elements:remove(element.id) end

self.itable[#self.itable + 1] = element
self._all[#self._all + 1] = element
self[element.id] = element

-- Sort by render order
table.sort(self._all, function(a, b) return a.render_order < b.render_order end)

request_render()
end

Expand All @@ -22,7 +25,7 @@ function Elements:remove(idOrElement)
if element then
if not element.destroyed then element:destroy() end
element.enabled = false
self.itable = itable_delete_value(self.itable, self[id])
self._all = itable_delete_value(self._all, self[id])
self[id] = nil
request_render()
end
Expand Down Expand Up @@ -95,7 +98,7 @@ end
-- Flash passed elements.
---@param ids string[] IDs of elements to peek.
function Elements:flash(ids)
local elements = itable_filter(self.itable, function(element) return itable_index_of(ids, element.id) ~= nil end)
local elements = itable_filter(self._all, function(element) return itable_index_of(ids, element.id) ~= nil end)
for _, element in ipairs(elements) do element:flash() end
end

Expand All @@ -108,8 +111,8 @@ end
-- Disabled elements don't receive these events.
---@param name string Event name.
function Elements:proximity_trigger(name, ...)
for i = #self.itable, 1, -1 do
local element = self.itable[i]
for i = #self._all, 1, -1 do
local element = self._all[i]
if element.enabled then
if element.proximity_raw == 0 then
if element:trigger(name, ...) == 'stop_propagation' then break end
Expand All @@ -119,7 +122,23 @@ function Elements:proximity_trigger(name, ...)
end
end

-- Returns a property of an element with a passed `id` if it exists, with an optional fallback.
---@param id string
---@param prop string
---@param fallback any
function Elements:v(id, prop, fallback)
if self[id] and self[id].enabled and self[id][prop] ~= nil then return self[id][prop] end
return fallback
end

-- Calls a method on an element with passed `id` if it exists.
---@param id string
---@param method string
function Elements:maybe(id, method, ...)
if self[id] then return self[id]:maybe(method, ...) end
end

function Elements:has(id) return self[id] ~= nil end
function Elements:ipairs() return ipairs(self.itable) end
function Elements:ipairs() return ipairs(self._all) end

return Elements
11 changes: 7 additions & 4 deletions scripts/uosc/elements/Menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function Menu:new(data, callback, opts) return Class.new(self, data, callback, o
---@param callback MenuCallback
---@param opts? MenuOptions
function Menu:init(data, callback, opts)
Element.init(self, 'menu', {ignores_menu = true})
Element.init(self, 'menu', {ignores_menu = true, render_order = 1000})

-----@type fun()
self.callback = callback
Expand Down Expand Up @@ -130,15 +130,15 @@ function Menu:init(data, callback, opts)

self:tween_property('opacity', 0, 1)
self:enable_key_bindings()
Elements.curtain:register('menu')
Elements:maybe('curtain', 'register', 'menu')
if self.opts.on_open then self.opts.on_open() end
end

function Menu:destroy()
Element.destroy(self)
self:disable_key_bindings()
self.is_closed = true
if not self.is_being_replaced then Elements.curtain:unregister('menu') end
if not self.is_being_replaced then Elements:maybe('curtain', 'unregister', 'menu') end
if utils.shared_script_property_set then
utils.shared_script_property_set('uosc-menu-type', nil)
end
Expand Down Expand Up @@ -969,11 +969,14 @@ function Menu:disable_key_bindings()
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 not self.is_closing and not self.is_closed then fn(...) end
if self:is_alive() then fn(...) end
end
end

Expand Down
Loading