2024-04-20 07:27:09 +00:00
|
|
|
local Elements = {_all = {}}
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
---@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
|
|
|
|
|
2024-04-20 07:27:09 +00:00
|
|
|
self._all[#self._all + 1] = element
|
2023-05-23 13:31:17 +00:00
|
|
|
self[element.id] = element
|
|
|
|
|
2024-04-20 07:27:09 +00:00
|
|
|
-- Sort by render order
|
|
|
|
table.sort(self._all, function(a, b) return a.render_order < b.render_order end)
|
|
|
|
|
2023-05-23 13:31:17 +00:00
|
|
|
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
|
2024-04-20 07:27:09 +00:00
|
|
|
self._all = itable_delete_value(self._all, self[id])
|
2023-05-23 13:31:17 +00:00
|
|
|
self[id] = nil
|
|
|
|
request_render()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function Elements:update_proximities()
|
2024-04-20 07:27:09 +00:00
|
|
|
local curtain_render_order = Elements.curtain.opacity > 0 and Elements.curtain.render_order or 0
|
2023-05-23 13:31:17 +00:00
|
|
|
local mouse_leave_elements = {}
|
|
|
|
local mouse_enter_elements = {}
|
|
|
|
|
2024-04-20 07:27:09 +00:00
|
|
|
-- Calculates proximities for all elements
|
2023-05-23 13:31:17 +00:00
|
|
|
for _, element in self:ipairs() do
|
|
|
|
if element.enabled then
|
|
|
|
local previous_proximity_raw = element.proximity_raw
|
|
|
|
|
2024-04-20 07:27:09 +00:00
|
|
|
-- If curtain is open, we disable all elements set to rendered below it
|
|
|
|
if not element.ignores_curtain and element.render_order < curtain_render_order then
|
|
|
|
element:reset_proximity()
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
|
|
|
element:update_proximity()
|
|
|
|
end
|
|
|
|
|
|
|
|
if element.proximity_raw == 0 then
|
|
|
|
-- 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
|
|
|
|
|
|
|
|
-- 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)
|
2024-04-20 07:27:09 +00:00
|
|
|
local has_invisible = itable_find(ids, function(id)
|
|
|
|
return Elements[id] and Elements[id].enabled and Elements[id]:get_visibility() ~= 1
|
|
|
|
end)
|
|
|
|
|
2023-05-23 13:31:17 +00:00
|
|
|
self:set_min_visibility(has_invisible and 1 or 0, ids)
|
2024-04-20 07:27:09 +00:00
|
|
|
|
2023-05-23 13:31:17 +00:00
|
|
|
-- Reset proximities when toggling off. Has to happen after `set_min_visibility`,
|
|
|
|
-- as that is using proximity as a tween starting point.
|
|
|
|
if not has_invisible then
|
|
|
|
for _, id in ipairs(ids) do
|
|
|
|
if Elements[id] then Elements[id]:reset_proximity() end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
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
|
|
|
|
local from = math.max(0, element:get_visibility())
|
|
|
|
element:tween_property('min_visibility', from, visibility)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Flash passed elements.
|
|
|
|
---@param ids string[] IDs of elements to peek.
|
|
|
|
function Elements:flash(ids)
|
2024-04-20 07:27:09 +00:00
|
|
|
local elements = itable_filter(self._all, function(element) return itable_has(ids, element.id) end)
|
2023-05-23 13:31:17 +00:00
|
|
|
for _, element in ipairs(elements) do element:flash() end
|
2024-04-20 07:27:09 +00:00
|
|
|
|
|
|
|
-- Special case for 'progress' since it's a state of timeline, not an element
|
|
|
|
if itable_has(ids, 'progress') and not itable_has(ids, 'timeline') then
|
|
|
|
Elements:maybe('timeline', 'flash_progress')
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
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, ...)
|
2024-04-20 07:27:09 +00:00
|
|
|
for i = #self._all, 1, -1 do
|
|
|
|
local element = self._all[i]
|
2023-05-23 13:31:17 +00:00
|
|
|
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
|
|
|
|
|
2024-04-20 07:27:09 +00:00
|
|
|
-- 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
|
|
|
|
|
2023-05-23 13:31:17 +00:00
|
|
|
function Elements:has(id) return self[id] ~= nil end
|
2024-04-20 07:27:09 +00:00
|
|
|
function Elements:ipairs() return ipairs(self._all) end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
return Elements
|