Element.lua 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. ---@alias ElementProps {enabled?: boolean; render_order?: number; ax?: number; ay?: number; bx?: number; by?: number; ignores_curtain?: boolean; anchor_id?: string;}
  2. -- Base class all elements inherit from.
  3. ---@class Element : Class
  4. local Element = class()
  5. ---@param id string
  6. ---@param props? ElementProps
  7. function Element:init(id, props)
  8. self.id = id
  9. self.render_order = 1
  10. -- `false` means element won't be rendered, or receive events
  11. self.enabled = true
  12. -- Element coordinates
  13. self.ax, self.ay, self.bx, self.by = 0, 0, 0, 0
  14. -- Relative proximity from `0` - mouse outside `proximity_max` range, to `1` - mouse within `proximity_min` range.
  15. self.proximity = 0
  16. -- Raw proximity in pixels.
  17. self.proximity_raw = math.huge
  18. ---@type number `0-1` factor to force min visibility. Used for toggling element's permanent visibility.
  19. self.min_visibility = 0
  20. ---@type number `0-1` factor to force a visibility value. Used for flashing, fading out, and other animations
  21. self.forced_visibility = nil
  22. ---@type boolean Show this element even when curtain is visible.
  23. self.ignores_curtain = false
  24. ---@type nil|string ID of an element from which this one should inherit visibility.
  25. self.anchor_id = nil
  26. ---@type fun()[] Disposer functions called when element is destroyed.
  27. self._disposers = {}
  28. if props then table_assign(self, props) end
  29. -- Flash timer
  30. self._flash_out_timer = mp.add_timeout(options.flash_duration / 1000, function()
  31. local function getTo() return self.proximity end
  32. local function onTweenEnd() self.forced_visibility = nil end
  33. if self.enabled then
  34. self:tween_property('forced_visibility', 1, getTo, onTweenEnd)
  35. else
  36. onTweenEnd()
  37. end
  38. end)
  39. self._flash_out_timer:kill()
  40. Elements:add(self)
  41. end
  42. function Element:destroy()
  43. for _, disposer in ipairs(self._disposers) do disposer() end
  44. self.destroyed = true
  45. Elements:remove(self)
  46. end
  47. function Element:reset_proximity() self.proximity, self.proximity_raw = 0, math.huge end
  48. ---@param ax number
  49. ---@param ay number
  50. ---@param bx number
  51. ---@param by number
  52. function Element:set_coordinates(ax, ay, bx, by)
  53. self.ax, self.ay, self.bx, self.by = ax, ay, bx, by
  54. Elements:update_proximities()
  55. self:maybe('on_coordinates')
  56. end
  57. function Element:update_proximity()
  58. if cursor.hidden then
  59. self:reset_proximity()
  60. else
  61. local range = options.proximity_out - options.proximity_in
  62. self.proximity_raw = get_point_to_rectangle_proximity(cursor, self)
  63. self.proximity = 1 - (clamp(0, self.proximity_raw - options.proximity_in, range) / range)
  64. end
  65. end
  66. function Element:is_persistent()
  67. local persist = config[self.id .. '_persistency']
  68. return persist and (
  69. (persist.audio and state.is_audio)
  70. or (
  71. persist.paused and state.pause
  72. and (not Elements.timeline or not Elements.timeline.pressed or Elements.timeline.pressed.pause)
  73. )
  74. or (persist.video and state.is_video)
  75. or (persist.image and state.is_image)
  76. or (persist.idle and state.is_idle)
  77. or (persist.windowed and not state.fullormaxed)
  78. or (persist.fullscreen and state.fullormaxed)
  79. )
  80. end
  81. -- Decide elements visibility based on proximity and various other factors
  82. function Element:get_visibility()
  83. -- Hide when curtain is visible, unless this elements ignores it
  84. local min_order = (Elements.curtain.opacity > 0 and not self.ignores_curtain) and Elements.curtain.render_order or 0
  85. if self.render_order < min_order then return 0 end
  86. -- Persistency
  87. if self:is_persistent() then return 1 end
  88. -- Forced visibility
  89. if self.forced_visibility then return math.max(self.forced_visibility, self.min_visibility) end
  90. -- Anchor inheritance
  91. -- If anchor returns -1, it means all attached elements should force hide.
  92. local anchor = self.anchor_id and Elements[self.anchor_id]
  93. local anchor_visibility = anchor and anchor:get_visibility() or 0
  94. return anchor_visibility == -1 and 0 or math.max(self.proximity, anchor_visibility, self.min_visibility)
  95. end
  96. -- Call method if it exists
  97. function Element:maybe(name, ...)
  98. if self[name] then return self[name](self, ...) end
  99. end
  100. -- Attach a tweening animation to this element
  101. ---@param from number
  102. ---@param to number|fun():number
  103. ---@param setter fun(value: number)
  104. ---@param duration_or_callback? number|fun() Duration in milliseconds or a callback function.
  105. ---@param callback? fun() Called either on animation end, or when animation is killed.
  106. function Element:tween(from, to, setter, duration_or_callback, callback)
  107. self:tween_stop()
  108. self._kill_tween = self.enabled and tween(
  109. from, to, setter, duration_or_callback,
  110. function()
  111. self._kill_tween = nil
  112. if callback then callback() end
  113. end
  114. )
  115. end
  116. function Element:is_tweening() return self and self._kill_tween end
  117. function Element:tween_stop() self:maybe('_kill_tween') end
  118. -- Animate an element property between 2 values.
  119. ---@param prop string
  120. ---@param from number
  121. ---@param to number|fun():number
  122. ---@param duration_or_callback? number|fun() Duration in milliseconds or a callback function.
  123. ---@param callback? fun() Called either on animation end, or when animation is killed.
  124. function Element:tween_property(prop, from, to, duration_or_callback, callback)
  125. self:tween(from, to, function(value) self[prop] = value end, duration_or_callback, callback)
  126. end
  127. ---@param name string
  128. function Element:trigger(name, ...)
  129. local result = self:maybe('on_' .. name, ...)
  130. request_render()
  131. return result
  132. end
  133. -- Briefly flashes the element for `options.flash_duration` milliseconds.
  134. -- Useful to visualize changes of volume and timeline when changed via hotkeys.
  135. function Element:flash()
  136. if self.enabled and options.flash_duration > 0 and (self.proximity < 1 or self._flash_out_timer:is_enabled()) then
  137. self:tween_stop()
  138. self.forced_visibility = 1
  139. request_render()
  140. self._flash_out_timer.timeout = options.flash_duration / 1000
  141. self._flash_out_timer:kill()
  142. self._flash_out_timer:resume()
  143. end
  144. end
  145. -- Register disposer to be called when element is destroyed.
  146. ---@param disposer fun()
  147. function Element:register_disposer(disposer)
  148. if not itable_index_of(self._disposers, disposer) then
  149. self._disposers[#self._disposers + 1] = disposer
  150. end
  151. end
  152. -- Automatically registers disposer for the passed callback.
  153. ---@param event string
  154. ---@param callback fun()
  155. function Element:register_mp_event(event, callback)
  156. mp.register_event(event, callback)
  157. self:register_disposer(function() mp.unregister_event(callback) end)
  158. end
  159. -- Automatically registers disposer for the observer.
  160. ---@param name string
  161. ---@param type_or_callback string|fun(name: string, value: any)
  162. ---@param callback_maybe nil|fun(name: string, value: any)
  163. function Element:observe_mp_property(name, type_or_callback, callback_maybe)
  164. local callback = type(type_or_callback) == 'function' and type_or_callback or callback_maybe
  165. local prop_type = type(type_or_callback) == 'string' and type_or_callback or 'native'
  166. mp.observe_property(name, prop_type, callback)
  167. self:register_disposer(function() mp.unobserve_property(callback) end)
  168. end
  169. return Element