summaryrefslogtreecommitdiffstats
path: root/.config/mpv/scripts/uosc_shared/elements/Elements.lua
blob: fe2cd6b10905b195ff4d7b7285952aa2748e53b6 (plain)
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