summaryrefslogtreecommitdiffstats
path: root/.config/mpv/scripts/uosc_shared/elements/Element.lua
diff options
context:
space:
mode:
Diffstat (limited to '.config/mpv/scripts/uosc_shared/elements/Element.lua')
-rw-r--r--.config/mpv/scripts/uosc_shared/elements/Element.lua149
1 files changed, 149 insertions, 0 deletions
diff --git a/.config/mpv/scripts/uosc_shared/elements/Element.lua b/.config/mpv/scripts/uosc_shared/elements/Element.lua
new file mode 100644
index 0000000..718782d
--- /dev/null
+++ b/.config/mpv/scripts/uosc_shared/elements/Element.lua
@@ -0,0 +1,149 @@
+---@alias ElementProps {enabled?: boolean; ax?: number; ay?: number; bx?: number; by?: number; ignores_menu?: boolean; anchor_id?: string;}
+
+-- Base class all elements inherit from.
+---@class Element : Class
+local Element = class()
+
+---@param id string
+---@param props? ElementProps
+function Element:init(id, props)
+ self.id = id
+ -- `false` means element won't be rendered, or receive events
+ self.enabled = true
+ -- Element coordinates
+ self.ax, self.ay, self.bx, self.by = 0, 0, 0, 0
+ -- Relative proximity from `0` - mouse outside `proximity_max` range, to `1` - mouse within `proximity_min` range.
+ self.proximity = 0
+ -- Raw proximity in pixels.
+ self.proximity_raw = infinity
+ ---@type number `0-1` factor to force min visibility. Used for toggling element's permanent visibility.
+ self.min_visibility = 0
+ ---@type number `0-1` factor to force a visibility value. Used for flashing, fading out, and other animations
+ self.forced_visibility = nil
+ ---@type boolean Render this element even when menu is open.
+ self.ignores_menu = false
+ ---@type nil|string ID of an element from which this one should inherit visibility.
+ self.anchor_id = nil
+
+ if props then table_assign(self, props) end
+
+ -- Flash timer
+ self._flash_out_timer = mp.add_timeout(options.flash_duration / 1000, function()
+ local getTo = function() return self.proximity end
+ self:tween_property('forced_visibility', 1, getTo, function()
+ self.forced_visibility = nil
+ end)
+ end)
+ self._flash_out_timer:kill()
+
+ Elements:add(self)
+end
+
+function Element:destroy()
+ self.destroyed = true
+ Elements:remove(self)
+end
+
+---@param ax number
+---@param ay number
+---@param bx number
+---@param by number
+function Element:set_coordinates(ax, ay, bx, by)
+ self.ax, self.ay, self.bx, self.by = ax, ay, bx, by
+ Elements:update_proximities()
+ self:maybe('on_coordinates')
+end
+
+function Element:update_proximity()
+ if cursor.hidden then
+ self.proximity_raw = infinity
+ self.proximity = 0
+ else
+ local range = options.proximity_out - options.proximity_in
+ self.proximity_raw = get_point_to_rectangle_proximity(cursor, self)
+ self.proximity = 1 - (clamp(0, self.proximity_raw - options.proximity_in, range) / range)
+ end
+end
+
+-- Decide elements visibility based on proximity and various other factors
+function Element:get_visibility()
+ -- Hide when menu is open, unless this is a menu
+ ---@diagnostic disable-next-line: undefined-global
+ if not self.ignores_menu and Menu and Menu:is_open() then return 0 end
+
+ -- Persistency
+ local persist = config[self.id .. '_persistency']
+ if persist and (
+ (persist.audio and state.is_audio)
+ or (persist.paused and state.pause)
+ or (persist.video and state.is_video)
+ or (persist.image and state.is_image)
+ or (persist.idle and state.is_idle)
+ ) then return 1 end
+
+ -- Forced visibility
+ if self.forced_visibility then return math.max(self.forced_visibility, self.min_visibility) end
+
+ -- Anchor inheritance
+ -- If anchor returns -1, it means all attached elements should force hide.
+ local anchor = self.anchor_id and Elements[self.anchor_id]
+ local anchor_visibility = anchor and anchor:get_visibility() or 0
+
+ return anchor_visibility == -1 and 0 or math.max(self.proximity, anchor_visibility, self.min_visibility)
+end
+
+-- Call method if it exists
+function Element:maybe(name, ...)
+ if self[name] then return self[name](self, ...) end
+end
+
+-- Attach a tweening animation to this element
+---@param from number
+---@param to number|fun():number
+---@param setter fun(value: number)
+---@param factor_or_callback? number|fun()
+---@param callback? fun() Called either on animation end, or when animation is killed.
+function Element:tween(from, to, setter, factor_or_callback, callback)
+ self:tween_stop()
+ self._kill_tween = self.enabled and tween(
+ from, to, setter, factor_or_callback,
+ function()
+ self._kill_tween = nil
+ if callback then callback() end
+ end
+ )
+end
+
+function Element:is_tweening() return self and self._kill_tween end
+function Element:tween_stop() self:maybe('_kill_tween') end
+
+-- Animate an element property between 2 values.
+---@param prop string
+---@param from number
+---@param to number|fun():number
+---@param factor_or_callback? number|fun()
+---@param callback? fun() Called either on animation end, or when animation is killed.
+function Element:tween_property(prop, from, to, factor_or_callback, callback)
+ self:tween(from, to, function(value) self[prop] = value end, factor_or_callback, callback)
+end
+
+---@param name string
+function Element:trigger(name, ...)
+ local result = self:maybe('on_' .. name, ...)
+ request_render()
+ return result
+end
+
+-- Briefly flashes the element for `options.flash_duration` milliseconds.
+-- Useful to visualize changes of volume and timeline when changed via hotkeys.
+function Element:flash()
+ if options.flash_duration > 0 and (self.proximity < 1 or self._flash_out_timer:is_enabled()) then
+ self:tween_stop()
+ self.forced_visibility = 1
+ request_render()
+ self._flash_out_timer:kill()
+ self._flash_out_timer:resume()
+ end
+end
+
+return Element