local Storage = require("modules/exp_util/storage") local ExpElement = require("modules/exp_gui/prototype") --- @alias Gui.VisibleCallback fun(player: LuaPlayer, element: LuaGuiElement): boolean --- @class Gui.player_elements --- @field top table --- @field left table --- @field relative table --- @type table | table<"_debug", boolean> local player_elements = {} Storage.register(player_elements, function(tbl) player_elements = tbl end) --- @class Gui local Gui = { define = ExpElement.new, from_argument = ExpElement.from_argument, from_name = ExpElement.from_name, no_return = ExpElement.no_return, top_elements = {}, --- @type table left_elements = {}, --- @type table relative_elements = {}, --- @type table } local mod_gui = require("mod-gui") Gui.get_top_flow = mod_gui.get_button_flow Gui.get_left_flow = mod_gui.get_frame_flow --- Set the gui to redraw all elements on join, helps with style debugging --- @param state boolean function Gui._debug(state) if state == nil then player_elements._debug = true else player_elements._debug = state end end --- Get a player from an element or gui event --- @param input LuaGuiElement | { player_index: uint } | { element: LuaGuiElement } --- @return LuaPlayer function Gui.get_player(input) if type(input) == "table" and not input.player_index then return assert(game.get_player(input.element.player_index)) end return assert(game.get_player(input.player_index)) end --- Toggle the enable state of an element --- @param element LuaGuiElement --- @param state boolean? --- @return boolean function Gui.toggle_enabled_state(element, state) if not element or not element.valid then return false end if state == nil then state = not element.enabled end element.enabled = state return state end --- Toggle the visibility of an element --- @param element LuaGuiElement --- @param state boolean? --- @return boolean function Gui.toggle_visible_state(element, state) if not element or not element.valid then return false end if state == nil then state = not element.visible end element.visible = state return state end --- Destroy an element if it exists and is valid --- @param element LuaGuiElement? function Gui.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 Gui.VisibleCallback | boolean | nil function Gui.add_top_element(define, visible) assert(Gui.top_elements[define.name] == nil, "Element is already added to the top flow") Gui.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 Gui.VisibleCallback | boolean | nil function Gui.add_left_element(define, visible) assert(Gui.left_elements[define.name] == nil, "Element is already added to the left flow") Gui.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 Gui.VisibleCallback | boolean | nil function Gui.add_relative_element(define, visible) assert(Gui.relative_elements[define.name] == nil, "Element is already added to the relative flow") Gui.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 Gui.get_top_element(define, player) return assert(player_elements[player.index].top[define.name], "Element is not on the top flow") end --- Register a element define to be drawn to the left flow on join --- @param define ExpElement --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_left_element(define, player) return assert(player_elements[player.index].left[define.name], "Element is not on the left flow") end --- Register a element define to be drawn to the relative flow on join --- @param define ExpElement --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_relative_element(define, player) return assert(player_elements[player.index].relative[define.name], "Element is not on the relative flow") end --- Ensure all the correct elements are visible and exist --- @param player LuaPlayer --- @param element_defines table --- @param elements table --- @param parent LuaGuiElement local function ensure_elements(player, element_defines, elements, parent) local done = {} for define, visible in pairs(element_defines) do done[define.name] = true local element = elements[define.name] if not element or not element.valid then element = assert(define(parent), "Element define did not return an element: " .. define.name) elements[define.name] = element if type(visible) == "function" then visible = visible(player, element) end element.visible = visible end 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 Gui._ensure_consistency(event) local player = assert(game.get_player(event.player_index)) local elements = player_elements[event.player_index] or { top = {}, left = {}, relative = {}, } player_elements[event.player_index] = elements if player_elements._debug and event.name == defines.events.on_player_joined_game then log("Gui debug active, clearing gui for: " .. player.name) player.gui.relative.clear() player.gui.screen.clear() player.gui.center.clear() player.gui.left.clear() player.gui.top.clear() end ensure_elements(player, Gui.top_elements, elements.top, Gui.get_top_flow(player)) ensure_elements(player, Gui.left_elements, elements.left, Gui.get_left_flow(player)) ensure_elements(player, Gui.relative_elements, elements.relative, player.gui.relative) -- This check isn't needed, but allows the toolbar file to be deleted without modifying any lib code if Gui.toolbar then --- @diagnostic disable-next-line invisible Gui.toolbar._create_elements(player) --- @diagnostic disable-next-line invisible Gui.toolbar._ensure_consistency(player) end end --- Rerun the visible check for relative elements --- @param event EventData.on_gui_opened local function on_gui_opened(event) local player = Gui.get_player(event) local original_element = event.element for define, visible in pairs(Gui.relative_elements) do local element = Gui.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] = Gui._ensure_consistency, [e.on_player_joined_game] = Gui._ensure_consistency, [e.on_gui_opened] = on_gui_opened, } Gui.events = events return Gui