diff --git a/exp_gui/module/module.json b/exp_gui/module/module.json index c5aaae1d..1fdadc0d 100644 --- a/exp_gui/module/module.json +++ b/exp_gui/module/module.json @@ -1,6 +1,9 @@ { "name": "exp_gui", "load": [ + "data.lua", + "iter.lua", + "prototype.lua", "module_exports.lua" ], "require": [ diff --git a/exp_gui/module/module_exports.lua b/exp_gui/module/module_exports.lua index e69de29b..47f3377d 100644 --- a/exp_gui/module/module_exports.lua +++ b/exp_gui/module/module_exports.lua @@ -0,0 +1,186 @@ + +local Storage = require("modules/exp_util/storage") + +local ExpElement = require("./prototype") + +--- @alias ExpGui.VisibleCallback fun(player: LuaPlayer, element: LuaGuiElement): boolean + +--- @class ExpGui.player_elements +--- @field top table +--- @field left table +--- @field relative table + +--- @type table +local player_elements = {} +Storage.register(player_elements, function(tbl) + player_elements = tbl +end) + +--- @class ExpGui +local ExpGui = { + element = ExpElement, + top_elements = {}, --- @type table + left_elements = {}, --- @type table + relative_elements = {}, --- @type table +} + +local mod_gui = require("mod-gui") +ExpGui.get_top_flow = mod_gui.get_button_flow +ExpGui.get_left_flow = mod_gui.get_frame_flow + +--- Get a player from an element or gui event +--- @param input LuaGuiElement | { player_index: uint } +--- @return LuaPlayer +function ExpGui.get_player(input) + return assert(game.get_player(input.player_index)) +end + +--- Toggle the enable state of an element +--- @param element LuaGuiElement +--- @param state boolean? +function ExpGui.toggle_enabled_state(element, state) + if not element or not element.valid then return end + if state == nil then + state = not element.enabled + end + element.enabled = state +end + +--- Toggle the visibility of an element +--- @param element LuaGuiElement +--- @param state boolean? +function ExpGui.toggle_visible_state(element, state) + if not element or not element.valid then return end + if state == nil then + state = not element.visible + end + element.visible = state +end + +--- Destroy an element if it exists and is valid +--- @param element LuaGuiElement? +function ExpGui.destroy_if_valid(element) + if not element or not element.valid then return end + element.destroy() +end + +--- Register a element define to be drawn to the top flow on join +--- @param define ExpElement +--- @param visible ExpGui.VisibleCallback | boolean | nil +function ExpGui.add_top_element(define, visible) + assert(ExpGui.top_elements[define.name] == nil, "Element is already added to the top flow") + ExpGui.top_elements[define] = visible or false +end + +--- Register a element define to be drawn to the left flow on join +--- @param define ExpElement +--- @param visible ExpGui.VisibleCallback | boolean | nil +function ExpGui.add_left_element(define, visible) + assert(ExpGui.left_elements[define.name] == nil, "Element is already added to the left flow") + ExpGui.left_elements[define] = visible or false + +end + +--- Register a element define to be drawn to the relative flow on join +--- @param define ExpElement +--- @param visible ExpGui.VisibleCallback | boolean | nil +function ExpGui.add_relative_element(define, visible) + assert(ExpGui.relative_elements[define.name] == nil, "Element is already added to the relative flow") + ExpGui.relative_elements[define] = visible or false +end + +--- Register a element define to be drawn to the top flow on join +--- @param define ExpElement +--- @param player LuaPlayer +--- @return LuaGuiElement +function ExpGui.get_top_element(define, player) + return player_elements[player.index].top[define.name] +end + +--- Register a element define to be drawn to the left flow on join +--- @param define ExpElement +--- @param player LuaPlayer +--- @return LuaGuiElement +function ExpGui.get_left_element(define, player) + return player_elements[player.index].left[define.name] +end + +--- Register a element define to be drawn to the relative flow on join +--- @param define ExpElement +--- @param player LuaPlayer +--- @return LuaGuiElement +function ExpGui.get_relative_element(define, player) + return player_elements[player.index].relative[define.name] +end + +--- Ensure all the correct elements are visible and exist +--- @param player LuaPlayer +--- @param element_defines table +--- @param elements LuaGuiElement[] +--- @param parent LuaGuiElement +local function ensure_elements(player, element_defines, elements, parent) + local done = {} + for define, visible in pairs(element_defines) do + local element = elements[define.name] + if not element then + element = define(parent) + end + + if type(visible) == "function" then + visible = visible(player, element) + end + element.visible = visible + done[define.name] = true + end + + for name, element in pairs(elements) do + if not done[name] then + element.destroy() + elements[name] = nil + end + end +end + +--- Ensure all elements have been created +--- @param event EventData.on_player_created | EventData.on_player_joined_game +function ExpGui._ensure_elements(event) + local player = assert(game.get_player(event.player_index)) + local elements = player_elements[event.player_index] + ensure_elements(player, ExpGui.top_elements, elements.top, player.gui.top) + ensure_elements(player, ExpGui.left_elements, elements.left, player.gui.left) + ensure_elements(player, ExpGui.relative_elements, elements.relative, player.gui.relative) +end + +--- Rerun the visible check for relative elements +--- @param event EventData.on_gui_opened +local function on_gui_opened(event) + local player = ExpGui.get_player(event) + local original_element = event.element + + for define, visible in pairs(ExpGui.relative_elements) do + local element = ExpGui.get_relative_element(define, player) + + if type(visible) == "function" then + visible = visible(player, element) + end + element.visible = visible + + if visible then + event.element = element + --- @diagnostic disable-next-line invisible + define:_raise_event(event) + end + end + + event.element = original_element +end + +local e = defines.events +local events = { + [e.on_player_created] = ExpGui._ensure_elements, + [e.on_player_joined_game] = ExpGui._ensure_elements, + [e.on_gui_opened] = on_gui_opened, +} + +ExpGui.events = events +return ExpGui diff --git a/exp_gui/module/prototype.lua b/exp_gui/module/prototype.lua index 861cdf09..9698aaa0 100644 --- a/exp_gui/module/prototype.lua +++ b/exp_gui/module/prototype.lua @@ -27,7 +27,6 @@ local ExpElement = { --- @class ExpElement --- @field name string ---- @field scope string --- @field data ExpElement.data --- @field anchor ExpElement.anchor? --- @field _debug ExpElement._debug @@ -37,6 +36,7 @@ local ExpElement = { --- @field _player_data ExpElement.DataCallback? --- @field _force_data ExpElement.DataCallback? --- @field _events table +--- @overload fun(parent: LuaGuiElement, ...: any): LuaGuiElement ExpElement._prototype = { _track_elements = false, _has_handlers = false, @@ -63,11 +63,9 @@ ExpElement._anchor_metatable = { function ExpElement.create(name) ExpUtil.assert_not_runtime() assert(ExpElement._elements[name] == nil, "ExpElement already defined with name: " .. name) - local scope = ExpUtil.get_module_name(2) .. "::" .. name local instance = { name = name, - scope = scope, _events = {}, _debug = { defined_at = ExpUtil.safe_file_path(2), @@ -75,7 +73,7 @@ function ExpElement.create(name) } ExpElement._elements[name] = instance - instance.data = GuiData.create(scope, instance) + instance.data = GuiData.create(name, instance) instance.anchor = setmetatable({}, ExpElement._anchor_metatable) return setmetatable(instance, ExpElement._metatable) end @@ -220,14 +218,14 @@ end --- @param filter ExpGui_GuiIter.FilterType --- @return ExpGui_GuiIter.ReturnType function ExpElement._prototype:tracked_elements(filter) - return GuiIter.get_tracked_elements(self.scope, filter) + return GuiIter.get_tracked_elements(self.name, filter) end --- Iterate the tracked elements of all online players --- @param filter ExpGui_GuiIter.FilterType --- @return ExpGui_GuiIter.ReturnType function ExpElement._prototype:online_elements(filter) - return GuiIter.get_online_elements(self.scope, filter) + return GuiIter.get_online_elements(self.name, filter) end --- Track an arbitrary element, tracked elements can be iterated @@ -235,7 +233,7 @@ end --- @return LuaGuiElement --- @return function function ExpElement._prototype:track_element(element) - GuiIter.add_element(self.scope, element) + GuiIter.add_element(self.name, element) return element, ExpElement._prototype.track_element end @@ -244,7 +242,7 @@ end --- @return LuaGuiElement --- @return function function ExpElement._prototype:untrack_element(element) - GuiIter.remove_element(self.scope, element.player_index, element.index) + GuiIter.remove_element(self.name, element.player_index, element.index) return element, ExpElement._prototype.untrack_element end @@ -266,8 +264,8 @@ function ExpElement._prototype:link_element(element) end --- @cast event_tags string[] - if not table.array_contains(event_tags, self.scope) then - event_tags[#event_tags + 1] = self.scope + if not table.array_contains(event_tags, self.name) then + event_tags[#event_tags + 1] = self.name end element.tags = element_tags @@ -292,7 +290,7 @@ function ExpElement._prototype:unlink_element(element) end --- @cast event_tags string[] - table.remove_element(event_tags, self.scope) + table.remove_element(event_tags, self.name) element.tags = element_tags return element, ExpElement._prototype.unlink_element end