1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
local Elements = {itable = {}}
---@param element Element
function Elements:add(element)
if not element.id then
msg.error('attempt to add element without "id" property')
return
end
if self:has(element.id) then Elements:remove(element.id) end
self.itable[#self.itable + 1] = element
self[element.id] = element
request_render()
end
function Elements:remove(idOrElement)
if not idOrElement then return end
local id = type(idOrElement) == 'table' and idOrElement.id or idOrElement
local element = Elements[id]
if element then
if not element.destroyed then element:destroy() end
element.enabled = false
self.itable = itable_remove(self.itable, self[id])
self[id] = nil
request_render()
end
end
function Elements:update_proximities()
local capture_mbtn_left = false
local capture_wheel = false
local menu_only = Elements.menu ~= nil
local mouse_leave_elements = {}
local mouse_enter_elements = {}
-- Calculates proximities and opacities for defined elements
for _, element in self:ipairs() do
if element.enabled then
local previous_proximity_raw = element.proximity_raw
-- If menu is open, all other elements have to be disabled
if menu_only then
if element.ignores_menu then
capture_mbtn_left = true
capture_wheel = true
element:update_proximity()
else
element.proximity_raw = infinity
element.proximity = 0
end
else
element:update_proximity()
end
-- Element has global forced key listeners
if element.on_global_mbtn_left_down then capture_mbtn_left = true end
if element.on_global_wheel_up or element.on_global_wheel_down then capture_wheel = true end
if element.proximity_raw == 0 then
-- Element has local forced key listeners
if element.on_mbtn_left_down then capture_mbtn_left = true end
if element.on_wheel_up or element.on_wheel_up then capture_wheel = true end
-- Mouse entered element area
if previous_proximity_raw ~= 0 then
mouse_enter_elements[#mouse_enter_elements + 1] = element
end
else
-- Mouse left element area
if previous_proximity_raw == 0 then
mouse_leave_elements[#mouse_leave_elements + 1] = element
end
end
end
end
-- Enable key group captures requested by elements
mp[capture_mbtn_left and 'enable_key_bindings' or 'disable_key_bindings']('mbtn_left')
mp[capture_wheel and 'enable_key_bindings' or 'disable_key_bindings']('wheel')
-- Trigger `mouse_leave` and `mouse_enter` events
for _, element in ipairs(mouse_leave_elements) do element:trigger('mouse_leave') end
for _, element in ipairs(mouse_enter_elements) do element:trigger('mouse_enter') end
end
-- Toggles passed elements' min visibilities between 0 and 1.
---@param ids string[] IDs of elements to peek.
function Elements:toggle(ids)
local has_invisible = itable_find(ids, function(id) return Elements[id] and Elements[id].min_visibility ~= 1 end)
self:set_min_visibility(has_invisible and 1 or 0, ids)
end
-- Set (animate) elements' min visibilities to passed value.
---@param visibility number 0-1 floating point.
---@param ids string[] IDs of elements to peek.
function Elements:set_min_visibility(visibility, ids)
for _, id in ipairs(ids) do
local element = Elements[id]
if element then element:tween_property('min_visibility', element.min_visibility, visibility) end
end
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)
for _, element in ipairs(elements) do element:flash() end
end
---@param name string Event name.
function Elements:trigger(name, ...)
for _, element in self:ipairs() do element:trigger(name, ...) end
end
-- Trigger two events, `name` and `global_name`, depending on element-cursor proximity.
-- Disabled elements don't receive these events.
---@param name string Event name.
function Elements:proximity_trigger(name, ...)
local stop_normal, stop_global = false, false
for i = #self.itable, 1, -1 do
local element = self.itable[i]
if element.enabled then
if element.proximity_raw == 0 then
if element:trigger(name, ...) == 'stop_propagation' then break end
end
if element:trigger('global_' .. name, ...) == 'stop_propagation' then break end
end
end
end
function Elements:has(id) return self[id] ~= nil end
function Elements:ipairs() return ipairs(self.itable) end
---@param name string Event name.
function Elements:create_proximity_dispatcher(name)
return function(...) self:proximity_trigger(name, ...) end
end
mp.set_key_bindings({
{
'mbtn_left',
Elements:create_proximity_dispatcher('mbtn_left_up'),
function(...)
update_mouse_pos(nil, mp.get_property_native('mouse-pos'), true)
Elements:proximity_trigger('mbtn_left_down', ...)
end,
},
{'mbtn_left_dbl', 'ignore'},
}, 'mbtn_left', 'force')
mp.set_key_bindings({
{'wheel_up', Elements:create_proximity_dispatcher('wheel_up')},
{'wheel_down', Elements:create_proximity_dispatcher('wheel_down')},
}, 'wheel', 'force')
return Elements
|