diff --git a/exp_gui/index.ts b/exp_gui/index.ts new file mode 100644 index 00000000..eb9596f4 --- /dev/null +++ b/exp_gui/index.ts @@ -0,0 +1,8 @@ +import * as lib from "@clusterio/lib"; + +export const plugin: lib.PluginDeclaration = { + name: "exp_gui", + title: "exp_gui", + description: "Example Description. Plugin. Change me in index.ts", + instanceEntrypoint: "./dist/node/instance", +}; diff --git a/exp_gui/instance.ts b/exp_gui/instance.ts new file mode 100644 index 00000000..e8fa7eeb --- /dev/null +++ b/exp_gui/instance.ts @@ -0,0 +1,7 @@ +import * as lib from "@clusterio/lib"; +import { BaseInstancePlugin } from "@clusterio/host"; + +export class InstancePlugin extends BaseInstancePlugin { + // This class is empty because an instance plugin must be defined for a module to be injected + // This requirement may change in the future to allow for standalone modules +} diff --git a/exp_gui/module/control.lua b/exp_gui/module/control.lua new file mode 100644 index 00000000..8ebb048c --- /dev/null +++ b/exp_gui/module/control.lua @@ -0,0 +1,213 @@ + +local Storage = require("modules/exp_util/storage") + +local ExpElement = require("modules/exp_gui/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.create, + property_from_arg = ExpElement.property_from_arg, + property_from_name = ExpElement.property_from_name, + 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 } | { element: LuaGuiElement } +--- @return LuaPlayer +function ExpGui.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 ExpGui.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 ExpGui.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 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 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 ExpGui.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 ExpGui.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 ExpGui._ensure_consistency(event) + local player = assert(game.get_player(event.player_index)) + local elements = player_elements[event.player_index] + if not elements then + elements = { + top = {}, + left = {}, + relative = {}, + } + player_elements[event.player_index] = elements + end + + ensure_elements(player, ExpGui.top_elements, elements.top, ExpGui.get_top_flow(player)) + ensure_elements(player, ExpGui.left_elements, elements.left, ExpGui.get_left_flow(player)) + ensure_elements(player, ExpGui.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 ExpGui.toolbar then + --- @diagnostic disable-next-line invisible + ExpGui.toolbar._create_elements(player) + --- @diagnostic disable-next-line invisible + ExpGui.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 = 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_consistency, + [e.on_player_joined_game] = ExpGui._ensure_consistency, + [e.on_gui_opened] = on_gui_opened, +} + +ExpGui.events = events +return ExpGui diff --git a/exp_gui/module/data.lua b/exp_gui/module/data.lua new file mode 100644 index 00000000..b5ceac5c --- /dev/null +++ b/exp_gui/module/data.lua @@ -0,0 +1,208 @@ +--[[-- ExpGui - GuiData +Provides a method of storing data for elements, players, and forces under a given scope. +This is not limited to GUI element definitions but this is the most common use case. +]] + +local ExpUtil = require("modules/exp_util") +local Storage = require("modules/exp_util/storage") + +--- @type table +local scope_data = {} + +--- @type table +local registered_scopes = {} + +--- @type table Reg -> Player Index +local registration_numbers = {} +local reg_obj = script.register_on_object_destroyed + +--- @alias DataKey LuaGuiElement | LuaPlayer | LuaForce +--- @alias DataKeys "element_data" | "player_data" | "force_data" | "global_data" +local DataKeys = ExpUtil.enum{ "element_data", "player_data", "force_data", "global_data" } +local DataKeysConcat = table.concat(DataKeys, ", ") + +Storage.register({ + scope_data = scope_data, + registration_numbers = registration_numbers, +}, function(tbl) + registration_numbers = tbl.registration_numbers + for scope, data in pairs(tbl.scope_data) do + local proxy = registered_scopes[scope] + if proxy then + rawset(proxy, "_raw", data) + for k, v in pairs(data) do + rawset(proxy, k, v) + end + end + end +end) + +--- @class ExpGui_GuiData +local GuiData = { + _scopes = registered_scopes, +} + +--- @class ExpGui.GuiData_Raw +--- @field element_data table?>? +--- @field player_data table? +--- @field force_data table? +--- @field global_data table? +-- This class has no prototype methods +-- Keep this in sync with DataKeys to block arbitrary strings + +--- @class ExpGui.GuiData_Internal +--- @field _scope string +--- @field _raw ExpGui.GuiData_Raw +-- This class has no prototype methods +-- Do add keys to _raw without also referencing scope_data + +--- @class ExpGui.GuiData: ExpGui.GuiData_Internal +--- @field element_data table> +--- @field player_data table +--- @field force_data table +--- @field global_data table +-- This class has no prototype methods +-- Same as raw but __index ensures the values exist + +GuiData._metatable = { + __class = "GuiData", +} + +--- Return the index for a given key +--- @param self ExpGui.GuiData_Internal +--- @param key DataKeys | DataKey +--- @return any +function GuiData._metatable.__index(self, key) + if type(key) == "string" then + -- This is only called when the key does not exist, ie it is not in storage + -- Once created, these tables are never removed as they are likely to be used again + assert(DataKeys[key], "Valid keys are: " .. DataKeysConcat) + local value = {} + self._raw[key] = value + rawset(self, key, value) + scope_data[self._scope] = self._raw + return value + end + + -- Check a given child table based on the object type + assert(type(key) == "userdata", "Index type '" .. ExpUtil.get_class_name(key) .. "' given to GuiData. Must be of type userdata.") + local object_name = key.object_name + if object_name == "LuaGuiElement" then + local data = self._raw.element_data + local player_elements = data and data[key.player_index] + return player_elements and player_elements[key.index] + elseif object_name == "LuaPlayer" then + local data = self._raw.player_data + return data and data[key.index] + elseif object_name == "LuaForce" then + local data = self._raw.force_data + return data and data[key.index] + else + error("Unsupported object class '" .. object_name .. "' given as index to GuiData.") + end +end + +--- Set the value index of a given key +-- Internal type is not used here to allow for creation of storage +--- @param self ExpGui.GuiData +--- @param key DataKey +--- @param value unknown +function GuiData._metatable.__newindex(self, key, value) + assert(type(key) == "userdata", "Index type '" .. ExpUtil.get_class_name(key) .. "' given to GuiData. Must be of type userdata.") + local object_name = key.object_name + if object_name == "LuaGuiElement" then + local data = self.element_data + local player_elements = data[key.player_index] + if not player_elements then + player_elements = {} + data[key.player_index] = player_elements + end + player_elements[key.index] = value + registration_numbers[reg_obj(key)] = key.player_index + elseif object_name == "LuaPlayer" then + self.player_data[key.index] = value + elseif object_name == "LuaForce" then + self.force_data[key.index] = value + else + error("Unsupported object class '" .. object_name .. "' given as index to GuiData.") + end +end + +--- Create the data object for a given scope +--- @param scope string +--- @return ExpGui.GuiData +function GuiData.create(scope) + ExpUtil.assert_not_runtime() + assert(GuiData._scopes[scope] == nil, "Scope already exists with name: " .. scope) + + local instance = { + _scope = scope, + _raw = {}, + } + + GuiData._scopes[scope] = instance + --- @cast instance ExpGui.GuiData + return setmetatable(instance, GuiData._metatable) +end + +--- Get the link to an existing data scope +--- @param scope string +--- @return ExpGui.GuiData +function GuiData.get(scope) + return GuiData._scopes[scope] --[[ @as ExpGui.GuiData ]] +end + +--- Used to clean up data from destroyed elements +--- @param event EventData.on_object_destroyed +local function on_object_destroyed(event) + local player_index = registration_numbers[event.registration_number] + if not player_index then return end + + local element_index = event.useful_id + registration_numbers[event.registration_number] = nil + + for _, scope in pairs(registered_scopes) do + local data = scope._raw.element_data + local player_elements = data and data[player_index] + if player_elements then + player_elements[element_index] = nil + if not next(player_elements) then + data[player_index] = nil + end + end + end +end + +--- Used to clean up data from destroyed players +--- @param event EventData.on_player_removed +local function on_player_removed(event) + local player_index = event.player_index + for _, scope in pairs(registered_scopes) do + local data = scope._raw.player_data + if data then + data[player_index] = nil + end + end +end + +--- Used to clean up data from destroyed forces +--- @param event EventData.on_forces_merged +local function on_forces_merged(event) + local force_index = event.source_index + for _, scope in pairs(registered_scopes) do + local data = scope._raw.force_data + if data then + data[force_index] = nil + end + end +end + +local e = defines.events +local events = { + [e.on_object_destroyed] = on_object_destroyed, + [e.on_player_removed] = on_player_removed, + [e.on_forces_merged] = on_forces_merged, +} + +GuiData.events = events +return GuiData diff --git a/exp_gui/module/elements.lua b/exp_gui/module/elements.lua new file mode 100644 index 00000000..6d2ef628 --- /dev/null +++ b/exp_gui/module/elements.lua @@ -0,0 +1,193 @@ +--- @class ExpGui +local ExpGui = require("modules/exp_gui") + +--- @class ExpGui.elements +local elements = {} +ExpGui.elements = elements + +--- A flow which aligns its content as specified +elements.aligned_flow = ExpGui.element("aligned_flow") + :draw{ + type = "flow", + name = ExpGui.property_from_arg("name"), + } + :style(function(def, element, parent, opts) + opts = opts or {} + local vertical_align = opts.vertical_align or "center" + local horizontal_align = opts.horizontal_align or "right" + return { + padding = { 1, 2 }, + vertical_align = vertical_align, + horizontal_align = horizontal_align, + vertically_stretchable = vertical_align ~= "center", + horizontally_stretchable = horizontal_align ~= "center", + } + end) + +--- A solid horizontal white bar element +elements.bar = ExpGui.element("bar") + :draw{ + type = "progressbar", + value = 1, + } + :style(function(def, element, parent, width) + local style = element.style + style.color = { r = 255, g = 255, b = 255 } + style.height = 4 + if width then + style.width = width + else + style.horizontally_stretchable = true + end + end) + +--- A label which is centered +elements.centered_label = ExpGui.element("centered_label") + :draw{ + type = "label", + caption = ExpGui.property_from_arg(2), + tooltip = ExpGui.property_from_arg(3), + } + :style{ + horizontal_align = "center", + single_line = false, + width = ExpGui.property_from_arg(1), + } + +--- A label which has two white bars on either side of it +elements.title_label = ExpGui.element("title_label") + :draw(function(def, parent, width, caption, tooltip) + local flow = + parent.add{ + type = "flow" + } + + flow.style.vertical_align = "center" + elements.bar(flow, width) + + local label = + flow.add{ + type = "label", + style = "frame_title", + caption = caption, + tooltip = tooltip, + } + + elements.bar(flow) + + return label + end) + +--- A fixed size vertical scroll pane +elements.scroll_pane = ExpGui.element("scroll_pane") + :draw{ + type = "scroll-pane", + name = ExpGui.property_from_arg(2), + direction = "vertical", + horizontal_scroll_policy = "never", + vertical_scroll_policy = "auto", + style = "scroll_pane_under_subheader", + } + :style{ + padding = { 1, 3 }, + maximal_height = ExpGui.property_from_arg(1), + horizontally_stretchable = true, + } + +--- A fixed size vertical scroll pane containing a table +elements.scroll_table = ExpGui.element("scroll_table") + :draw(function(def, parent, height, column_count, scroll_name) + local scroll_pane = elements.scroll_pane(parent, height, scroll_name) + + return scroll_pane.add{ + type = "table", + name = "table", + column_count = column_count, + } + end) + :style{ + padding = 0, + cell_padding = 0, + vertical_align = "center", + horizontally_stretchable = true, + } + +--- A container frame +elements.container = ExpGui.element("container") + :draw(function(def, parent, width, container_name) + local container = + parent.add{ + type = "frame", + name = container_name, + } + + local style = container.style + style.horizontally_stretchable = false + style.minimal_width = width + style.padding = 2 + + return container.add{ + type = "frame", + name = "frame", + direction = "vertical", + style = "inside_shallow_frame_packed", + } + end) + :style{ + vertically_stretchable = false, + } + +--- A frame within a container +elements.subframe_base = ExpGui.element("container_subframe") + :draw{ + type = "frame", + name = ExpGui.property_from_arg(2), + style = ExpGui.property_from_arg(1), + } + :style{ + height = 0, + minimal_height = 36, + padding = { 3, 4 }, + use_header_filler = false, + horizontally_stretchable = true, + } + +--- A header frame within a container +elements.header = ExpGui.element("container_header") + :draw(function(def, parent, opts) + opts = opts or {} + local subframe = elements.subframe_base(parent, "subheader_frame", opts.name) + + if opts.caption then + subframe.add{ + type = "label", + name = opts.label_name, + caption = opts.caption, + tooltip = opts.tooltip, + style = "frame_title", + } + end + + return opts.no_flow and subframe or elements.aligned_flow(subframe, { name = "flow" }) + end) + +--- A footer frame within a container +elements.footer = ExpGui.element("container_footer") + :draw(function(def, parent, opts) + opts = opts or {} + local subframe = elements.subframe_base(parent, "subfooter_frame", opts.name) + + if opts.caption then + subframe.add{ + type = "label", + name = opts.label_name, + caption = opts.caption, + tooltip = opts.tooltip, + style = "frame_title", + } + end + + return opts.no_flow and subframe or elements.aligned_flow(subframe, { name = "flow" }) + end) + +return elements diff --git a/exp_gui/module/iter.lua b/exp_gui/module/iter.lua new file mode 100644 index 00000000..450a7a52 --- /dev/null +++ b/exp_gui/module/iter.lua @@ -0,0 +1,274 @@ +--[[-- ExpGui - GuiData +Provides a method of storing elements created for a player and provide a global iterator for them. +]] + +local ExpUtil = require("modules/exp_util") +local Storage = require("modules/exp_util/storage") + +--- @alias ExpGui_GuiIter.FilterType LuaPlayer | LuaForce | LuaPlayer[] | nil +--- @alias ExpGui_GuiIter.ReturnType fun(): LuaPlayer?, LuaGuiElement? + +--- @type table>> +local registered_scopes = {} + +--- @type table Reg -> Player Index +local registration_numbers = {} +local reg_obj = script.register_on_object_destroyed + +Storage.register({ + registered_scopes = registered_scopes, + registration_numbers = registration_numbers, +}, function(tbl) + registered_scopes = tbl.registered_scopes + registration_numbers = tbl.registration_numbers +end) + +--- @class ExpGui_GuiIter +local GuiIter = { + _scopes = registered_scopes, +} + +local function nop() return nil, nil end + +--- Get the next valid element +--- @param elements table +--- @param prev_index uint? +--- @return uint?, LuaGuiElement? +local function next_valid_element(elements, prev_index) + local element_index, element = next(elements, prev_index) + while element and not element.valid do + elements[element_index] = nil + element_index, element = next(elements, element_index) + end + return element_index, element +end + +--- Get the next valid player with elements +--- @param scope_elements table> +--- @param players LuaPlayer[] +--- @param prev_index uint? +--- @param online boolean? +--- @return uint?, LuaPlayer?, table? +local function next_valid_player(scope_elements, players, prev_index, online) + local index, player = nil, nil + while true do + index, player = next(players, prev_index) + while player and not player.valid do + scope_elements[player.index] = nil + index, player = next(players, index) + end + + if index == nil then + return nil, nil, nil + end + + if online == nil or player.connected == online then + local player_elements = scope_elements[player.index] + if player_elements and #player_elements > 0 then + return index, player, player_elements + end + end + end +end + +--- Iterate over all valid elements for a player +--- @param scope string +--- @param player LuaPlayer +--- @return ExpGui_GuiIter.ReturnType +function GuiIter.player_elements(scope, player) + if not player.valid then return nop end + + local scope_elements = registered_scopes[scope] + if not scope_elements then return nop end + + local player_elements = scope_elements[player.index] + if not player_elements then return nop end + + local element_index, element = nil, nil + return function() + element_index, element = next_valid_element(player_elements, element_index) + if element_index == nil then return nil, nil end + return player, element + end +end + +--- Iterate over all valid elements for a player +--- @param scope string +--- @param players LuaPlayer[] +--- @param online boolean? +--- @return ExpGui_GuiIter.ReturnType +function GuiIter.filtered_elements(scope, players, online) + local scope_elements = registered_scopes[scope] + if not scope_elements then return nop end + + local index, player, player_elements = nil, nil, nil + local element_index, element = nil, nil + return function() + while true do + -- Get the next valid player elements if needed + if element_index == nil then + index, player, player_elements = next_valid_player(scope_elements, players, index, online) + if index == nil then return nil, nil end + --- @cast player_elements -nil + end + + -- Get the next element + element_index, element = next_valid_element(player_elements, element_index) + if element_index then + return player, element + end + end + end +end + +--- Iterate over all valid elements +--- @param scope string +--- @return ExpGui_GuiIter.ReturnType +function GuiIter.all_elements(scope) + local scope_elements = registered_scopes[scope] + if not scope_elements then return nop end + + local player_index, player_elements, player = nil, nil, nil + local element_index, element = nil, nil + return function() + while true do + if element_index == nil then + -- Get the next player + player_index, player_elements = next(scope_elements, player_index) + if player_index == nil then return nil, nil end + player = game.get_player(player_index) + + -- Ensure next player is valid + while player and not player.valid do + scope_elements[player_index] = nil + player_index, player_elements = next(scope_elements, player_index) + if player_index == nil then return nil, nil end + player = game.get_player(player_index) + end + --- @cast player_elements -nil + end + + -- Get the next element + element_index, element = next_valid_element(player_elements, element_index) + if element_index then + return player, element + end + end + end +end + +--- Iterate over all valid gui elements for all players +--- @param scope string +--- @param filter ExpGui_GuiIter.FilterType +--- @return ExpGui_GuiIter.ReturnType +function GuiIter.get_tracked_elements(scope, filter) + local class_name = ExpUtil.get_class_name(filter) + if class_name == "nil" then + --- @cast filter nil + return GuiIter.all_elements(scope) + elseif class_name == "LuaPlayer" then + --- @cast filter LuaPlayer + return GuiIter.player_elements(scope, filter) + elseif class_name == "LuaForce" then + --- @cast filter LuaForce + return GuiIter.filtered_elements(scope, filter.players) + elseif type(filter) == "table" and ExpUtil.get_class_name(filter[1]) == "LuaPlayer" then + --- @cast filter LuaPlayer[] + return GuiIter.filtered_elements(scope, filter) + else + error("Unknown filter type: " .. class_name) + end +end + +--- Iterate over all valid gui elements for all online players +--- @param scope string +--- @param filter ExpGui_GuiIter.FilterType +--- @return ExpGui_GuiIter.ReturnType +function GuiIter.get_online_elements(scope, filter) + local class_name = ExpUtil.get_class_name(filter) + if class_name == "nil" then + --- @cast filter nil + return GuiIter.filtered_elements(scope, game.connected_players) + elseif class_name == "LuaPlayer" then + --- @cast filter LuaPlayer + if not filter.connected then return nop end + return GuiIter.player_elements(scope, filter) + elseif class_name == "LuaForce" then + --- @cast filter LuaForce + return GuiIter.filtered_elements(scope, filter.connected_players) + elseif type(filter) == "table" and ExpUtil.get_class_name(filter[1]) == "LuaPlayer" then + --- @cast filter LuaPlayer[] + return GuiIter.filtered_elements(scope, filter, true) + else + error("Unknown filter type: " .. class_name) + end +end + +--- Add a new element to the global iter +--- @param scope string +--- @param element LuaGuiElement +function GuiIter.add_element(scope, element) + if not element.valid then return end + + local scope_elements = registered_scopes[scope] + if not scope_elements then + scope_elements = {} + registered_scopes[scope] = scope_elements + end + + local player_elements = scope_elements[element.player_index] + if not player_elements then + player_elements = {} + scope_elements[element.player_index] = player_elements + end + + player_elements[element.index] = element + registration_numbers[reg_obj(element)] = element.player_index +end + +--- Remove an element from the global iter +--- @param scope string +--- @param player_index uint +--- @param element_index uint +function GuiIter.remove_element(scope, player_index, element_index) + local scope_elements = registered_scopes[scope] + if not scope_elements then return end + local player_elements = registered_scopes[player_index] + if not player_elements then return end + player_elements[element_index] = nil +end + +--- Used to clean up data from destroyed elements +--- @param event EventData.on_object_destroyed +local function on_object_destroyed(event) + local player_index = registration_numbers[event.registration_number] + if not player_index then return end + + local element_index = event.useful_id + registration_numbers[event.registration_number] = nil + + for _, scope in pairs(registered_scopes) do + local player_elements = scope[player_index] + if player_elements then + player_elements[element_index] = nil + end + end +end + +--- Used to clean up data from destroyed players +--- @param event EventData.on_player_removed +local function on_player_removed(event) + local player_index = event.player_index + for _, scope in pairs(registered_scopes) do + scope[player_index] = nil + end +end + +local e = defines.events +local events = { + [e.on_object_destroyed] = on_object_destroyed, + [e.on_player_removed] = on_player_removed, +} + +GuiIter.events = events +return GuiIter diff --git a/exp_gui/module/locale/en.cfg b/exp_gui/module/locale/en.cfg new file mode 100644 index 00000000..931539da --- /dev/null +++ b/exp_gui/module/locale/en.cfg @@ -0,0 +1,13 @@ + +[exp-gui] +clear-left-flow=Hide all open windows. +close-toolbar=__CONTROL_LEFT_CLICK__: Toggle Settings\n__CONTROL_RIGHT_CLICK__: Close Toolbar +open-toolbar=__CONTROL_LEFT_CLICK__: Toggle Settings\n__CONTROL_RIGHT_CLICK__: Open Toolbar + +[exp-gui_toolbar-settings] +main-caption=Toolbox +main-tooltip=Toolbox Settings\nUse the checkboxes to select favourites +reset=Reset All +toggle=Toggle Favourites +move-up=Move Up +move-down=Move Down diff --git a/exp_gui/module/module.json b/exp_gui/module/module.json new file mode 100644 index 00000000..c30eede0 --- /dev/null +++ b/exp_gui/module/module.json @@ -0,0 +1,18 @@ +{ + "name": "exp_gui", + "load": [ + "data.lua", + "iter.lua", + "prototype.lua", + "control.lua" + ], + "require": [ + "elements.lua", + "styles.lua", + "toolbar.lua" + ], + "dependencies": { + "clusterio": "*", + "exp_util": "*" + } +} diff --git a/exp_gui/module/module_exports.lua b/exp_gui/module/module_exports.lua new file mode 100644 index 00000000..a8322b0e --- /dev/null +++ b/exp_gui/module/module_exports.lua @@ -0,0 +1 @@ +return require("modules/exp_gui/control") diff --git a/exp_gui/module/prototype.lua b/exp_gui/module/prototype.lua new file mode 100644 index 00000000..2c868619 --- /dev/null +++ b/exp_gui/module/prototype.lua @@ -0,0 +1,491 @@ + +local ExpUtil = require("modules/exp_util") + +local GuiData = require("modules/exp_gui/data") +local GuiIter = require("modules/exp_gui/iter") + +--- @class ExpGui_ExpElement +local ExpElement = { + _elements = {}, --- @type table +} + +ExpElement.events = {} + +--- @alias ExpElement.DrawCallback fun(def: ExpElement, parent: LuaGuiElement, ...): LuaGuiElement?, function? +--- @alias ExpElement.PostDrawCallback fun(def: ExpElement, element: LuaGuiElement?, parent: LuaGuiElement, ...): table? +--- @alias ExpElement.PostDrawCallbackAdder fun(self: ExpElement, definition: table | ExpElement.PostDrawCallback): ExpElement +--- @alias ExpElement.EventHandler fun(def: ExpElement, player: LuaPlayer, element: LuaGuiElement, event: E) +--- @alias ExpElement.OnEventAdder fun(self: ExpElement, handler: fun(def: ExpElement, player: LuaPlayer, element: LuaGuiElement, event: E)): ExpElement + +--- @class ExpElement._debug +--- @field defined_at string +--- @field draw_definition table? +--- @field draw_from_args table? +--- @field style_definition table? +--- @field style_from_args table? +--- @field element_data_definition table? +--- @field element_data_from_args table? +--- @field player_data_definition table? +--- @field player_data_from_args table? +--- @field force_data_definition table? +--- @field force_data_from_args table? +--- @field global_data_definition table? +--- @field global_data_from_args table? + +--- @class ExpElement +--- @field name string +--- @field data ExpGui.GuiData +--- @field _debug ExpElement._debug +--- @field _draw ExpElement.DrawCallback? +--- @field _style ExpElement.PostDrawCallback? +--- @field _element_data ExpElement.PostDrawCallback? +--- @field _player_data ExpElement.PostDrawCallback? +--- @field _force_data ExpElement.PostDrawCallback? +--- @field _global_data ExpElement.PostDrawCallback? +--- @field _events table[]> +--- @overload fun(parent: LuaGuiElement, ...: any): LuaGuiElement +ExpElement._prototype = { + _track_elements = false, + _has_handlers = false, +} + +ExpElement._metatable = { + __call = nil, -- ExpElement._prototype.create + __index = ExpElement._prototype, + __class = "ExpElement", +} + +--- Used to signal that the property should be the same as the define name +--- @return function +function ExpElement.property_from_name() + return ExpElement.property_from_name +end + +--- Used to signal that a property should be taken from the arguments, a string means key of last arg +--- @param arg number|string|nil +--- @return [function, number|string|nil] +function ExpElement.property_from_arg(arg) + return { ExpElement.property_from_arg, arg } +end + +--- Extract the from args properties from a definition +--- @param definition table +--- @return table +function ExpElement._prototype:_extract_signals(definition) + local from_args = {} + for k, v in pairs(definition) do + if v == ExpElement.property_from_arg then + from_args[#from_args + 1] = k + elseif v == ExpElement.property_from_name then + definition[k] = self.name + elseif type(v) == "table" and rawget(v, 1) == ExpElement.property_from_arg then + from_args[v[2] or (#from_args + 1)] = k + end + end + return from_args +end + +--- Register a new instance of a prototype +--- @param name string +--- @return ExpElement +function ExpElement.create(name) + ExpUtil.assert_not_runtime() + local module_name = ExpUtil.get_module_name(2) + local element_name = module_name .. "/" .. name + assert(ExpElement._elements[element_name] == nil, "ExpElement already defined with name: " .. name) + + local instance = { + name = element_name, + data = GuiData.create(element_name), + _events = {}, + _debug = { + defined_at = ExpUtil.get_current_line(2), + }, + } + + ExpElement._elements[element_name] = instance + return setmetatable(instance, ExpElement._metatable) +end + +--- Get an element by its name +--- @param name string +--- @return ExpElement +function ExpElement.get(name) + return assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name)) +end + +--- Create a new instance of this element definition +--- @param parent LuaGuiElement +--- @param ... any +--- @return LuaGuiElement? +function ExpElement._prototype:create(parent, ...) + assert(self._draw, "Element does not have a draw definition") + local element, status = self:_draw(parent, ...) + local player = assert(game.get_player(parent.player_index)) + + if self._style then + local style = self:_style(element, parent, ...) + if style then + assert(element, "Cannot set style when no element was returned by draw definition") + local element_style = element.style + for k, v in pairs(style) do + element_style[k] = v + end + end + end + + if self._element_data then + local data = self:_element_data(element, parent, ...) + if data then + assert(element, "Cannot set element data when no element was returned by draw definition") + self.data[element] = data + end + end + + if self._player_data then + local data = self:_player_data(element, parent, ...) + if data then + self.data[player] = data + end + end + + if self._force_data then + local data = self:_force_data(element, parent, ...) + if data then + self.data[player.force] = data + end + end + + if self._global_data then + local data = self:_global_data(element, parent, ...) + if data then + local global_data = self.data.global_data + for k, v in pairs(data) do + global_data[k] = v + end + end + end + + if not element then return end + + if self._track_elements and status ~= ExpElement._prototype.track_element and status ~= ExpElement._prototype.untrack_element then + self:track_element(element) + end + + if self._has_handlers and status ~= ExpElement._prototype.link_element and status ~= ExpElement._prototype.unlink_element then + self:link_element(element) + end + + return element +end + +--- Enable tracking of all created elements +--- @return ExpElement +function ExpElement._prototype:track_all_elements() + ExpUtil.assert_not_runtime() + self._track_elements = true + return self +end + +--- Add a temp draw definition, useful for when working top down +--- @return ExpElement +function ExpElement._prototype:dev() + log("Dev draw used for " .. self.name) + return self:draw{ + type = "flow" + } +end + +--- @alias ExpElement.add_param LuaGuiElement.add_param | table + +--- Set the draw definition +--- @param definition ExpElement.add_param | ExpElement.DrawCallback +--- @return ExpElement +function ExpElement._prototype:draw(definition) + ExpUtil.assert_not_runtime() + if type(definition) == "function" then + self._draw = definition + return self + end + + assert(type(definition) == "table", "Definition is not a table or function") + local from_args = self:_extract_signals(definition) + self._debug.draw_definition = definition + + if not next(from_args) then + self._draw = function(_, parent) + return parent.add(definition) + end + return self + end + + self._debug.draw_from_args = from_args + self._draw = function(_, parent, ...) + local args = { ... } + local last = args[#args] or args + -- 'or args' used instead of empty table + for i, k in pairs(from_args) do + if type(i) == "string" then + definition[k] = last[i] + else + definition[k] = args[i] + end + end + return parent.add(definition) + end + + return self +end + +--- Create a definition adder for anything other than draaw +--- @param prop_name string +--- @param debug_def string +--- @param debug_args string +--- @return ExpElement.PostDrawCallbackAdder +local function definition_factory(prop_name, debug_def, debug_args) + return function(self, definition) + ExpUtil.assert_not_runtime() + if type(definition) == "function" then + self[prop_name] = definition + return self + end + + assert(type(definition) == "table", "Definition is not a table or function") + local from_args = self:_extract_signals(definition) + self._debug[debug_def] = definition + + if #from_args == 0 then + self[prop_name] = function(_, _, _) + return definition + end + return self + end + + self._debug[debug_args] = from_args + self[prop_name] = function(_, _, _, ...) + local args = { ... } + for i, k in pairs(from_args) do + definition[k] = args[i] + end + return definition + end + + return self + end +end + +--- Set the style definition +--- @type ExpElement.PostDrawCallbackAdder +ExpElement._prototype.style = definition_factory("_style", "style_definition", "style_from_args") + +--- Set the default element data +--- @type ExpElement.PostDrawCallbackAdder +ExpElement._prototype.element_data = definition_factory("_element_data", "element_data_definition", "element_data_from_args") + +--- Set the default player data +--- @type ExpElement.PostDrawCallbackAdder +ExpElement._prototype.player_data = definition_factory("_player_data", "player_data_definition", "player_data_from_args") + +--- Set the default force data +--- @type ExpElement.PostDrawCallbackAdder +ExpElement._prototype.force_data = definition_factory("_force_data", "force_data_definition", "force_data_from_args") + +--- Set the default global data +--- @type ExpElement.PostDrawCallbackAdder +ExpElement._prototype.global_data = definition_factory("_global_data", "global_data_definition", "global_data_from_args") + +--- Iterate the tracked elements of all players +--- @param filter ExpGui_GuiIter.FilterType +--- @return ExpGui_GuiIter.ReturnType +function ExpElement._prototype:tracked_elements(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.name, filter) +end + +--- Track an arbitrary element, tracked elements can be iterated +--- @param element LuaGuiElement +--- @return LuaGuiElement +--- @return function +function ExpElement._prototype:track_element(element) + GuiIter.add_element(self.name, element) + return element, ExpElement._prototype.track_element +end + +--- Untrack an arbitrary element, untracked elements can't be iterated +--- @param element LuaGuiElement +--- @return LuaGuiElement +--- @return function +function ExpElement._prototype:untrack_element(element) + GuiIter.remove_element(self.name, element.player_index, element.index) + return element, ExpElement._prototype.untrack_element +end + +--- Link an arbitrary element, linked elements call event handlers +--- @param element LuaGuiElement +--- @return LuaGuiElement +--- @return function +function ExpElement._prototype:link_element(element) + assert(self._has_handlers, "Element has no event handlers") + local element_tags = element.tags + if not element_tags then + element_tags = {} + end + + local event_tags = element_tags["ExpGui"] + if not event_tags then + event_tags = {} + element_tags["ExpGui"] = event_tags + end + --- @cast event_tags string[] + + if not table.array_contains(event_tags, self.name) then + event_tags[#event_tags + 1] = self.name + end + + element.tags = element_tags + return element, ExpElement._prototype.link_element +end + +--- Unlink an arbitrary element, unlinked elements do not call event handlers +--- @param element LuaGuiElement +--- @return LuaGuiElement +--- @return function +function ExpElement._prototype:unlink_element(element) + assert(self._has_handlers, "Element has no event handlers") + local element_tags = element.tags + if not element_tags then + return element, ExpElement._prototype.unlink_element + end + + local event_tags = element_tags["ExpGui"] + if not event_tags then + event_tags = {} + element_tags["ExpGui"] = event_tags + end + --- @cast event_tags string[] + + table.remove_element(event_tags, self.name) + element.tags = element_tags + return element, ExpElement._prototype.unlink_element +end + +--- Handle any gui events +--- @param event EventData.on_gui_click +local function event_handler(event) + local element = event.element + if not element or not element.valid then return end + + local event_tags = element.tags and element.tags["ExpGui"] + if not event_tags then return end + --- @cast event_tags string[] + + for _, define_name in ipairs(event_tags) do + local define = ExpElement._elements[define_name] + if define then + define:raise_event(event) + end + end +end + +--- Raise all handlers for an event on this definition +--- @param event EventData | { element: LuaGuiElement, player_index: number? } +function ExpElement._prototype:raise_event(event) + local handlers = self._events[event.name] + if not handlers then return end + local player = event.player_index and game.get_player(event.player_index) + if event.element then player = game.get_player(event.element.player_index) end + for _, handler in ipairs(handlers) do + -- All gui elements will contain player and element, other events might have these as nil + -- Therefore only the signature of on_event has these values as optional + handler(self, player --[[ @as LuaPlayer ]], event.element, event --[[ @as EventData ]]) + end +end + +--- Add an event handler +--- @param event defines.events +--- @param handler fun(def: ExpElement, player: LuaPlayer?, element: LuaGuiElement?, event: EventData) +--- @return ExpElement +function ExpElement._prototype:on_event(event, handler) + ExpElement.events[event] = event_handler + self._has_handlers = true + + local handlers = self._events[event] or {} + handlers[#handlers + 1] = handler + self._events[event] = handlers + + return self +end + +--- Create a function to add event handlers to an element definition +--- @param event defines.events +--- @return ExpElement.OnEventAdder +local function event_factory(event) + return function(self, handler) + return self:on_event(event, handler) + end +end + +--- Called when LuaGuiElement checked state is changed (related to checkboxes and radio buttons). +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_checked_state_changed = event_factory(defines.events.on_gui_checked_state_changed) + +--- Called when LuaGuiElement is clicked. +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_click = event_factory(defines.events.on_gui_click) + +--- Called when the player closes the GUI they have open. +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_closed = event_factory(defines.events.on_gui_closed) + +--- Called when a LuaGuiElement is confirmed, for example by pressing Enter in a textfield. +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_confirmed = event_factory(defines.events.on_gui_confirmed) + +--- Called when LuaGuiElement element value is changed (related to choose element buttons). +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_elem_changed = event_factory(defines.events.on_gui_elem_changed) + +--- Called when LuaGuiElement is hovered by the mouse. +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_hover = event_factory(defines.events.on_gui_hover) + +--- Called when the player's cursor leaves a LuaGuiElement that was previously hovered. +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_leave = event_factory(defines.events.on_gui_leave) + +--- Called when LuaGuiElement element location is changed (related to frames in player.gui.screen). +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_location_changed = event_factory(defines.events.on_gui_location_changed) + +--- Called when the player opens a GUI. +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_opened = event_factory(defines.events.on_gui_opened) + +--- Called when LuaGuiElement selected tab is changed (related to tabbed-panes). +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_selected_tab_changed = event_factory(defines.events.on_gui_selected_tab_changed) + +--- Called when LuaGuiElement selection state is changed (related to drop-downs and listboxes). +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_selection_state_changed = event_factory(defines.events.on_gui_selection_state_changed) + +--- Called when LuaGuiElement switch state is changed (related to switches). +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_switch_state_changed = event_factory(defines.events.on_gui_switch_state_changed) + +--- Called when LuaGuiElement text is changed by the player. +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_text_changed = event_factory(defines.events.on_gui_text_changed) + +--- Called when LuaGuiElement slider value is changed (related to the slider element). +--- @type ExpElement.OnEventAdder +ExpElement._prototype.on_value_changed = event_factory(defines.events.on_gui_value_changed) + +ExpElement._metatable.__call = ExpElement._prototype.create +return ExpElement diff --git a/exp_gui/module/styles.lua b/exp_gui/module/styles.lua new file mode 100644 index 00000000..6d8fa31d --- /dev/null +++ b/exp_gui/module/styles.lua @@ -0,0 +1,16 @@ +--- @class ExpGui +local ExpGui = require("modules/exp_gui") + +--- @class ExpGui.styles +local styles = {} +ExpGui.styles = styles + +function styles.sprite(style) + style = style or {} + if not style.padding then + style.padding = -2 + end + return style +end + +return styles diff --git a/exp_gui/module/toolbar.lua b/exp_gui/module/toolbar.lua new file mode 100644 index 00000000..9ffb4595 --- /dev/null +++ b/exp_gui/module/toolbar.lua @@ -0,0 +1,739 @@ + +--- @class ExpGui +local ExpGui = require("modules/exp_gui") +local ExpElement = require("modules/exp_gui/prototype") +local mod_gui = require("mod-gui") + +local toolbar_button_default_style = mod_gui.button_style +local toolbar_button_active_style = "menu_button_continue" +local toolbar_button_size = 36 +local toolbar_button_small = 20 + +--- @class ExpGui.toolbar +local Toolbar = {} +ExpGui.toolbar = Toolbar + +local elements = {} +Toolbar.elements = elements + +local toolbar_buttons = {} --- @type ExpElement[] +local left_elements_with_button = {} --- @type table +local buttons_with_left_element = {} --- @type table + +--- Set the visible state of the toolbar +--- @param player LuaPlayer +--- @param state boolean? toggles if nil +--- @return boolean +function Toolbar.set_visible_state(player, state) + -- Update the top flow + local top_flow = assert(ExpGui.get_top_flow(player).parent) + if state == nil then state = not top_flow.visible end + top_flow.visible = state + + -- Update the open toolbar button + for _, open_toolbar in elements.open_toolbar:tracked_elements(player) do + open_toolbar.visible = not state + end + + -- Update the toggle toolbar button + for _, toggle_toolbar in elements.toggle_toolbar:tracked_elements(player) do + toggle_toolbar.toggled = state + end + + return state +end + +--- Get the visible state of the toolbar +--- @param player LuaPlayer +--- @return boolean +function Toolbar.get_visible_state(player) + local top_flow = assert(ExpGui.get_top_flow(player).parent) + return top_flow.visible +end + +--- Set the toggle state of a toolbar button, does not check for a linked left element +--- @param define ExpElement +--- @param player LuaPlayer +--- @param state boolean? toggles if nil +--- @return boolean +function Toolbar.set_button_toggled_state(define, player, state) + local top_element = assert(ExpGui.get_top_element(define, player), "Element is not on the top flow") + if state == nil then state = top_element.style.name == toolbar_button_default_style end + + for _, element in define:tracked_elements(player) do + local original_width, original_height = element.style.minimal_width, element.style.maximal_height + element.style = state and toolbar_button_active_style or toolbar_button_default_style + + -- Make the extra required adjustments + local style = element.style + style.minimal_width = original_width + style.maximal_height = original_height + if element.type == "sprite-button" then + style.padding = -2 + else + style.font = "default-semibold" + style.padding = 0 + end + end + + return state +end + +--- Get the toggle state of a toolbar button +--- @param define ExpElement +--- @param player LuaPlayer +--- @return boolean +function Toolbar.get_button_toggled_state(define, player) + local element = assert(ExpGui.get_top_element(define, player), "Element is not on the top flow") + return element.style.name == toolbar_button_active_style +end + +--- Set the visible state of a left element +--- @param define ExpElement +--- @param player LuaPlayer +--- @param state boolean? toggles if nil +--- @param _skip_consistency boolean? +--- @return boolean +function Toolbar.set_left_element_visible_state(define, player, state, _skip_consistency) + local element = assert(ExpGui.get_left_element(define, player), "Element is not on the left flow") + if state == nil then state = not element.visible end + element.visible = state + + -- Check if there is a linked toolbar button and update it + local button = left_elements_with_button[define] + if button then + Toolbar.set_button_toggled_state(button, player, state) + end + + -- This check is O(n^2) when setting all left elements to hidden, so internals can it + if _skip_consistency then return state end + + -- Update the clear left flow button, visible when at least one element is visible + local has_visible = Toolbar.has_visible_left_elements(player) + for _, clear_left_flow in elements.clear_left_flow:tracked_elements(player) do + clear_left_flow.visible = state or has_visible + end + + return state +end + +--- Get the toggle state of a left element +--- @param define ExpElement +--- @param player LuaPlayer +--- @return boolean +function Toolbar.get_left_element_visible_state(define, player) + local element = assert(ExpGui.get_left_element(define, player), "Element is not on the left flow") + return element.visible +end + +--- Check if there are any visible toolbar buttons +--- @param player any +--- @return boolean +function Toolbar.has_visible_buttons(player) + local top_flow = ExpGui.get_top_flow(player) + local settings_button = ExpGui.get_top_element(elements.close_toolbar, player) + + for _, element in pairs(top_flow.children) do + if element.visible and element ~= settings_button then + return true + end + end + + return false +end + +--- Check if there are any visible left elements +--- @param player any +--- @return boolean +function Toolbar.has_visible_left_elements(player) + local left_flow = ExpGui.get_left_flow(player) + local core_button_flow = ExpGui.get_left_element(elements.core_button_flow, player) + + for _, element in pairs(left_flow.children) do + if element.visible and element ~= core_button_flow then + return true + end + end + + return false +end + +--- @class ExpGui.toolbar.create_button__param: LuaGuiElement.add_param.sprite_button, LuaGuiElement.add_param.button +--- @field name string +--- @field type nil +--- @field left_element ExpElement| nil +--- @field visible ExpGui.VisibleCallback | boolean | nil + +--- Create a toolbar button +--- @param options ExpGui.toolbar.create_button__param +--- @return ExpElement +function Toolbar.create_button(options) + -- Extract the custom options from the element.add options + local name = assert(options.name, "Name is required for the element") + options.type = options.sprite ~= nil and "sprite-button" or "button" + + local visible = options.visible + if visible == nil then visible = true end + options.visible = nil + + local auto_toggle = options.auto_toggle + options.auto_toggle = nil + + local left_element = options.left_element + options.left_element = nil + + if options.style == nil then + options.style = toolbar_button_default_style + end + + -- Create the new element define + local toolbar_button = ExpGui.element(name) + :track_all_elements() + :draw(options) + :style{ + minimal_width = toolbar_button_size, + height = toolbar_button_size, + padding = options.sprite ~= nil and -2 or nil, + } + + -- If a left element was given then link the define + if left_element then + left_elements_with_button[left_element] = toolbar_button + buttons_with_left_element[toolbar_button.name] = left_element + end + + -- Setup auto toggle, required if there is a left element + if auto_toggle or left_element then + toolbar_button:on_click(function(def, player) + if left_element then + Toolbar.set_left_element_visible_state(left_element, player) + else + Toolbar.set_button_toggled_state(toolbar_button, player) + end + end) + end + + -- Add the define to the top flow and return + toolbar_buttons[#toolbar_buttons + 1] = toolbar_button + ExpGui.add_top_element(toolbar_button, visible) + return toolbar_button +end + +--- Toggles the toolbar settings, RMB will instead hide the toolbar +elements.close_toolbar = ExpGui.element("close_toolbar") + :draw{ + type = "sprite-button", + sprite = "utility/preset", + style = "tool_button", + tooltip = { "exp-gui.close-toolbar" }, + } + :style{ + padding = -2, + width = 18, + height = 36, + } + :on_click(function(def, player, element, event) + if event.button == defines.mouse_button_type.left then + Toolbar.set_left_element_visible_state(elements.toolbar_settings, player) + else + Toolbar.set_visible_state(player, false) + end + end) + +--- Shows the toolbar, if no buttons are visible then it shows the settings instead +elements.open_toolbar = ExpGui.element("open_toolbar") + :track_all_elements() + :draw{ + type = "sprite-button", + sprite = "utility/preset", + style = "tool_button", + tooltip = { "exp-gui.open-toolbar" }, + } + :style{ + padding = -2, + width = 18, + height = 20, + } + :on_click(function(def, player, element, event) + if event.button == defines.mouse_button_type.left then + Toolbar.set_left_element_visible_state(elements.toolbar_settings, player) + else + Toolbar.set_visible_state(player, true) + end + end) + +--- Hides all left elements when clicked +elements.clear_left_flow = ExpGui.element("clear_left_flow") + :track_all_elements() + :draw{ + type = "sprite-button", + sprite = "utility/close_black", + style = "tool_button", + tooltip = { "exp-gui.clear-left-flow" }, + } + :style{ + padding = -3, + width = 18, + height = 20, + } + :on_click(function(def, player, element) + element.visible = false + for define in pairs(ExpGui.left_elements) do + if define ~= elements.core_button_flow then + Toolbar.set_left_element_visible_state(define, player, false, true) + end + end + end) + +--- Contains the two buttons on the left flow +elements.core_button_flow = ExpGui.element("core_button_flow") + :draw(function(def, parent) + local flow = parent.add{ + type = "flow", + direction = "vertical", + } + + elements.open_toolbar(flow) + elements.clear_left_flow(flow) + + return flow + end) + +--[[ +Below here is the toolbar settings GUI and its associated functions +]] + +--- Set the style of the fake toolbar element +--- @param src LuaGuiElement +--- @param dst LuaGuiElement +local function copy_style(src, dst) + dst.style = src.style.name + dst.style.height = toolbar_button_small + dst.style.width = toolbar_button_small + dst.style.padding = -2 +end + +--- Reorder the buttons relative to each other, this will update the datastore +--- @param player LuaPlayer +--- @param item LuaGuiElement +--- @param offset number +local function move_toolbar_button(player, item, offset) + local old_index = item.get_index_in_parent() + local new_index = old_index + offset + + -- Swap the position in the list + local list = assert(item.parent) + local other_item = list.children[new_index] + list.swap_children(old_index, new_index) + + -- Swap the position in the top flow, offset by 1 because of settings button + local top_flow = ExpGui.get_top_flow(player) + top_flow.swap_children(old_index + 1, new_index + 1) + + -- Check if the element has a left element to move + local left_element = buttons_with_left_element[item.name] + local other_left_element = buttons_with_left_element[other_item.name] + if left_element and other_left_element then + local element = ExpGui.get_left_element(left_element, player) + local other_element = ExpGui.get_left_element(other_left_element, player) + local left_index = element.get_index_in_parent() + local other_index = other_element.get_index_in_parent() + element.parent.swap_children(left_index, other_index) + end + + -- If we are moving in/out of first/last place we need to update the move buttons + local last_index = #list.children + local item_data = elements.toolbar_list_item.data[item] + local other_item_data = elements.toolbar_list_item.data[other_item] + if old_index == 1 then -- Moving out of index 1 + item_data.move_item_up.enabled = true + other_item_data.move_item_up.enabled = false + elseif new_index == 1 then -- Moving into index 1 + item_data.move_item_up.enabled = false + other_item_data.move_item_up.enabled = true + elseif old_index == last_index then -- Moving out of the last index + item_data.move_item_down.enabled = true + other_item_data.move_item_down.enabled = false + elseif new_index == last_index then -- Moving into the last index + item_data.move_item_down.enabled = false + other_item_data.move_item_down.enabled = true + end +end + +--- @alias ExpGui.ToolbarOrder { name: string, favourite: boolean }[] + +--- Reorder the toolbar buttons +--- @param player LuaPlayer +--- @param order ExpGui.ToolbarOrder +function Toolbar.set_order(player, order) + local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]] + local left_flow = ExpGui.get_left_flow(player) + local top_flow = ExpGui.get_top_flow(player) + + -- Reorder the buttons + local left_index = 1 + local last_index = #order + for index, item_state in ipairs(order) do + -- Switch item order + local item = assert(list[item_state.name], "Missing toolbox item for " .. tostring(item_state.name)) + list.swap_children(index, item.get_index_in_parent()) + + -- Switch the toolbar button order + local element_define = ExpElement.get(item_state.name) + local toolbar_button = ExpGui.get_top_element(element_define, player) + top_flow.swap_children(index + 1, toolbar_button.get_index_in_parent()) + + -- Update the children buttons + local data = elements.toolbar_list_item.data[item] + data.set_favourite.state = item_state.favourite + data.move_item_up.enabled = index ~= 1 + data.move_item_down.enabled = index ~= last_index + + -- Switch the left element order + local left_define = buttons_with_left_element[item_state.name] + if left_define then + local left_element = ExpGui.get_left_element(left_define, player) + left_flow.swap_children(left_index, left_element.get_index_in_parent()) + left_index = left_index + 1 + end + end +end + +--- @class (exact) ExpGui.ToolbarState +--- @field order ExpGui.ToolbarOrder +--- @field open string[] +--- @field visible boolean + +--- Reorder the toolbar buttons and set the open state of the left flows +--- @param player LuaPlayer +--- @param state ExpGui.ToolbarState +function Toolbar.set_state(player, state) + Toolbar.set_order(player, state.order) + Toolbar.set_visible_state(player, state.visible) + + local done = {} + -- Make all open elements visible + for _, name in pairs(state.open) do + local left_element = ExpElement.get(name) + Toolbar.set_left_element_visible_state(left_element, player, true, true) + done[left_element] = true + end + + -- Make all other elements hidden + for left_element in pairs(ExpGui.left_elements) do + if not done[left_element] then + Toolbar.set_left_element_visible_state(left_element, player, false, true) + end + end + + -- Update clear_left_flow (because we skip above) + local has_visible = Toolbar.has_visible_left_elements(player) + for _, clear_left_flow in elements.clear_left_flow:tracked_elements(player) do + clear_left_flow.visible = has_visible + end +end + +--- Get the full toolbar state for a player +--- @param player LuaPlayer +--- @return ExpGui.ToolbarState +function Toolbar.get_state(player) + -- Get the order of toolbar buttons + local order = {} + local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]] + for index, item in pairs(list.children) do + order[index] = { name = item.name, favourite = elements.toolbar_list_item.data[item].set_favourite.state } + end + + -- Get the names of all open left elements + local open, open_index = {}, 1 + for left_element in pairs(ExpGui.left_elements) do + if Toolbar.get_left_element_visible_state(left_element, player) then + open[open_index] = left_element.name + open_index = open_index + 1 + end + end + + return { order = order, open = open, visible = Toolbar.get_visible_state(player) } +end + +--- Ensure the toolbar settings gui has all its elements +--- @param player LuaPlayer +function Toolbar._create_elements(player) + -- Add any missing items to the gui + local toolbar_list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]] + local previous_last_index = #toolbar_list.children_names + for define in pairs(ExpGui.top_elements) do + if define ~= elements.close_toolbar and toolbar_list[define.name] == nil then + local element = elements.toolbar_list_item(toolbar_list, define) + element.visible = ExpGui.get_top_element(define, player).visible + end + end + + -- Reset the state of the previous last child + local children = toolbar_list.children + if previous_last_index > 0 then + elements.toolbar_list_item.data[children[previous_last_index]].move_item_down.enabled = true + end + + -- Set the state of the move buttons for the first and last element + if #children > 0 then + elements.toolbar_list_item.data[children[1]].move_item_up.enabled = false + elements.toolbar_list_item.data[children[#children]].move_item_down.enabled = false + end +end + +--- Ensure all the toolbar buttons are in a consistent state +--- @param player LuaPlayer +function Toolbar._ensure_consistency(player) + -- Update the toolbar buttons + local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]] + for _, button in ipairs(toolbar_buttons) do + -- Update the visible state based on if the player is allowed the button + local element = ExpGui.get_top_element(button, player) + local allowed = ExpGui.top_elements[button] + if type(allowed) == "function" then + allowed = allowed(player, element) + end + element.visible = allowed and element.visible or false + list[button.name].visible = element.visible + + -- Update the toggle state and hide the linked left element if the button is not allowed + local left_define = buttons_with_left_element[button.name] + if left_define then + local left_element = ExpGui.get_left_element(left_define, player) + Toolbar.set_button_toggled_state(button, player, left_element.visible) + if not allowed then + Toolbar.set_left_element_visible_state(left_define, player, false) + end + end + end + + -- Update clear_left_flow + local has_visible = Toolbar.has_visible_left_elements(player) + for _, clear_left_flow in elements.clear_left_flow:tracked_elements(player) do + clear_left_flow.visible = has_visible + end + + -- Update open_toolbar + local top_flow = assert(ExpGui.get_top_flow(player).parent) + for _, open_toolbar in elements.open_toolbar:tracked_elements(player) do + open_toolbar.visible = not top_flow.visible + end + + -- Update toggle_toolbar + local has_buttons = Toolbar.has_visible_buttons(player) + for _, toggle_toolbar in elements.toggle_toolbar:tracked_elements(player) do + toggle_toolbar.enabled = has_buttons + end +end + +do + local default_order --- @type ExpGui.ToolbarOrder + --- Gets the default order for the toolbar + --- @return ExpGui.ToolbarOrder + function Toolbar.get_default_order() + if default_order then return default_order end + + local index = 1 + default_order = {} + for define in pairs(ExpGui.top_elements) do + if define ~= elements.close_toolbar then + default_order[index] = { name = define.name, favourite = true } + index = index + 1 + end + end + + return default_order + end +end + +--- Toggle the visibility of the toolbar, does not care if buttons are visible +elements.toggle_toolbar = ExpGui.element("toggle_toolbar") + :track_all_elements() + :draw{ + type = "sprite-button", + sprite = "utility/bookmark", + tooltip = { "exp-gui_toolbar-settings.toggle" }, + style = "tool_button", + auto_toggle = true, + } + :style(ExpGui.styles.sprite{ + size = 22, + }) + :on_click(function(def, player, element) + Toolbar.set_visible_state(player, element.toggled) + end) + +--- Reset the toolbar to its default state +elements.reset_toolbar = ExpGui.element("reset_toolbar") + :draw{ + type = "sprite-button", + sprite = "utility/reset", + style = "shortcut_bar_button_red", + tooltip = { "exp-gui_toolbar-settings.reset" }, + } + :style(ExpGui.styles.sprite{ + size = 22, + padding = -1, + }) + :on_click(function(def, player, element) + Toolbar.set_order(player, Toolbar.get_default_order()) + end) + +--- Move an item up/left on the toolbar +elements.move_item_up = ExpGui.element("move_item_up") + :draw{ + type = "sprite-button", + sprite = "utility/speed_up", + tooltip = { "exp-gui_toolbar-settings.move-up" }, + } + :style(ExpGui.styles.sprite{ + size = toolbar_button_small, + }) + :on_click(function(def, player, element) + local item = assert(element.parent.parent) + move_toolbar_button(player, item, -1) + end) + +--- Move an item down/right on the toolbar +elements.move_item_down = ExpGui.element("move_item_down") + :draw{ + type = "sprite-button", + sprite = "utility/speed_down", + tooltip = { "exp-gui_toolbar-settings.move-down" }, + } + :style(ExpGui.styles.sprite{ + size = toolbar_button_small, + }) + :on_click(function(def, player, element) + local item = assert(element.parent.parent) + move_toolbar_button(player, item, 1) + end) + +--- Set an item as a favourite, making it appear on the toolbar +elements.set_favourite = ExpGui.element("set_favourite") + :draw(function(def, parent, item_define) + --- @cast item_define ExpElement + local player = ExpGui.get_player(parent) + local top_element = ExpGui.get_top_element(item_define, player) + + return parent.add{ + type = "checkbox", + caption = top_element.tooltip or top_element.caption or nil, + state = top_element.visible or false, + tags = { + element_name = item_define.name, + }, + } + end) + :style{ + width = 180, + } + :on_checked_state_changed(function(def, player, element) + local define = ExpElement.get(element.tags.element_name --[[ @as string ]]) + local top_element = ExpGui.get_top_element(define, player) + local had_visible = Toolbar.has_visible_buttons(player) + top_element.visible = element.state + + -- Check if we are on the edge case between 0 and 1 visible elements + if element.state and not had_visible then + Toolbar.set_visible_state(player, true) + for _, toggle_toolbar in elements.toggle_toolbar:tracked_elements(player) do + toggle_toolbar.enabled = true + end + elseif not element.state and not Toolbar.has_visible_buttons(player) then + Toolbar.set_visible_state(player, false) + for _, toggle_toolbar in elements.toggle_toolbar:tracked_elements(player) do + toggle_toolbar.enabled = false + end + end + end) + +elements.toolbar_list_item = ExpGui.element("toolbar_list_item") + :draw(function(def, parent, item_define) + --- @cast item_define ExpElement + local data = {} + + -- Add the flow for the item + local flow = parent.add{ + name = item_define.name, + type = "frame", + style = "shortcut_selection_row", + } + flow.style.horizontally_stretchable = true + flow.style.vertical_align = "center" + + -- Add the button and the icon edit button + local element = item_define(flow) + local player = ExpGui.get_player(parent) + local top_element = ExpGui.get_top_element(item_define, player) + copy_style(top_element, element) + + -- Add the favourite checkbox and label + data.set_favourite = elements.set_favourite(flow, item_define) + + -- Add the buttons used to move the flow up and down + local move_flow = flow.add{ type = "flow", name = "move" } + move_flow.style.horizontal_spacing = 0 + data.move_item_up = elements.move_item_up(move_flow) + data.move_item_down = elements.move_item_down(move_flow) + + def.data[flow] = data + return flow + end) + +--- Main list for all toolbar items +elements.toolbar_list = ExpGui.element("toolbar_list") + :draw(function(def, parent) + local scroll = parent.add{ + type = "scroll-pane", + direction = "vertical", + horizontal_scroll_policy = "never", + vertical_scroll_policy = "auto", + style = "scroll_pane_under_subheader", + } + scroll.style.horizontally_stretchable = true + scroll.style.maximal_height = 224 + scroll.style.padding = 0 + + -- This is required because vertical_spacing can't be set on a scroll pane + return scroll.add{ + type = "flow", + direction = "vertical", + } + end) + :style{ + horizontally_stretchable = true, + vertical_spacing = 0, + } + +-- The main container for the toolbar settings +elements.toolbar_settings = ExpGui.element("toolbar_settings") + :draw(function(def, parent) + -- Draw the container + local frame = ExpGui.elements.container(parent, 268) + frame.style.maximal_width = 268 + frame.style.minimal_width = 268 + + -- Draw the header + local player = ExpGui.get_player(parent) + local header = ExpGui.elements.header(frame, { + caption = { "exp-gui_toolbar-settings.main-caption" }, + tooltip = { "exp-gui_toolbar-settings.main-tooltip" }, + }) + + -- Draw the toolbar control buttons + local toggle_element = elements.toggle_toolbar(header) + toggle_element.toggled = Toolbar.get_visible_state(player) + elements.reset_toolbar(header) + + def.data[player] = elements.toolbar_list(frame) + Toolbar._create_elements(player) + return frame.parent + end) + +ExpGui.add_left_element(elements.core_button_flow, true) +ExpGui.add_left_element(elements.toolbar_settings, false) +ExpGui.add_top_element(elements.close_toolbar, true) + +return Toolbar diff --git a/exp_gui/package.json b/exp_gui/package.json new file mode 100644 index 00000000..1bd59be4 --- /dev/null +++ b/exp_gui/package.json @@ -0,0 +1,35 @@ +{ + "name": "@expcluster/lib_gui", + "version": "0.1.0", + "description": "Clusterio plugin providing a Lua GUI definition library.", + "author": "Cooldude2606 ", + "license": "MIT", + "repository": "explosivegaming/ExpCluster", + "main": "dist/node/index.js", + "scripts": { + "prepare": "tsc --build" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@clusterio/lib": "^2.0.0-alpha.19" + }, + "devDependencies": { + "@clusterio/lib": "2.0.0-alpha.19", + "@types/node": "^20.14.9", + "typescript": "^5.5.3" + }, + "dependencies": { + "@expcluster/lib_util": "workspace:*", + "@sinclair/typebox": "^0.30.4" + }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "clusterio", + "clusterio-plugin", + "factorio" + ] +} diff --git a/exp_gui/tsconfig.json b/exp_gui/tsconfig.json new file mode 100644 index 00000000..a0473e75 --- /dev/null +++ b/exp_gui/tsconfig.json @@ -0,0 +1,6 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" } + ] +} diff --git a/exp_gui/tsconfig.node.json b/exp_gui/tsconfig.node.json new file mode 100644 index 00000000..3218f2e7 --- /dev/null +++ b/exp_gui/tsconfig.node.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.node.json", + "include": ["./**/*.ts"], + "exclude": ["test/*", "./dist/*"], +} diff --git a/exp_legacy/module/config/_file_loader.lua b/exp_legacy/module/config/_file_loader.lua index 3ab87539..70ecea6f 100644 --- a/exp_legacy/module/config/_file_loader.lua +++ b/exp_legacy/module/config/_file_loader.lua @@ -41,14 +41,15 @@ return { -- 'modules.data.bonus', "modules.data.personal-logistic", "modules.data.language", + --"modules.data.toolbar", --- GUI "modules.gui.readme", -- "modules.gui.rocket-info", -- "modules.gui.science-info", "modules.gui.autofill", - "modules.gui.warp-list", "modules.gui.task-list", + "modules.gui.warp-list", "modules.gui.player-list", "modules.gui.server-ups", "modules.gui.bonus", @@ -60,9 +61,9 @@ return { "modules.gui.production", "modules.gui.playerdata", "modules.gui.surveillance", - "modules.graftorio.require", -- graftorio - "modules.gui.toolbar", -- must be loaded last to register toolbar handlers + "modules.gui._role_updates", + "modules.graftorio.require", -- graftorio --- Config Files "config.expcore.permission_groups", -- loads some predefined permission groups "config.expcore.roles", -- loads some predefined roles diff --git a/exp_legacy/module/config/gui/player_list_actions.lua b/exp_legacy/module/config/gui/player_list_actions.lua index 962907a5..9dddc079 100644 --- a/exp_legacy/module/config/gui/player_list_actions.lua +++ b/exp_legacy/module/config/gui/player_list_actions.lua @@ -6,7 +6,7 @@ -- @config Player-List local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local Reports = require("modules.exp_legacy.modules.control.reports") --- @dep modules.control.reports local Warnings = require("modules.exp_legacy.modules.control.warnings") --- @dep modules.control.warnings @@ -47,22 +47,24 @@ local function teleport(from_player, to_player) end local function new_button(sprite, tooltip) - return Gui.element{ - type = "sprite-button", - style = "tool_button", - sprite = sprite, - tooltip = tooltip, - }:style{ - padding = -1, - height = 28, - width = 28, - } + return Gui.element(tooltip[1]) + :draw{ + type = "sprite-button", + style = "tool_button", + sprite = sprite, + tooltip = tooltip, + } + :style{ + padding = -1, + height = 28, + width = 28, + } end --- Teleports the user to the action player -- @element goto_player local goto_player = new_button("utility/export", { "player-list.goto-player" }) - :on_click(function(player) + :on_click(function(def, player, element) local selected_player_name = get_action_player_name(player) local selected_player = game.players[selected_player_name] if not player.character or not selected_player.character then @@ -75,7 +77,7 @@ local goto_player = new_button("utility/export", { "player-list.goto-player" }) --- Teleports the action player to the user -- @element bring_player local bring_player = new_button("utility/import", { "player-list.bring-player" }) - :on_click(function(player) + :on_click(function(def, player, element) local selected_player_name = get_action_player_name(player) local selected_player = game.players[selected_player_name] if not player.character or not selected_player.character then @@ -88,7 +90,7 @@ local bring_player = new_button("utility/import", { "player-list.bring-player" } --- Reports the action player, requires a reason to be given -- @element report_player local report_player = new_button("utility/spawn_flag", { "player-list.report-player" }) - :on_click(function(player) + :on_click(function(def, player, element) local selected_player_name = get_action_player_name(player) if Reports.is_reported(selected_player_name, player.name) then player.print({ "exp-commands_report.already-reported" }, Colors.orange_red) @@ -108,7 +110,7 @@ end --- Gives the action player a warning, requires a reason -- @element warn_player local warn_player = new_button("utility/spawn_flag", { "player-list.warn-player" }) - :on_click(function(player) + :on_click(function(def, player, element) SelectedAction:set(player, "command/give-warning") end) @@ -122,7 +124,7 @@ end --- Jails the action player, requires a reason -- @element jail_player local jail_player = new_button("utility/multiplayer_waiting_icon", { "player-list.jail-player" }) - :on_click(function(player) + :on_click(function(def, player, element) local selected_player_name, selected_player_color = get_action_player_name(player) if Jail.is_jailed(selected_player_name) then player.print({ "exp-commands_jail.already-jailed", selected_player_color }, Colors.orange_red) @@ -141,7 +143,7 @@ end --- Kicks the action player, requires a reason -- @element kick_player local kick_player = new_button("utility/warning_icon", { "player-list.kick-player" }) - :on_click(function(player) + :on_click(function(def, player, element) SelectedAction:set(player, "command/kick") end) @@ -153,7 +155,7 @@ end --- Bans the action player, requires a reason -- @element ban_player local ban_player = new_button("utility/danger_icon", { "player-list.ban-player" }) - :on_click(function(player) + :on_click(function(def, player, element) SelectedAction:set(player, "command/ban") end) diff --git a/exp_legacy/module/config/gui/science.lua b/exp_legacy/module/config/gui/science.lua index 572de9d2..b8128bbe 100644 --- a/exp_legacy/module/config/gui/science.lua +++ b/exp_legacy/module/config/gui/science.lua @@ -13,4 +13,9 @@ return { "production-science-pack", "utility-science-pack", "space-science-pack", + "metallurgic-science-pack", + "agricultural-science-pack", + "electromagnetic-science-pack", + "cryogenic-science-pack", + "promethium-science-pack", } diff --git a/exp_legacy/module/expcore/datastore.lua b/exp_legacy/module/expcore/datastore.lua index 9e97f23e..9d498787 100644 --- a/exp_legacy/module/expcore/datastore.lua +++ b/exp_legacy/module/expcore/datastore.lua @@ -164,6 +164,10 @@ Storage.register(Data, function(tbl) for name, datastore in pairs(Datastores) do datastore.data = Data[name] end +end, function(tbl) + for name in pairs(Datastores) do + tbl[name] = tbl[name] or {} + end end) ----- Datastore Manager diff --git a/exp_legacy/module/expcore/gui.lua b/exp_legacy/module/expcore/gui.lua deleted file mode 100644 index 86c4c457..00000000 --- a/exp_legacy/module/expcore/gui.lua +++ /dev/null @@ -1 +0,0 @@ -return require("modules.exp_legacy.expcore.gui._require") diff --git a/exp_legacy/module/expcore/gui/_require.lua b/exp_legacy/module/expcore/gui/_require.lua deleted file mode 100644 index 3bf672ea..00000000 --- a/exp_legacy/module/expcore/gui/_require.lua +++ /dev/null @@ -1,144 +0,0 @@ ---[[-- Core Module - Gui -- Used to simplify gui creation using factory functions called element defines -@core Gui -@alias Gui - -@usage-- To draw your element you only need to call the factory function --- You are able to pass any other arguments that are used in your custom functions but the first is always the parent element -local example_button_element = example_button(parent_element) - -@usage-- Making a factory function for a button with the caption "Example Button" --- This method has all the same features as LuaGuiElement.add -local example_button = -Gui.element{ - type = 'button', - caption = 'Example Button' -} - -@usage-- Making a factory function for a button which is contained within a flow --- This method is for when you still want to register event handlers but cant use the table method -local example_flow_with_button = -Gui.element(function(definition, parent, ...) - -- ... shows that all other arguments from the factory call are passed to this function - -- Here we are adding a flow which we will then later add a button to - local flow = - parent.add{ -- paraent is the element which is passed to the factory function - name = 'example_flow', - type = 'flow' - } - - -- Now we add the button to the flow that we created earlier - local element = definition:triggers_event( - flow.add{ - type = 'button', - caption = 'Example Button' - } - ) - - -- You must return a new element, this is so styles can be applied and returned to the caller - -- You may return any of your elements that you added, consider the context in which it will be used for which should be returned - return element -end) - -@usage-- Styles can be added to any element define, simplest way mimics LuaGuiElement.style[key] = value -local example_button = -Gui.element{ - type = 'button', - caption = 'Example Button', - style = 'forward_button' -- factorio styles can be applied here -} -:style{ - height = 25, -- same as element.style.height = 25 - width = 100 -- same as element.style.width = 25 -} - -@usage-- Styles can also have a custom function when the style is dynamic and depends on other factors --- Use this method if your style is dynamic and depends on other factors -local example_button = -Gui.element{ - type = 'button', - caption = 'Example Button', - style = 'forward_button' -- factorio styles can be applied here -} -:style(function(style, element, ...) - -- style is the current style object for the elemenent - -- element is the element that is being changed - -- ... shows that all other arguments from the factory call are passed to this function - local player = game.players[element.player_index] - style.height = 25 - style.width = 100 - style.font_color = player.color -end) - -@usage-- You are able to register event handlers to your elements, these can be factorio events or custom ones --- All events are checked to be valid before raising any handlers, this means element.valid = true and player.valid = true -Gui.element{ - type = 'button', - caption = 'Example Button' -} -:on_click(function(player, element, event) - -- player is the player who interacted with the element to cause the event - -- element is a refrence to the element which caused the event - -- event is a raw refrence to the event data if player and element are not enough - player.print('Clicked: '..element.name) -end) - -@usage-- Example from core_defines, Gui.core_defines.hide_left_flow, called like: hide_left_flow(parent_element) ---- Button which hides the elements in the left flow, shows inside the left flow when frames are visible --- @element hide_left_flow -local hide_left_flow = -Gui.element{ - type = 'sprite-button', - sprite = 'utility/close_black', - style = 'tool_button', - tooltip = {'expcore-gui.left-button-tooltip'} -} -:style{ - padding = -3, - width = 18, - height = 20 -} -:on_click(function(player, _,_) - Gui.hide_left_flow(player) -end) - -@usage-- Eample from defines, Gui.alignment, called like: Gui.alignment(parent, name, horizontal_align, vertical_align) --- Notice how _ are used to blank arguments that are not needed in that context and how they line up with above -Gui.alignment = -Gui.element(function(_, parent, name, _,_) - return parent.add{ - name = name or 'alignment', - type = 'flow', - } -end) -:style(function(style, _,_, horizontal_align, vertical_align) - style.padding = {1, 2} - style.vertical_align = vertical_align or 'center' - style.horizontal_align = horizontal_align or 'right' - style.vertically_stretchable = style.vertical_align ~= 'center' - style.horizontally_stretchable = style.horizontal_align ~= 'center' -end) - -]] - -local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui.prototype") -require("modules.exp_legacy.expcore.gui.helper_functions") -require("modules.exp_legacy.expcore.gui.core_defines") -require("modules.exp_legacy.expcore.gui.top_flow") -require("modules.exp_legacy.expcore.gui.left_flow") -require("modules.exp_legacy.expcore.gui.defines") - -local Roles = ExpUtil.optional_require("modules.exp_legacy.expcore.roles") -local Event = ExpUtil.optional_require("modules/exp_legacy/utils/event") - -if Roles and Event then - Event.add(Roles.events.on_role_assigned, function(e) - Gui.update_top_flow(game.players[e.player_index]) - end) - Event.add(Roles.events.on_role_unassigned, function(e) - Gui.update_top_flow(game.players[e.player_index]) - end) -end - -return Gui diff --git a/exp_legacy/module/expcore/gui/core_defines.lua b/exp_legacy/module/expcore/gui/core_defines.lua deleted file mode 100644 index fc61fc7d..00000000 --- a/exp_legacy/module/expcore/gui/core_defines.lua +++ /dev/null @@ -1,89 +0,0 @@ ---[[-- Core Module - Gui -- Gui defines that are used internally by the gui system -@module Gui -]] - -local Gui = require("modules.exp_legacy.expcore.gui.prototype") -local Event = require("modules/exp_legacy/utils/event") - ---- Core Defines. --- @section coreDefines - ---- Button which toggles the top flow elements, version which shows inside the top flow when top flow is visible --- @element hide_top_flow -local hide_top_flow = - Gui.element{ - type = "sprite-button", - sprite = "utility/preset", - style = "tool_button", - tooltip = { "gui_util.button_tooltip" }, - name = Gui.unique_static_name, - } - :style{ - padding = -2, - width = 18, - height = 36, - } - :on_click(function(player, _, _) - Gui.toggle_top_flow(player, false) - end) -Gui.core_defines.hide_top_flow = hide_top_flow - ---- Button which toggles the top flow elements, version which shows inside the left flow when top flow is hidden --- @element show_top_flow -local show_top_flow = - Gui.element{ - type = "sprite-button", - sprite = "utility/preset", - style = "tool_button", - tooltip = { "gui_util.button_tooltip" }, - name = Gui.unique_static_name, - } - :style{ - padding = -2, - width = 18, - height = 20, - } - :on_click(function(player, _, _) - Gui.toggle_top_flow(player, true) - end) -Gui.core_defines.show_top_flow = show_top_flow - ---- Button which hides the elements in the left flow, shows inside the left flow when frames are visible --- @element hide_left_flow -local hide_left_flow = - Gui.element{ - type = "sprite-button", - sprite = "utility/close_black", - style = "tool_button", - tooltip = { "expcore-gui.left-button-tooltip" }, - name = Gui.unique_static_name, - } - :style{ - padding = -3, - width = 18, - height = 20, - } - :on_click(function(player, _, _) - Gui.hide_left_flow(player) - end) -Gui.core_defines.hide_left_flow = hide_left_flow - ---- Draw the core elements when a player joins the game -Event.add(defines.events.on_player_created, function(event) - local player = game.players[event.player_index] - - -- Draw the top flow - local top_flow = Gui.get_top_flow(player) - hide_top_flow(top_flow) - Gui.update_top_flow(player) - - -- Draw the left flow - local left_flow = Gui.get_left_flow(player) - local button_flow = left_flow.add{ type = "flow", name = "gui_core_buttons", direction = "vertical" } - local show_top = show_top_flow(button_flow) - local hide_left = hide_left_flow(button_flow) - show_top.visible = false - hide_left.visible = false - Gui.draw_left_flow(player) -end) diff --git a/exp_legacy/module/expcore/gui/defines.lua b/exp_legacy/module/expcore/gui/defines.lua deleted file mode 100644 index 548e0365..00000000 --- a/exp_legacy/module/expcore/gui/defines.lua +++ /dev/null @@ -1,301 +0,0 @@ ---[[-- Core Module - Gui -- Common defines that are used by other modules, non of these are used internally -@module Gui -]] - -local Gui = require("modules.exp_legacy.expcore.gui.prototype") - ---- Defines. --- @section defines - ---[[-- Draw a flow used to align its child elements, default is right align -@element Gui.alignment -@tparam LuaGuiElement parent the parent element to which the alignment will be added -@tparam[opt='alignment'] string name the name of the alignment flow which is added -@tparam[opt='right'] string horizontal_align the horizontal alignment of the elements in the flow -@tparam[opt='center'] string vertical_align the vertical alignment of the elements in the flow -@treturn LuaGuiElement the alignment flow that was created - -@usage-- Adding a right align flow -local alignment = Gui.alignment(element, 'example_right_alignment') - -@usage-- Adding a horizontal center and top align flow -local alignment = Gui.alignment(element, 'example_center_top_alignment', 'center', 'top') - -]] -Gui.alignment = - Gui.element(function(_, parent, name, _, _) - return parent.add{ - name = name or "alignment", - type = "flow", - } - end) - :style(function(style, _, _, horizontal_align, vertical_align) - style.padding = { 1, 2 } - style.vertical_align = vertical_align or "center" - style.horizontal_align = horizontal_align or "right" - style.vertically_stretchable = style.vertical_align ~= "center" - style.horizontally_stretchable = style.horizontal_align ~= "center" - end) - ---[[-- Draw a scroll pane that has a table inside of it -@element Gui.scroll_table -@tparam LuaGuiElement parent the parent element to which the scroll table will be added -@tparam number height the maximum height for the scroll pane -@tparam number column_count the number of columns that the table will have -@tparam[opt='scroll'] string name the name of the scroll pane that is added, the table is always called "table" -@treturn LuaGuiElement the table that was created - -@usage-- Adding a scroll table with max height of 200 and column count of 3 -local scroll_table = Gui.scroll_table(element, 200, 3) - -]] -Gui.scroll_table = - Gui.element(function(_, parent, height, column_count, name) - -- Draw the scroll - local scroll_pane = - parent.add{ - name = name or "scroll", - type = "scroll-pane", - direction = "vertical", - horizontal_scroll_policy = "never", - vertical_scroll_policy = "auto", - style = "scroll_pane_under_subheader", - } - - -- Set the style of the scroll pane - local scroll_style = scroll_pane.style - scroll_style.padding = { 1, 3 } - scroll_style.maximal_height = height - scroll_style.horizontally_stretchable = true - - -- Draw the table - local scroll_table = - scroll_pane.add{ - type = "table", - name = "table", - column_count = column_count, - } - - -- Return the scroll table - return scroll_table - end) - :style{ - padding = 0, - cell_padding = 0, - vertical_align = "center", - horizontally_stretchable = true, - } - ---[[-- Used to add a frame with the header style, has the option for a right alignment flow for buttons -@element Gui.header -@tparam LuaGuiElement parent the parent element to which the header will be added -@tparam ?string|Concepts.LocalizedString caption the caption that will be shown on the header -@tparam[opt] ?string|Concepts.LocalizedString tooltip the tooltip that will be shown on the header -@tparam[opt=false] boolean add_alignment when true an alignment flow will be added to the header -@tparam[opt='header'] string name the name of the header that is being added, the alignment is always called "alignment" -@treturn LuaGuiElement either the header or the header alignment if add_alignment is true - -@usage-- Adding a custom header with a label -local header = Gui.header( - element, - 'Example Caption', - 'Example Tooltip' -) - -]] -Gui.header = - Gui.element(function(_, parent, caption, tooltip, add_alignment, name, label_name) - -- Draw the header - local header = - parent.add{ - name = name or "header", - type = "frame", - style = "subheader_frame", - } - - -- Change the style of the header - local style = header.style - style.padding = { 2, 4 } - style.use_header_filler = false - style.horizontally_stretchable = true - - -- Draw the caption label - if caption then - header.add{ - name = label_name or "header_label", - type = "label", - style = "frame_title", - caption = caption, - tooltip = tooltip, - } - end - - -- Return either the header or the added alignment - return add_alignment and Gui.alignment(header) or header - end) - ---[[-- Used to add a frame with the footer style, has the option for a right alignment flow for buttons -@element Gui.footer -@tparam LuaGuiElement parent the parent element to which the footer will be added -@tparam ?string|Concepts.LocalizedString caption the caption that will be shown on the footer -@tparam[opt] ?string|Concepts.LocalizedString tooltip the tooltip that will be shown on the footer -@tparam[opt=false] boolean add_alignment when true an alignment flow will be added to the footer -@tparam[opt='footer'] string name the name of the footer that is being added, the alignment is always called "alignment" -@treturn LuaGuiElement either the footer or the footer alignment if add_alignment is true - -@usage-- Adding a custom footer with a label -local footer = Gui.footer( - element, - 'Example Caption', - 'Example Tooltip' -) - -]] -Gui.footer = - Gui.element(function(_, parent, caption, tooltip, add_alignment, name) - -- Draw the header - local footer = - parent.add{ - name = name or "footer", - type = "frame", - style = "subfooter_frame", - } - - -- Change the style of the footer - local style = footer.style - style.padding = { 2, 4 } - style.use_header_filler = false - style.horizontally_stretchable = true - - -- Draw the caption label - if caption then - footer.add{ - name = "footer_label", - type = "label", - style = "frame_title", - caption = caption, - tooltip = tooltip, - } - end - - -- Return either the footer or the added alignment - return add_alignment and Gui.alignment(footer) or footer - end) - ---[[-- Used for left frames to give them a nice boarder -@element Gui.container -@tparam LuaGuiElement parent the parent element to which the container will be added -@tparam string name the name that you want to give to the outer frame, often just event_trigger -@tparam number width the minimal width that the frame will have - -@usage-- Adding a container as a base -local container = Gui.container(parent, 'my_container', 200) - -]] -Gui.container = - Gui.element(function(_, parent, name, _) - -- Draw the external container - local frame = - parent.add{ - name = name, - type = "frame", - } - frame.style.horizontally_stretchable = false - - -- Return the container - return frame.add{ - name = "container", - type = "frame", - direction = "vertical", - style = "inside_shallow_frame_packed", - } - end) - :style(function(style, element, _, width) - style.vertically_stretchable = false - local frame_style = element.parent.style - frame_style.padding = 2 - frame_style.minimal_width = width - end) - ---[[-- Used to make a solid white bar in a gui -@element Gui.bar -@tparam LuaGuiElement parent the parent element to which the bar will be added -@tparam number width the width of the bar that will be made, if not given bar will strech to fill the parent - -@usage-- Adding a bar to a gui -local bar = Gui.bar(parent, 100) - -]] -Gui.bar = - Gui.element(function(_, parent) - return parent.add{ - type = "progressbar", - size = 1, - value = 1, - } - end) - :style(function(style, _, width) - style.height = 3 - style.color = { r = 255, g = 255, b = 255 } - if width then - style.width = width - else - style.horizontally_stretchable = true - end - end) - ---[[-- Used to make a label which is centered and of a certian size -@element Gui.centered_label -@tparam LuaGuiElement parent the parent element to which the label will be added -@tparam number width the width of the label, must be given in order to center the caption -@tparam ?string|Concepts.LocalizedString caption the caption that will be shown on the label -@tparam[opt] ?string|Concepts.LocalizedString tooltip the tooltip that will be shown on the label - -@usage-- Adding a centered label -local label = Gui.centered_label(parent, 100, 'This is centered') - -]] -Gui.centered_label = - Gui.element(function(_, parent, width, caption, tooltip) - local label = parent.add{ - type = "label", - caption = caption, - tooltip = tooltip, - } - - local style = label.style - style.horizontal_align = "center" - style.single_line = false - style.width = width - - return label - end) - ---[[-- Used to make a title which has two bars on either side -@element Gui.title_label -@tparam LuaGuiElement parent the parent element to which the label will be added -@tparam number width the width of the first bar, this can be used to position the label -@tparam ?string|Concepts.LocalizedString caption the caption that will be shown on the label -@tparam[opt] ?string|Concepts.LocalizedString tooltip the tooltip that will be shown on the label - -@usage-- Adding a centered label -local label = Gui.centered_label(parent, 100, 'This is centered') - -]] -Gui.title_label = - Gui.element(function(_, parent, width, caption, tooltip) - local title_flow = parent.add{ type = "flow" } - title_flow.style.vertical_align = "center" - - Gui.bar(title_flow, width) - local title_label = title_flow.add{ - type = "label", - caption = caption, - tooltip = tooltip, - style = "frame_title", - } - Gui.bar(title_flow) - - return title_label - end) diff --git a/exp_legacy/module/expcore/gui/helper_functions.lua b/exp_legacy/module/expcore/gui/helper_functions.lua deleted file mode 100644 index 6cfb37db..00000000 --- a/exp_legacy/module/expcore/gui/helper_functions.lua +++ /dev/null @@ -1,91 +0,0 @@ ---[[-- Core Module - Gui -- Functions used to help with the use of guis -@module Gui -]] - -local Gui = require("modules.exp_legacy.expcore.gui.prototype") - ---- Helper Functions. --- @section helperFunctions - ---[[-- Get the player that owns a gui element -@tparam LuaGuiElement element the element to get the owner of -@treturn LuaPlayer the player that owns this element - -@usage-- Geting the owner of an element -local player = Gui.get_player_from_element(element) - -]] -function Gui.get_player_from_element(element) - if not element or not element.valid then return end - return game.players[element.player_index] -end - ---[[-- Will toggle the enabled state of an element or set it to the one given -@tparam LuaGuiElement element the element to toggle/set the enabled state of -@tparam[opt] boolean state with given will set the state, else state will be toggled -@treturn boolean the new enabled state that the element has - -@usage-- Toggling the the enabled state -local new_enabled_state = Gui.toggle_enabled_state(element) - -]] -function Gui.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 - return state -end - ---[[-- Will toggle the visible state of an element or set it to the one given -@tparam LuaGuiElement element the element to toggle/set the visible state of -@tparam[opt] boolean state with given will set the state, else state will be toggled -@treturn boolean the new visible state that the element has - -@usage-- Toggling the the visible state -local new_visible_state = Gui.toggle_visible_state(element) - -]] -function Gui.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 - return state -end - ---[[-- Destory a gui element without causing any errors, often because the element was already removed -@tparam LuaGuiElement element the element that you want to remove -@treturn boolean true if the element was valid and has been removed - -@usage-- Remove a child element if it exists -Gui.destroy_if_valid(element[child_name]) - -]] -function Gui.destroy_if_valid(element) - if not element or not element.valid then return false end - element.destroy() - return true -end - ---[[-- Returns a table to be used as the style for a sprite buttons, produces a sqaure button -@tparam number size the size that you want the button to be -@tparam[opt=-2] number padding the padding that you want on the sprite -@tparam[opt] table style any extra style settings that you want to have -@treturn table the style table to be used with element_define:style() - -@usage-- Adding a sprite button with size 20 -local button = -Gui.element{ - type = 'sprite-button', - sprite = 'entity/inserter' -} -:style(Gui.sprite_style(20)) - -]] -function Gui.sprite_style(size, padding, style) - style = style or {} - style.padding = padding or -2 - style.height = size - style.width = size - return style -end diff --git a/exp_legacy/module/expcore/gui/left_flow.lua b/exp_legacy/module/expcore/gui/left_flow.lua deleted file mode 100644 index e292b427..00000000 --- a/exp_legacy/module/expcore/gui/left_flow.lua +++ /dev/null @@ -1,277 +0,0 @@ ---[[-- Core Module - Gui -- Used to define new gui elements and gui event handlers -@module Gui -]] - -local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui.prototype") -local mod_gui = require "mod-gui" - -local hide_left_flow = Gui.core_defines.hide_left_flow.name - ---- Left Flow. --- @section leftFlow - --- Triggered when a user changed the visibility of a left flow element by clicking a button -Gui.events.on_visibility_changed_by_click = "on_visibility_changed_by_click" - ---- Contains the uids of the elements that will shown on the left flow and their join functions --- @table left_elements -Gui.left_elements = {} - ---[[-- Gets the flow refered to as the left flow, each player has one left flow -@function Gui.get_left_flow(player) -@tparam LuaPlayer player the player that you want to get the left flow for -@treturn LuaGuiElement the left element flow - -@usage-- Geting your left flow -local left_flow = Gui.get_left_flow(game.player) - -]] -Gui.get_left_flow = mod_gui.get_frame_flow - ---[[-- Sets an element define to be drawn to the left flow when a player joins, includes optional check -@tparam[opt] ?boolean|function open_on_join called during first darw to decide if the element should be visible -@treturn table the new element define that is used to register events to this element - -@usage-- Adding the example button -example_flow_with_button:add_to_left_flow(true) - -]] -function Gui._prototype_element:add_to_left_flow(open_on_join) - ExpUtil.assert_not_runtime() - if not self.name then error("Elements for the top flow must have a static name") end - self.open_on_join = open_on_join or false - table.insert(Gui.left_elements, self) - return self -end - ---[[-- Creates a button on the top flow which will toggle the given element define, the define must exist in the left flow -@tparam string sprite the sprite that you want to use on the button -@tparam ?string|Concepts.LocalizedString tooltip the tooltip that you want the button to have -@tparam table element_define the element define that you want to have toggled by this button, define must exist on the left flow -@tparam[opt] function authenticator used to decide if the button should be visible to a player - -@usage-- Add a button to toggle a left element -local toolbar_button = -Gui.left_toolbar_button('entity/inserter', 'Nothing to see here', example_flow_with_button, function(player) - return player.admin -end) - -]] -function Gui.left_toolbar_button(sprite, tooltip, element_define, authenticator) - local button = Gui.toolbar_button(sprite, tooltip, authenticator) - - -- Add on_click handler to handle click events comming from the player - button:on_click(function(player, _, _) - -- Raise custom event that tells listening elements if the element has changed visibility by a player clicking - -- Used in warp gui to handle the keep open logic - button:raise_event{ - name = Gui.events.on_visibility_changed_by_click, - element = Gui.get_top_element(player, button), - state = Gui.toggle_left_element(player, element_define), - } - end) - - -- Add property to the left flow element with the name of the button - -- This is for the ability to reverse lookup the button from the left flow element - element_define.toolbar_button = button - button.left_flow_element = element_define - return button -end - -Gui._left_flow_order_src = "" ---- Get the order of elements in the left flow, first argument is player but is unused in the default method -function Gui.get_left_flow_order(_) - return Gui.left_elements -end - ---- Inject a custom left flow order provider, this should accept a player and return a list of elements definitions to draw -function Gui.inject_left_flow_order(provider) - Gui.get_left_flow_order = provider - local debug_info = debug.getinfo(2, "Sn") - local file_name = debug_info.short_src:sub(10, -5) - local func_name = debug_info.name or ("") - Gui._left_flow_order_src = file_name .. ":" .. func_name -end - ---[[-- Draw all the left elements onto the left flow, internal use only with on join -@tparam LuaPlayer player the player that you want to draw the elements for - -@usage-- Draw all the left elements -Gui.draw_left_flow(player) - -]] -function Gui.draw_left_flow(player) - local left_flow = Gui.get_left_flow(player) - local hide_button = left_flow.gui_core_buttons[hide_left_flow] - local show_hide_button = false - - -- Get the order to draw the elements in - local flow_order = Gui.get_left_flow_order(player) - if #flow_order ~= #Gui.left_elements then - error(string.format("Left flow order provider (%s) did not return the correct element count, expect %d got %d", - Gui._left_flow_order_src, #Gui.left_elements, #flow_order - )) - end - - for _, element_define in ipairs(flow_order) do - -- Draw the element to the left flow - local draw_success, left_element = xpcall(function() - return element_define(left_flow) - end, debug.traceback) - - if not draw_success then - log("There as been an error with an element draw function: " .. element_define.defined_at .. "\n\t" .. left_element) - goto continue - end - - -- Check if it should be open by default - local open_on_join = element_define.open_on_join - local visible = type(open_on_join) == "boolean" and open_on_join or false - if type(open_on_join) == "function" then - local success, err = xpcall(open_on_join, debug.traceback, player) - if not success then - log("There as been an error with an open on join hander for a gui element:\n\t" .. err) - goto continue - end - visible = err - end - - -- Set the visible state of the element - left_element.visible = visible - show_hide_button = show_hide_button or visible - - -- Check if the the element has a button attached - if element_define.toolbar_button then - Gui.toggle_toolbar_button(player, element_define.toolbar_button, visible) - end - ::continue:: - end - - hide_button.visible = show_hide_button -end - ---- Reorder the left flow elements to match that returned by the provider, uses a method equivalent to insert sort -function Gui.reorder_left_flow(player) - local left_flow = Gui.get_left_flow(player) - - -- Get the order to draw the elements in - local flow_order = Gui.get_left_flow_order(player) - if #flow_order ~= #Gui.left_elements then - error(string.format("Left flow order provider (%s) did not return the correct element count, expect %d got %d", - Gui._left_flow_order_src, #Gui.left_elements, #flow_order - )) - end - - -- Reorder the elements, index 1 is the core ui buttons so +1 is required - for index, element_define in ipairs(flow_order) do - local element = left_flow[element_define.name] - left_flow.swap_children(index + 1, element.get_index_in_parent()) - end -end - ---[[-- Update the visible state of the hide button, can be used to check if any frames are visible -@tparam LuaPlayer player the player to update the left flow for -@treturn boolean true if any left element is visible - -@usage-- Check if any left elements are visible -local visible = Gui.update_left_flow(player) - -]] -function Gui.update_left_flow(player) - local left_flow = Gui.get_left_flow(player) - local hide_button = left_flow.gui_core_buttons[hide_left_flow] - for _, element_define in ipairs(Gui.left_elements) do - local left_element = left_flow[element_define.name] - if left_element.visible then - hide_button.visible = true - return true - end - end - - hide_button.visible = false - return false -end - ---[[-- Hides all left elements for a player -@tparam LuaPlayer player the player to hide the elements for - -@usage-- Hide your left elements -Gui.hide_left_flow(game.player) - -]] -function Gui.hide_left_flow(player) - local top_flow = Gui.get_top_flow(player) - local left_flow = Gui.get_left_flow(player) - local hide_button = left_flow.gui_core_buttons[hide_left_flow] - - -- Set the visible state of all elements in the flow - hide_button.visible = false - for _, element_define in ipairs(Gui.left_elements) do - left_flow[element_define.name].visible = false - - -- Check if the the element has a toobar button attached - if element_define.toolbar_button then - -- Check if the topflow contains the button - local button = top_flow[element_define.toolbar_button.name] - if button then - -- Style the button - Gui.toggle_toolbar_button(player, element_define.toolbar_button, false) - -- Raise the custom event if all of the top checks have passed - element_define.toolbar_button:raise_event{ - name = Gui.events.on_visibility_changed_by_click, - element = button, - state = false, - } - end - end - end -end - ---- Checks if an element is loaded, used internally when the normal left gui assumptions may not hold -function Gui.left_flow_loaded(player, element_define) - local left_flow = Gui.get_left_flow(player) - return left_flow[element_define.name] ~= nil -end - ---[[-- Get the element define that is in the left flow, use in events without an element refrence -@tparam LuaPlayer player the player that you want to get the element for -@tparam table element_define the element that you want to get -@treturn LuaGuiElement the gui element linked to this define for this player - -@usage-- Get your left element -local frame = Gui.get_left_element(game.player, example_flow_with_button) - -]] -function Gui.get_left_element(player, element_define) - local left_flow = Gui.get_left_flow(player) - return assert(left_flow[element_define.name], "Left element failed to load") -end - ---[[-- Toggles the visible state of a left element for a given player, can be used to set the visible state -@tparam LuaPlayer player the player that you want to toggle the element for -@tparam table element_define the element that you want to toggle -@tparam[opt] boolean state with given will set the state, else state will be toggled -@treturn boolean the new visible state of the element - -@usage-- Toggle your example button -Gui.toggle_top_flow(game.player, example_flow_with_button) - -@usage-- Show your example button -Gui.toggle_top_flow(game.player, example_flow_with_button, true) - -]] -function Gui.toggle_left_element(player, element_define, state) - -- Set the visible state - local element = Gui.get_left_element(player, element_define) - if state == nil then state = not element.visible end - element.visible = state - Gui.update_left_flow(player) - - -- Check if the the element has a button attached - if element_define.toolbar_button then - Gui.toggle_toolbar_button(player, element_define.toolbar_button, state) - end - return state -end diff --git a/exp_legacy/module/expcore/gui/prototype.lua b/exp_legacy/module/expcore/gui/prototype.lua deleted file mode 100644 index 596a9157..00000000 --- a/exp_legacy/module/expcore/gui/prototype.lua +++ /dev/null @@ -1,415 +0,0 @@ ---[[-- Core Module - Gui -- Used to simplify gui creation using factory functions called element defines -@module Gui -]] - -local ExpUtil = require("modules/exp_util") -local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event - -local Gui = { - --- The current highest uid that is being used by a define, will not increase during runtime - uid = 0, - --- Used to automatically assign a unique static name to an element - unique_static_name = {}, - --- String indexed table used to avoid conflict with custom event names, similar to how defines.events works - events = {}, - --- Uid indexed array that stores all the factory functions that were defined, no new values will be added during runtime - defines = {}, - --- An string indexed table of all the defines which are used by the core of the gui system, used for internal reference - core_defines = {}, - --- Used to store the file names where elements were defined, this can be useful to find the uid of an element, mostly for debugging - file_paths = {}, - --- Used to store extra information about elements as they get defined such as the params used and event handlers registered to them - debug_info = {}, - --- The prototype used to store the functions of an element define - _prototype_element = {}, - --- The prototype metatable applied to new element defines - _mt_element = {}, -} - ---- Allow access to the element prototype methods -Gui._mt_element.__index = Gui._prototype_element - ---- Allows the define to be called to draw the element -function Gui._mt_element.__call(self, parent, ...) - local element, no_events = self._draw(self, parent, ...) - if self._style then self._style(element.style, element, ...) end - - -- Asserts to catch common errors - if element then - if self.name and self.name ~= element.name then - error("Static name \"" .. self.name .. "\" expected but got: " .. tostring(element.name)) - end - local event_triggers = element.tags and element.tags.ExpGui_event_triggers - if event_triggers and table.array_contains(event_triggers, self.uid) then - error("Element::triggers_events should not be called on the value you return from the definition") - end - elseif self.name then - error("Static name \"" .. self.name .. "\" expected but no element was returned from the definition") - end - - -- Register events by default, but allow skipping them - if no_events == self.no_events then - return element - else - return element and self:triggers_events(element) - end -end - ---- Get where a function was defined as a string -local function get_defined_at(level) - local debug_info = debug.getinfo(level, "Sn") - local file_name = debug_info.short_src:sub(10, -5) - local func_name = debug_info.name or ("") - return file_name .. ":" .. func_name -end - ---- Element Define. --- @section elementDefine - ---[[-- Used to define new elements for your gui, can be used like LuaGuiElement.add or a custom function -@tparam ?table|function element_define the define information for the gui element, same data as LuaGuiElement.add, or a custom function may be used -@treturn table the new element define, this can be considered a factory for the element which can be called to draw the element to any other element - -@usage-- Using element defines like LuaGuiElement.add --- This returns a factory function to draw a button with the caption "Example Button" -local example_button = -Gui.element{ - type = 'button', - caption = 'Example Button' -} - -@usage-- Using element defines with a custom factory function --- This method can be used if you still want to be able register event handlers but it is too complex to be compatible with LuaGuiElement.add -local example_flow_with_button = -Gui.element(function(event_trigger, parent, ...) - -- ... shows that all other arguments from the factory call are passed to this function - -- parent is the element which was passed to the factory function where you should add your new element - -- here we are adding a flow which we will then later add a button to - local flow = - parent.add{ - name = 'example_flow', - type = 'flow' - } - - -- event_trigger should be the name of any elements you want to trigger your event handlers, such as on_click or on_state_changed - -- now we add the button to the flow that we created earlier - local element = - flow.add{ - name = event_trigger, - type = 'button', - caption = 'Example Button' - } - - -- you must return your new element, this is so styles can be applied and returned to the caller - -- you may return any of your elements that you add, consider the context in which it will be used for what should be returned - return element -end) - -]] -function Gui.element(element_define) - ExpUtil.assert_not_runtime() - -- Set the metatable to allow access to register events - local element = setmetatable({}, Gui._mt_element) - - -- Increment the uid counter - local uid = Gui.uid + 1 - Gui.uid = uid - element.uid = uid - Gui.debug_info[uid] = { draw = "None", style = "None", events = {} } - - -- Add the definition function - if type(element_define) == "table" then - Gui.debug_info[uid].draw = element_define - if element_define.name == Gui.unique_static_name then - element_define.name = "ExpGui_" .. tostring(uid) - end - for k, v in pairs(element_define) do - if element[k] == nil then - element[k] = v - end - end - - element._draw = function(_, parent) - return parent.add(element_define) - end - else - Gui.debug_info[uid].draw = get_defined_at(element_define) - element._draw = element_define - end - - -- Add the define to the base module - element.defined_at = get_defined_at(3) - Gui.file_paths[uid] = element.defined_at - Gui.defines[uid] = element - - -- Return the element so event handers can be accessed - return element -end - ---[[-- Used to extent your element define with a style factory, this style will be applied to your element when created, can also be a custom function -@tparam ?table|function style_define style table where each key and value pair is treated like LuaGuiElement.style[key] = value, a custom function can be used -@treturn table the element define is returned to allow for event handlers to be registered - -@usage-- Using the table method of setting the style -local example_button = -Gui.element{ - type = 'button', - caption = 'Example Button', - style = 'forward_button' -- factorio styles can be applied here -} -:style{ - height = 25, -- same as element.style.height = 25 - width = 100 -- same as element.style.width = 25 -} - -@usage-- Using the function method to set the style --- Use this method if your style is dynamic and depends on other factors -local example_button = -Gui.element{ - type = 'button', - caption = 'Example Button', - style = 'forward_button' -- factorio styles can be applied here -} -:style(function(style, element, ...) - -- style is the current style object for the elemenent - -- element is the element that is being changed - -- ... shows that all other arguments from the factory call are passed to this function - local player = game.players[element.player_index] - style.height = 25 - style.width = 100 - style.font_color = player.color -end) - -]] -function Gui._prototype_element:style(style_define) - ExpUtil.assert_not_runtime() - -- Add the definition function - if type(style_define) == "table" then - Gui.debug_info[self.uid].style = style_define - self._style = function(style) - for key, value in pairs(style_define) do - style[key] = value - end - end - else - Gui.debug_info[self.uid].style = get_defined_at(style_define) - self._style = style_define - end - - -- Return the element so event handers can be accessed - return self -end - ---[[-- Enforce the fact the element has a static name, this is required for the cases when a function define is used -@tparam[opt] string element The element that will trigger calls to the event handlers -@treturn table the element define is returned to allow for event handlers to be registered -]] -function Gui._prototype_element:static_name(name) - ExpUtil.assert_not_runtime() - if name == Gui.unique_static_name then - self.name = "ExpGui_" .. tostring(self.uid) - else - self.name = name - end - return self -end - ---[[-- Used to link an element to an element define such that any event on the element will call the handlers on the element define --- You should not call this on the element you return from your constructor because this is done automatically -@tparam LuaGuiElement element The element that will trigger calls to the event handlers -@treturn LuaGuiElement The element passed as the argument to allow for cleaner returns -]] -function Gui._prototype_element:triggers_events(element) - if not self._has_events then return element end - local tags = element.tags - if not tags then - element.tags = { ExpGui_event_triggers = { self.uid } } - return element - elseif not tags.ExpGui_event_triggers then - --- @diagnostic disable-next-line: name-style-check - tags.ExpGui_event_triggers = { self.uid } - elseif table.array_contains(tags.ExpGui_event_triggers, self.uid) then - error("Element::triggers_events called multiple times on the same element with the same definition") - else - table.insert(tags.ExpGui_event_triggers, self.uid) - end - -- To modify a set of tags, the whole table needs to be written back to the respective property. - element.tags = tags - return element -end - ---- Explicitly skip events on the element returned by your definition function -function Gui._prototype_element:no_events(element) - return element, self.no_events -end - ---[[-- Set the handler which will be called for a custom event, only one handler can be used per event per element -@tparam string event_name the name of the event you want to handler to be called on, often from Gui.events -@tparam function handler the handler that you want to be called when the event is raised -@treturn table the element define so more handleres can be registered - -@usage-- Register a handler to "my_custom_event" for this element -element_deinfe:on_event('my_custom_event', function(event) - event.player.print(player.name) -end) - -]] -function Gui._prototype_element:on_event(event_name, handler) - ExpUtil.assert_not_runtime() - table.insert(Gui.debug_info[self.uid].events, event_name) - Gui.events[event_name] = event_name - self[event_name] = handler - self._has_events = true - return self -end - ---[[-- Raise the handler which is attached to an event; external use should be limited to custom events -@tparam table event the event table passed to the handler, must contain fields: name, element -@treturn table the element define so more events can be raised - -@usage Raising a custom event -element_define:raise_event{ - name = 'my_custom_event', - element = element -} - -]] -function Gui._prototype_element:raise_event(event) - -- Check the element is valid - local element = event.element - if not element or not element.valid then - return self - end - - -- Get the event handler for this element - local handler = self[event.name] - if not handler then - return self - end - - -- Get the player for this event - local player_index = event.player_index or element.player_index - local player = game.players[player_index] - if not player or not player.valid then - return self - end - event.player = player - - local success, err = xpcall(handler, debug.traceback, player, element, event) - if not success then - error("There as been an error with an event handler for a gui element:\n\t" .. err) - end - return self -end - --- This function is used to link element define events and the events from the factorio api -local function event_handler_factory(event_name) - Event.add(event_name, function(event) - local element = event.element - if not element or not element.valid then return end - local event_triggers = element.tags.ExpGui_event_triggers - if not event_triggers then return end - for _, uid in pairs(event_triggers) do - local element_define = Gui.defines[uid] - if element_define then - element_define:raise_event(event) - end - end - end) - - Gui.events[event_name] = event_name - return function(self, handler) - return self:on_event(event_name, handler) - end -end - ---- Element Events. --- @section elementEvents - ---- Called when the player opens a GUI. --- @tparam function handler the event handler which will be called --- @usage element_define:on_open(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_open = event_handler_factory(defines.events.on_gui_opened) - ---- Called when the player closes the GUI they have open. --- @tparam function handler the event handler which will be called --- @usage element_define:on_close(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_close = event_handler_factory(defines.events.on_gui_closed) - ---- Called when LuaGuiElement is clicked. --- @tparam function handler the event handler which will be called --- @usage element_define:on_click(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_click = event_handler_factory(defines.events.on_gui_click) - ---- Called when a LuaGuiElement is confirmed, for example by pressing Enter in a textfield. --- @tparam function handler the event handler which will be called --- @usage element_define:on_confirmed(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_confirmed = event_handler_factory(defines.events.on_gui_confirmed) - ---- Called when LuaGuiElement checked state is changed (related to checkboxes and radio buttons). --- @tparam function handler the event handler which will be called --- @usage element_define:on_checked_changed(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_checked_changed = event_handler_factory(defines.events.on_gui_checked_state_changed) - ---- Called when LuaGuiElement element value is changed (related to choose element buttons). --- @tparam function handler the event handler which will be called --- @usage element_define:on_elem_changed(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_elem_changed = event_handler_factory(defines.events.on_gui_elem_changed) - ---- Called when LuaGuiElement element location is changed (related to frames in player.gui.screen). --- @tparam function handler the event handler which will be called --- @usage element_define:on_location_changed(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_location_changed = event_handler_factory(defines.events.on_gui_location_changed) - ---- Called when LuaGuiElement selected tab is changed (related to tabbed-panes). --- @tparam function handler the event handler which will be called --- @usage element_define:on_tab_changed(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_tab_changed = event_handler_factory(defines.events.on_gui_selected_tab_changed) - ---- Called when LuaGuiElement selection state is changed (related to drop-downs and listboxes). --- @tparam function handler the event handler which will be called --- @usage element_define:on_selection_changed(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_selection_changed = event_handler_factory(defines.events.on_gui_selection_state_changed) - ---- Called when LuaGuiElement switch state is changed (related to switches). --- @tparam function handler the event handler which will be called --- @usage element_define:on_switch_changed(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_switch_changed = event_handler_factory(defines.events.on_gui_switch_state_changed) - ---- Called when LuaGuiElement text is changed by the player. --- @tparam function handler the event handler which will be called --- @usage element_define:on_text_changed(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_text_changed = event_handler_factory(defines.events.on_gui_text_changed) - ---- Called when LuaGuiElement slider value is changed (related to the slider element). --- @tparam function handler the event handler which will be called --- @usage element_define:on_value_changed(function(event) --- event.player.print(table.inspect(event)) --- end) -Gui._prototype_element.on_value_changed = event_handler_factory(defines.events.on_gui_value_changed) - --- Module return -return Gui diff --git a/exp_legacy/module/expcore/gui/top_flow.lua b/exp_legacy/module/expcore/gui/top_flow.lua deleted file mode 100644 index a955692e..00000000 --- a/exp_legacy/module/expcore/gui/top_flow.lua +++ /dev/null @@ -1,316 +0,0 @@ ---[[-- Core Module - Gui -- Controls the elements on the top flow -@module Gui -]] - -local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui.prototype") -local mod_gui = require "mod-gui" --- @dep mod-gui - -local toolbar_button_size = 36 -local hide_top_flow = Gui.core_defines.hide_top_flow.name -local show_top_flow = Gui.core_defines.show_top_flow.name - ---- Top Flow. --- @section topFlow - --- Triggered when a user changed the visibility of a left flow element by clicking a button -Gui.events.on_toolbar_button_toggled = "on_toolbar_button_toggled" - ---- Contains the uids of the elements that will shown on the top flow and their auth functions --- @table top_elements -Gui.top_elements = {} - ---- The style that should be used for buttons on the top flow --- @field Gui.top_flow_button_style -Gui.top_flow_button_style = mod_gui.button_style - ---- The style that should be used for buttons on the top flow when their flow is visible --- @field Gui.top_flow_button_toggled_style -Gui.top_flow_button_toggled_style = "menu_button_continue" - ---[[-- Styles a top flow button depending on the state given -@tparam LuaGuiElement button the button element to style -@tparam boolean state The state the button is in - -@usage-- Sets the button to the visible style -Gui.toolbar_button_style(button, true) - -@usage-- Sets the button to the hidden style -Gui.toolbar_button_style(button, false) - -]] -function Gui.toolbar_button_style(button, state, size) - --- @cast button LuaGuiElement - if state then - button.style = Gui.top_flow_button_toggled_style - else - button.style = Gui.top_flow_button_style - end - button.style.minimal_width = size or toolbar_button_size - button.style.height = size or toolbar_button_size - button.style.padding = -2 -end - ---[[-- Gets the flow refered to as the top flow, each player has one top flow -@function Gui.get_top_flow(player) -@tparam LuaPlayer player the player that you want to get the flow for -@treturn LuaGuiElement the top element flow - -@usage-- Geting your top flow -local top_flow = Gui.get_top_flow(game.player) - -]] -Gui.get_top_flow = mod_gui.get_button_flow - ---[[-- Sets an element define to be drawn to the top flow when a player joins, includes optional authenticator -@tparam[opt] function authenticator called during toggle or update to decide weather the element should be visible -@treturn table the new element define to allow event handlers to be registered - -@usage-- Adding an element to the top flow on join -example_button:add_to_top_flow(function(player) - -- example button will only be shown if the player is an admin - -- note button will not update its state when player.admin is changed Gui.update_top_flow must be called for this - return player.admin -end) - -]] -function Gui._prototype_element:add_to_top_flow(authenticator) - ExpUtil.assert_not_runtime() - if not self.name then error("Elements for the top flow must have a static name") end - self.authenticator = authenticator or true - table.insert(Gui.top_elements, self) - return self -end - ---- Returns true if the top flow has visible elements -function Gui.top_flow_has_visible_elements(player) - local top_flow = Gui.get_top_flow(player) - - for _, child in pairs(top_flow.children) do - if child.name ~= hide_top_flow then - if child.visible then - return true - end - end - end - - return false -end - -Gui._top_flow_order_src = "" ---- Get the order of elements in the top flow, first argument is player but is unused in the default method -function Gui.get_top_flow_order(_) - return Gui.top_elements -end - ---- Inject a custom top flow order provider, this should accept a player and return a list of elements definitions to draw -function Gui.inject_top_flow_order(provider) - Gui.get_top_flow_order = provider - local debug_info = debug.getinfo(2, "Sn") - local file_name = debug_info.short_src:sub(10, -5) - local func_name = debug_info.name or ("") - Gui._top_flow_order_src = file_name .. ":" .. func_name -end - ---[[-- Updates the visible state of all the elements on the players top flow, uses authenticator -@tparam LuaPlayer player the player that you want to update the top flow for - -@usage-- Update your top flow -Gui.update_top_flow(game.player) - -]] -function Gui.update_top_flow(player) - local top_flow = Gui.get_top_flow(player) - - -- Get the order to draw the elements in - local flow_order = Gui.get_top_flow_order(player) - if #flow_order ~= #Gui.top_elements then - error(string.format("Top flow order provider (%s) did not return the correct element count, expect %d got %d", - Gui._top_flow_order_src, #Gui.top_elements, #flow_order - )) - end - - -- Set the visible state of all elements in the flow - for index, element_define in ipairs(flow_order) do - -- Ensure the element exists - local element = top_flow[element_define.name] - if not element then - element = element_define(top_flow) - else - top_flow.swap_children(index + 1, element.get_index_in_parent()) - end - - -- Set the visible state - local allowed = element_define.authenticator - if type(allowed) == "function" then allowed = allowed(player) end - element.visible = allowed or false - - -- If its not visible and there is a left element, then hide it - if element_define.left_flow_element and not element.visible and Gui.left_flow_loaded(player, element_define.left_flow_element) then - Gui.toggle_left_element(player, element_define.left_flow_element, false) - end - end - - -- Check if there are any visible elements in the top flow - if not Gui.top_flow_has_visible_elements(player) then - -- None are visible so hide the top_flow and its show button - Gui.toggle_top_flow(player, false) - local left_flow = Gui.get_left_flow(player) - local show_button = left_flow.gui_core_buttons[show_top_flow] - show_button.visible = false - end -end - ---- Reorder the top flow elements to match that returned by the provider, uses a method equivalent to insert sort -function Gui.reorder_top_flow(player) - local top_flow = Gui.get_top_flow(player) - - -- Get the order to draw the elements in - local flow_order = Gui.get_top_flow_order(player) - if #flow_order ~= #Gui.top_elements then - error(string.format("Top flow order provider (%s) did not return the correct element count, expect %d got %d", - Gui._top_flow_order_src, #Gui.top_elements, #flow_order - )) - end - - -- Reorder the elements, index 1 is the core ui buttons so +1 is required - for index, element_define in ipairs(flow_order) do - local element = top_flow[element_define.name] - top_flow.swap_children(index + 1, element.get_index_in_parent()) - end -end - ---[[-- Toggles the visible state of all the elements on a players top flow, effects all elements -@tparam LuaPlayer player the player that you want to toggle the top flow for -@tparam[opt] boolean state if given then the state will be set to this -@treturn boolean the new visible state of the top flow - -@usage-- Toggle your flow -Gui.toggle_top_flow(game.player) - -@usage-- Open your top flow -Gui.toggle_top_flow(game.player, true) - -]] -function Gui.toggle_top_flow(player, state) - -- Get the top flow, we need the parent as we want to toggle the outer frame - local top_flow = Gui.get_top_flow(player).parent --- @cast top_flow -nil - if state == nil then state = not top_flow.visible end - - -- Get the show button for the top flow - local left_flow = Gui.get_left_flow(player) - local show_button = left_flow.gui_core_buttons[show_top_flow] - - -- Change the visibility of the top flow and show top flow button - show_button.visible = not state - top_flow.visible = state - - return state -end - ---[[-- Get the element define that is in the top flow, use in events without an element refrence -@tparam LuaPlayer player the player that you want to get the element for -@tparam table element_define the element that you want to get -@treturn LuaGuiElement the gui element linked to this define for this player - -@usage-- Get your top element -local button = Gui.get_top_element(game.player, example_button) - -]] -function Gui.get_top_element(player, element_define) - local top_flow = Gui.get_top_flow(player) - return assert(top_flow[element_define.name], "Top element failed to load") -end - ---[[-- Toggles the state of a toolbar button for a given player, can be used to set the visual state -@tparam LuaPlayer player the player that you want to toggle the element for -@tparam table element_define the element that you want to toggle -@tparam[opt] boolean state with given will set the state, else state will be toggled -@treturn boolean the new visible state of the element - -@usage-- Toggle your example button -Gui.toggle_toolbar_button(game.player, toolbar_button) - -@usage-- Show your example button -Gui.toggle_toolbar_button(game.player, toolbar_button, true) - -]] -function Gui.toggle_toolbar_button(player, element_define, state) - local toolbar_button = Gui.get_top_element(player, element_define) - if state == nil then state = toolbar_button.style.name ~= Gui.top_flow_button_toggled_style end - Gui.toolbar_button_style(toolbar_button, state, toolbar_button.style.minimal_width) - element_define:raise_event{ - name = Gui.events.on_toolbar_button_toggled, - element = toolbar_button, - player = player, - state = state, - } - return state -end - ---[[-- Creates a button on the top flow with consistent styling -@tparam string sprite the sprite that you want to use on the button -@tparam ?string|Concepts.LocalizedString tooltip the tooltip that you want the button to have -@tparam[opt] function authenticator used to decide if the button should be visible to a player - -@usage-- Add a button to the toolbar -local toolbar_button = -Gui.left_toolbar_button('entity/inserter', 'Nothing to see here', function(player) - return player.admin -end) - -]] -function Gui.toolbar_button(sprite, tooltip, authenticator) - return Gui.element{ - type = "sprite-button", - sprite = sprite, - tooltip = tooltip, - style = Gui.top_flow_button_style, - name = Gui.unique_static_name, - } - :style{ - minimal_width = toolbar_button_size, - height = toolbar_button_size, - padding = -2, - } - :add_to_top_flow(authenticator) -end - ---[[-- Creates a toggle button on the top flow with consistent styling -@tparam string sprite the sprite that you want to use on the button -@tparam ?string|Concepts.LocalizedString tooltip the tooltip that you want the button to have -@tparam[opt] function authenticator used to decide if the button should be visible to a player - -@usage-- Add a button to the toolbar -local toolbar_button = -Gui.toolbar_toggle_button('entity/inserter', 'Nothing to see here', function(player) - return player.admin -end) -:on_event(Gui.events.on_toolbar_button_toggled, function(player, element, event) - game.print(table.inspect(event)) -end) - -]] -function Gui.toolbar_toggle_button(sprite, tooltip, authenticator) - local button = - Gui.element{ - type = "sprite-button", - sprite = sprite, - tooltip = tooltip, - style = Gui.top_flow_button_style, - name = Gui.unique_static_name, - } - :style{ - minimal_width = toolbar_button_size, - height = toolbar_button_size, - padding = -2, - } - :add_to_top_flow(authenticator) - - button:on_click(function(player, _, _) - Gui.toggle_toolbar_button(player, button) - end) - - return button -end diff --git a/exp_legacy/module/locale/en/expcore.cfg b/exp_legacy/module/locale/en/expcore.cfg index 509e1734..494c1ad9 100644 --- a/exp_legacy/module/locale/en/expcore.cfg +++ b/exp_legacy/module/locale/en/expcore.cfg @@ -6,12 +6,6 @@ game-message-unassign=__1__ has been unassigned from __2__ by __3__ reject-role=Invalid Role Name. reject-player-role=Player has a higher role. -[gui_util] -button_tooltip=Shows/hides the toolbar. - -[expcore-gui] -left-button-tooltip=Hide all open windows. - [expcore-data] description-preference=Allows you to set/get your data saving preference. description-data=Writes all your player data to a file on your computer. diff --git a/exp_legacy/module/locale/en/gui.cfg b/exp_legacy/module/locale/en/gui.cfg index bfc6f3d5..31faf20f 100644 --- a/exp_legacy/module/locale/en/gui.cfg +++ b/exp_legacy/module/locale/en/gui.cfg @@ -316,14 +316,6 @@ type-player=Player type-static=Static type-player-loop=Player loop -[toolbar] -main-caption=Toolbox -main-tooltip=Toolbox Settings\nUse the checkboxs to select facourites -reset=Reset All -toggle=Toggle Favourites -move-up=Move Up -move-down=Move Down - [research] msg=[color=255, 255, 255] Research completed at __1__ - [technology=__2__][/color] inf=[color=255, 255, 255] Research completed at __1__ - [technology=__2__] - __3__[/color] diff --git a/exp_legacy/module/locale/zh-CN/expcore.cfg b/exp_legacy/module/locale/zh-CN/expcore.cfg index 8d4c6d24..5f4c5f12 100644 --- a/exp_legacy/module/locale/zh-CN/expcore.cfg +++ b/exp_legacy/module/locale/zh-CN/expcore.cfg @@ -6,12 +6,6 @@ game-message-unassign=__1__ 已被 __3__ 取消指派到身分組 __2__ reject-role=無效的身分組。 reject-player-role=用戶已有更高的身分組。 -[gui_util] -button_tooltip=是否顯示工具欄。 - -[expcore-gui] -left-button-tooltip=關閉所有打開的視窗。 - [expcore-data] description-preference=允許寫入資料。 description-data=把用戶資料寫入電腦 diff --git a/exp_legacy/module/locale/zh-TW/expcore.cfg b/exp_legacy/module/locale/zh-TW/expcore.cfg index 8d4c6d24..5f4c5f12 100644 --- a/exp_legacy/module/locale/zh-TW/expcore.cfg +++ b/exp_legacy/module/locale/zh-TW/expcore.cfg @@ -6,12 +6,6 @@ game-message-unassign=__1__ 已被 __3__ 取消指派到身分組 __2__ reject-role=無效的身分組。 reject-player-role=用戶已有更高的身分組。 -[gui_util] -button_tooltip=是否顯示工具欄。 - -[expcore-gui] -left-button-tooltip=關閉所有打開的視窗。 - [expcore-data] description-preference=允許寫入資料。 description-data=把用戶資料寫入電腦 diff --git a/exp_legacy/module/module.json b/exp_legacy/module/module.json index 5652b929..2a2ec59e 100644 --- a/exp_legacy/module/module.json +++ b/exp_legacy/module/module.json @@ -7,6 +7,7 @@ ], "dependencies": { "clusterio": "*", - "exp_util": "*" + "exp_util": "*", + "exp_gui": "*" } } diff --git a/exp_legacy/module/modules/addons/tree-decon.lua b/exp_legacy/module/modules/addons/tree-decon.lua index e886941d..0fa81f11 100644 --- a/exp_legacy/module/modules/addons/tree-decon.lua +++ b/exp_legacy/module/modules/addons/tree-decon.lua @@ -1,10 +1,11 @@ --- Makes trees which are marked for decon "decay" quickly to allow faster building -- @addon Tree-Decon -local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local Storage = require("modules/exp_util/storage") +local Gui = require("modules/exp_gui") + +local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data -- Storage queue used to store trees that need to be removed, also cache for player roles @@ -34,13 +35,20 @@ end local HasEnabledDecon = PlayerData.Settings:combine("HasEnabledDecon") HasEnabledDecon:set_default(false) -Gui.toolbar_toggle_button("entity/tree-01", { "tree-decon.main-tooltip" }, function(player) - return Roles.player_allowed(player, "fast-tree-decon") +Gui.toolbar.create_button{ + name = "toggle-tree-decon", + sprite = "entity/tree-01", + tooltip = { "tree-decon.main-tooltip" }, + auto_toggle = true, + visible = function(player, _) + return Roles.player_allowed(player, "fast-tree-decon") + end +}:on_click(function(def, event, element) + local player = Gui.get_player(event) + local state = Gui.toolbar.get_button_toggled_state(def, player) + HasEnabledDecon:set(player, state) + player.print{ "tree-decon.toggle-msg", state and { "tree-decon.enabled" } or { "tree-decon.disabled" } } end) - :on_event(Gui.events.on_toolbar_button_toggled, function(player, _, event) - HasEnabledDecon:set(player, event.state) - player.print{ "tree-decon.toggle-msg", event.state and { "tree-decon.enabled" } or { "tree-decon.disabled" } } - end) -- Add trees to queue when marked, only allows simple entities and for players with role permission Event.add(defines.events.on_marked_for_deconstruction, function(event) diff --git a/exp_legacy/module/modules/control/spectate.lua b/exp_legacy/module/modules/control/spectate.lua index 371c014c..57616586 100644 --- a/exp_legacy/module/modules/control/spectate.lua +++ b/exp_legacy/module/modules/control/spectate.lua @@ -1,6 +1,7 @@ -local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local Storage = require("modules/exp_util/storage") -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") + +local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event ----- Locals ----- local follow_label -- Gui constructor @@ -88,7 +89,7 @@ function Public.stop_follow(player) Public.stop_spectate(player) end - Gui.destroy_if_valid(player.gui.screen[follow_label.name]) + Gui.destroy_if_valid(player.gui.screen.follow_label) following[player.index] = nil end @@ -102,19 +103,18 @@ end ----- Gui ----- --- Label used to show that the player is following, also used to allow esc to stop following --- @element follow_label -follow_label = - Gui.element(function(definition, parent, target) - Gui.destroy_if_valid(parent[definition.name]) +follow_label = Gui.element("follow-label") + :draw(function(def, parent, target) + Gui.destroy_if_valid(parent.follow_label) local label = parent.add{ type = "label", + name = "follow_label", style = "frame_title", caption = "Following " .. target.name .. ".\nClick here or press esc to stop following.", - name = definition.name, } - local player = Gui.get_player_from_element(parent) + local player = Gui.get_player(parent) local res = player.display_resolution label.location = { 0, res.height - 150 } label.style.width = res.width @@ -123,9 +123,10 @@ follow_label = return label end) - :static_name(Gui.unique_static_name) - :on_click(Public.stop_follow) - :on_close(function(player) + :on_click(function(def, player, element) + Public.stop_follow(player) + end) + :on_closed(function(def, player, element) -- Don't call set_controller during on_close as it invalidates the controller -- Setting an invalid position (as to not equal their current) will call stop_follow on the next tick following[player.index][3] = {} diff --git a/exp_legacy/module/modules/data/toolbar.lua b/exp_legacy/module/modules/data/toolbar.lua new file mode 100644 index 00000000..fbb96c2b --- /dev/null +++ b/exp_legacy/module/modules/data/toolbar.lua @@ -0,0 +1,31 @@ +local Gui = require("modules/exp_gui") +local PlayerData = require("modules.exp_legacy.expcore.player_data") + +-- Used to store the state of the toolbar when a player leaves +local ToolbarState = PlayerData.Settings:combine("ToolbarState") +ToolbarState:set_metadata{ + stringify = function() + return "Toolbar is saved on exit" + end, +} + +--- Uncompress the data to be more useable +ToolbarState:on_load(function(player_name, value) + -- If there is no value, do nothing + if value == nil then return end + -- Old format, we discard it [ string[], string[], string[], boolean ] + if type(value) ~= "string" then return end + + local decompressed = helpers.json_to_table(assert(helpers.decode_string(value), "Failed String Decode")) + local player = assert(game.get_player(player_name)) + Gui.toolbar.set_state(player, decompressed --[[ @as ExpGui.ToolbarState ]]) + + return nil -- We don't save the state, use Gui.toolbar.get_state +end) + +--- Save the current state of the players toolbar menu +ToolbarState:on_save(function(player_name, _) + local player = assert(game.get_player(player_name)) + local value = Gui.toolbar.get_state(player) + return helpers.encode_string(helpers.table_to_json(value)) +end) diff --git a/exp_legacy/module/modules/gui/_role_updates.lua b/exp_legacy/module/modules/gui/_role_updates.lua new file mode 100644 index 00000000..75273b57 --- /dev/null +++ b/exp_legacy/module/modules/gui/_role_updates.lua @@ -0,0 +1,7 @@ +local Gui = require("modules/exp_gui") +local Roles = require("modules.exp_legacy.expcore.roles") +local Event = require("modules/exp_legacy/utils/event") + +--- @diagnostic disable invisible +Event.add(Roles.events.on_role_assigned, Gui._ensure_consistency) +Event.add(Roles.events.on_role_unassigned, Gui._ensure_consistency) diff --git a/exp_legacy/module/modules/gui/autofill.lua b/exp_legacy/module/modules/gui/autofill.lua index a435ebad..f0905230 100644 --- a/exp_legacy/module/modules/gui/autofill.lua +++ b/exp_legacy/module/modules/gui/autofill.lua @@ -4,10 +4,10 @@ @alias autofill ]] +local Storage = require("modules/exp_util/storage") local FlyingText = require("modules/exp_util/flying_text") -local Gui = require("modules.exp_legacy.expcore.gui") -- @dep expcore.gui -local Roles = require("modules.exp_legacy.expcore.roles") -- @dep expcore.gui -local Storage = require("modules/exp_util/storage") -- @dep utils.global +local Gui = require("modules/exp_gui") +local Roles = require("modules.exp_legacy.expcore.roles") local config = require("modules.exp_legacy.config.gui.autofill") -- @dep config.gui.autofill local Event = require("modules/exp_legacy/utils/event") -- @dep utils.event @@ -25,17 +25,19 @@ end --- Toggle entity section visibility -- @element toggle_item_button -local toggle_section = - Gui.element{ +local toggle_section = Gui.element("autofill_toggle_section") + :draw{ type = "sprite-button", sprite = "utility/expand", tooltip = { "autofill.toggle-section-tooltip" }, style = "frame_action_button", - name = Gui.unique_static_name, + name = Gui.property_from_name, } - :style(Gui.sprite_style(20)) - :on_click(function(_, element, _) - local header_flow = element.parent + :style(Gui.styles.sprite{ + size = 20 + }) + :on_click(function(def, player, element) + local header_flow = assert(element.parent) local flow_name = header_flow.caption local flow = header_flow.parent.parent[flow_name] if Gui.toggle_visible_state(flow) then @@ -50,8 +52,8 @@ local toggle_section = --- Toggle enitity button, used for toggling autofill for the specific entity -- All entity autofill settings will be ignored if its disabled -- @element entity_toggle -local entity_toggle = - Gui.element(function(_, parent, entity_name) +local entity_toggle = Gui.element("entity_toggle") + :draw(function(_, parent, entity_name) return parent.add{ type = "sprite-button", sprite = "utility/confirm_slot", @@ -59,8 +61,10 @@ local entity_toggle = style = "shortcut_bar_button_green", } end) - :style(Gui.sprite_style(22)) - :on_click(function(player, element, _) + :style(Gui.styles.sprite{ + size = 22 + }) + :on_click(function(def, player, element) local entity_name = string.match(element.parent.parent.name, "(.*)%-header") if not autofill_player_settings[player.name] then return end local setting = autofill_player_settings[player.name][entity_name] @@ -75,7 +79,7 @@ local entity_toggle = element.style = "shortcut_bar_button_green" end -- Correct the button size - local style = element.style --[[@as LuaStyle]] + local style = element.style style.padding = -2 style.height = 22 style.width = 22 @@ -83,18 +87,17 @@ local entity_toggle = --- Draw a section header and main scroll -- @element autofill_section_container -local section = - Gui.element(function(definition, parent, section_name, table_size) +local section = Gui.element("autofill_section") + :draw(function(def, parent, section_name, table_size) -- Draw the header for the section - local header = Gui.header( - parent, - { "autofill.toggle-section-caption", rich_img("item", section_name), { "entity-name." .. section_name } }, - { "autofill.toggle-section-tooltip" }, - true, - section_name .. "-header" - ) + local header = Gui.elements.header(parent, { + name = section_name .. "-header", + caption = { "autofill.toggle-section-caption", rich_img("item", section_name), { "entity-name." .. section_name } }, + tooltip = { "autofill.toggle-section-tooltip" }, + label_name = "label", + }) - definition:triggers_events(header.parent.header_label) + def:link_element(header.parent.label) -- Right aligned button to toggle the section header.caption = section_name @@ -109,17 +112,17 @@ local section = section_table.visible = false - return definition:no_events(section_table) + return def:unlink_element(section_table) end) - :on_click(function(_, element, event) + :on_click(function(def, player, element, event) event.element = element.parent.alignment[toggle_section.name] toggle_section:raise_event(event) end) --- Toggle item button, used for toggling autofill for the specific item -- @element toggle_item_button -local toggle_item_button = - Gui.element(function(_, parent, item) +local toggle_item_button = Gui.element("toggle_item_button") + :draw(function(_, parent, item) return parent.add{ type = "sprite-button", sprite = "item/" .. item.name, @@ -127,8 +130,11 @@ local toggle_item_button = style = "shortcut_bar_button_red", } end) - :style(Gui.sprite_style(32, nil, { right_margin = -3 })) - :on_click(function(player, element) + :style(Gui.styles.sprite{ + size = 32, + right_margin = -3, + }) + :on_click(function(def, player, element) local item_name = element.parent.tooltip local entity_name = element.parent.parent.parent.name if not autofill_player_settings[player.name] then return end @@ -144,7 +150,7 @@ local toggle_item_button = element.style = "shortcut_bar_button_green" end -- Correct the button size - local style = element.style --[[@as LuaStyle]] + local style = element.style style.right_margin = -3 style.padding = -2 style.height = 32 @@ -153,8 +159,8 @@ local toggle_item_button = --- Amount text field for a autofill item -- @element amount_textfield -local amount_textfield = - Gui.element(function(_, parent, item) +local amount_textfield = Gui.element("amount_textfield") + :draw(function(_, parent, item) return parent.add{ type = "textfield", text = item.amount, @@ -170,7 +176,7 @@ local amount_textfield = height = 31, padding = -2, } - :on_text_changed(function(player, element, _) + :on_text_changed(function(def, player, element) local value = tonumber(element.text) if not value then value = 0 end local clamped = math.clamp(value, 0, 1000) @@ -183,7 +189,7 @@ local amount_textfield = if not item then return end item.amount = clamped if clamped ~= value then - element.text = clamped + element.text = tostring(clamped) player.print{ "autofill.invalid", item.amount, rich_img("item", item.name), rich_img("entity", entity_name) } return end @@ -191,8 +197,8 @@ local amount_textfield = --- Autofill setting, contains a button and a textbox -- @element add_autofill_setting -local add_autofill_setting = - Gui.element(function(_, parent, item) +local add_autofill_setting = Gui.element("add_autofill_setting") + :draw(function(_, parent, item) local toggle_flow = parent.add{ type = "flow", name = "toggle-setting-" .. item.name, tooltip = item.name } local amount_flow = parent.add{ type = "flow", name = "amount-setting-" .. item.name, tooltip = item.name } toggle_flow.style.padding = 0 @@ -203,8 +209,8 @@ local add_autofill_setting = --- Autofill setting empty, contains filler button and textfield gui elements -- @element add_empty_autofill_setting -local add_empty_autofill_setting = - Gui.element(function(_, parent) +local add_empty_autofill_setting = Gui.element("add_empty_autofill_setting") + :draw(function(_, parent) local toggle_element = parent.add{ type = "sprite-button", } @@ -223,19 +229,19 @@ local add_empty_autofill_setting = --- Main gui container for the left flow -- @element autofill_container -autofill_container = - Gui.element(function(definition, parent) +autofill_container = Gui.element("autofill_container") + :draw(function(def, parent) -- Draw the internal container - local container = Gui.container(parent, definition.name) + local container = Gui.elements.container(parent) -- Draw the scroll container - local scroll_table = Gui.scroll_table(container, 400, 1, "autofill-scroll-table") + local scroll_table = Gui.elements.scroll_table(container, 400, 1, "autofill-scroll-table") -- Set the scroll panel to always show the scrollbar (not doing this will result in a changing gui size) scroll_table.parent.vertical_scroll_policy = "always" -- Scroll panel has by default padding scroll_table.parent.style.padding = 0 -- Remove the default gap that is added in a table between elements scroll_table.style.vertical_spacing = 0 - -- Center the first collumn in the table + -- Center the first column in the table scroll_table.style.column_alignments[1] = "center" -- Loop over each default entity config for _, setting in pairs(config.default_entities) do @@ -245,7 +251,7 @@ autofill_container = local entity_table = section(scroll_table, setting.entity, 3) -- Add some padding around the table entity_table.style.padding = 3 - -- Make sure each collumn is alignment top center + -- Make sure each column is alignment top center entity_table.style.column_alignments[1] = "top-center" entity_table.style.column_alignments[2] = "top-center" entity_table.style.column_alignments[3] = "top-center" @@ -284,14 +290,18 @@ autofill_container = -- Return the external container return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() ---- Button on the top flow used to toggle autofill container --- @element autofill_toggle -Gui.left_toolbar_button(config.icon, { "autofill.main-tooltip" }, autofill_container, function(player) - return Roles.player_allowed(player, "gui/autofill") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(autofill_container, false) +Gui.toolbar.create_button{ + name = "autofill_toggle", + left_element = autofill_container, + sprite = config.icon, + tooltip = { "autofill.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/autofill") + end +} --- When a player is created make sure they have the default autofill settings Event.add(defines.events.on_player_created, function(event) @@ -321,6 +331,7 @@ local function entity_build(event) -- Get the inventory of the player local player_inventory = player.get_main_inventory() --- @cast player_inventory -nil + local offset = { x = 0, y = 0 } -- Loop over all possible items to insert into the entity for _, item in pairs(entity_settings.items) do -- Check if the item is enabled or goto next item @@ -330,28 +341,30 @@ local function entity_build(event) local entity_inventory = entity.get_inventory(item.inv) if not entity_inventory then goto end_item end - local preferd_amount = item.amount + local preferred_amount = item.amount local item_amount = player_inventory.get_item_count(item.name) if item_amount ~= 0 then local inserted - local color = { r = 0, g = 255, b = 0, a = 1 } - if item_amount >= preferd_amount then + local color = { r = 0, g = 255, b = 0, a = 255 } + if item_amount >= preferred_amount then -- Can item be inserted? no, goto next item! - if not entity_inventory.can_insert{ name = item.name, count = preferd_amount } then + if not entity_inventory.can_insert{ name = item.name, count = preferred_amount } then goto end_item end - inserted = entity_inventory.insert{ name = item.name, count = preferd_amount } + inserted = entity_inventory.insert{ name = item.name, count = preferred_amount } else inserted = entity_inventory.insert{ name = item.name, count = item_amount } - color = { r = 255, g = 165, b = 0, a = 1 } + color = { r = 255, g = 165, b = 0, a = 255 } end player_inventory.remove{ name = item.name, count = inserted } FlyingText.create_above_entity{ target_entity = entity, text = { "autofill.inserted", inserted, rich_img("item", item.name), rich_img("entity", entity.name) }, + offset = offset, player = player, color = color, } + offset.y = offset.y - 0.33 end ::end_item:: end diff --git a/exp_legacy/module/modules/gui/bonus.lua b/exp_legacy/module/modules/gui/bonus.lua index e5c3022c..ac204757 100644 --- a/exp_legacy/module/modules/gui/bonus.lua +++ b/exp_legacy/module/modules/gui/bonus.lua @@ -3,9 +3,9 @@ @alias bonus_container ]] -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui -local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles +local Gui = require("modules/exp_gui") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event +local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local config = require("modules.exp_legacy.config.bonus") --- @dep config.bonus local vlayer = require("modules.exp_legacy.modules.control.vlayer") local format_number = require("util").format_number --- @dep util @@ -13,9 +13,12 @@ local format_number = require("util").format_number --- @dep util local bonus_container local bonus_gui_control_pts_a_count -local function bonus_gui_pts_needed(player) - local frame = Gui.get_left_element(player, bonus_container) - local disp = frame.container["bonus_st_2"].disp.table +--- @param player LuaPlayer +--- @param container LuaGuiElement? +--- @return number +local function bonus_gui_pts_needed(player, container) + container = container or Gui.get_left_element(bonus_container, player) + local disp = container.frame["bonus_st_2"].disp.table local total = 0 for k, v in pairs(config.conversion) do @@ -31,6 +34,7 @@ local function bonus_gui_pts_needed(player) return total end +--- @param player LuaPlayer local function apply_bonus(player) if not Roles.player_allowed(player, "gui/bonus") then for k, v in pairs(config.player_bonus) do @@ -50,8 +54,8 @@ local function apply_bonus(player) return end - local frame = Gui.get_left_element(player, bonus_container) - local disp = frame.container["bonus_st_2"].disp.table + local container = Gui.get_left_element(bonus_container, player) + local disp = container.frame["bonus_st_2"].disp.table for k, v in pairs(config.conversion) do player[v] = disp["bonus_display_" .. k .. "_slider"].slider_value @@ -81,8 +85,8 @@ local function apply_periodic_bonus(player) return end - local frame = Gui.get_left_element(player, bonus_container) - local disp = frame.container["bonus_st_2"].disp.table + local container = Gui.get_left_element(bonus_container, player) + local disp = container.frame["bonus_st_2"].disp.table if vlayer.get_statistics()["energy_sustained"] > 0 then local armor = player.get_inventory(defines.inventory.character_armor) @@ -109,20 +113,20 @@ end --- Control label for the bonus points available -- @element bonus_gui_control_pts_a -local bonus_gui_control_pts_a = - Gui.element{ +local bonus_gui_control_pts_a = Gui.element("bonus_gui_control_pts_a") + :draw{ type = "label", - name = "bonus_control_pts_a", + name = Gui.property_from_name, caption = { "bonus.control-pts-a" }, style = "heading_2_label", }:style{ width = config.gui_display_width["half"], } -bonus_gui_control_pts_a_count = - Gui.element{ +local bonus_gui_control_pts_a_count = Gui.element("bonus_gui_control_pts_a_count") + :draw{ type = "label", - name = "bonus_control_pts_a_count", + name = Gui.property_from_name, caption = config.pts.base, style = "heading_2_label", }:style{ @@ -131,20 +135,20 @@ bonus_gui_control_pts_a_count = --- Control label for the bonus points needed -- @element bonus_gui_control_pts_n -local bonus_gui_control_pts_n = - Gui.element{ +local bonus_gui_control_pts_n = Gui.element("bonus_gui_control_pts_n") + :draw{ type = "label", - name = "bonus_control_pts_n", + name = Gui.property_from_name, caption = { "bonus.control-pts-n" }, style = "heading_2_label", }:style{ width = config.gui_display_width["half"], } -local bonus_gui_control_pts_n_count = - Gui.element{ +local bonus_gui_control_pts_n_count = Gui.element("bonus_gui_control_pts_n_count") + :draw{ type = "label", - name = "bonus_control_pts_n_count", + name = Gui.property_from_name, caption = "0", style = "heading_2_label", }:style{ @@ -153,20 +157,20 @@ local bonus_gui_control_pts_n_count = --- Control label for the bonus points remaining -- @element bonus_gui_control_pts_r -local bonus_gui_control_pts_r = - Gui.element{ +local bonus_gui_control_pts_r = Gui.element("bonus_gui_control_pts_r") + :draw{ type = "label", - name = "bonus_control_pts_r", + name = Gui.property_from_name, caption = { "bonus.control-pts-r" }, style = "heading_2_label", }:style{ width = config.gui_display_width["half"], } -local bonus_gui_control_pts_r_count = - Gui.element{ +local bonus_gui_control_pts_r_count = Gui.element("bonus_gui_control_pts_r_count") + :draw{ type = "label", - name = "bonus_control_pts_r_count", + name = Gui.property_from_name, caption = "0", style = "heading_2_label", }:style{ @@ -175,16 +179,16 @@ local bonus_gui_control_pts_r_count = --- A button used for pts calculations -- @element bonus_gui_control_refresh -local bonus_gui_control_reset = - Gui.element{ +local bonus_gui_control_reset = Gui.element("bonus_gui_control_reset") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "bonus.control-reset" }, }:style{ width = config.gui_display_width["half"], - }:on_click(function(player, element, _) - local frame = Gui.get_left_element(player, bonus_container) - local disp = frame.container["bonus_st_2"].disp.table + }:on_click(function(def, player, element) + local container = Gui.get_left_element(bonus_container, player) + local disp = container.frame["bonus_st_2"].disp.table for k, v in pairs(config.conversion) do local s = "bonus_display_" .. k .. "_slider" @@ -208,14 +212,14 @@ local bonus_gui_control_reset = --- A button used for pts apply -- @element bonus_gui_control_apply -local bonus_gui_control_apply = - Gui.element{ +local bonus_gui_control_apply = Gui.element("bonus_gui_control_apply") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "bonus.control-apply" }, }:style{ width = config.gui_display_width["half"], - }:on_click(function(player, element, _) + }:on_click(function(def, player, element) local n = bonus_gui_pts_needed(player) element.parent[bonus_gui_control_pts_n_count.name].caption = n local r = tonumber(element.parent[bonus_gui_control_pts_a_count.name].caption) - n @@ -228,10 +232,10 @@ local bonus_gui_control_apply = --- A vertical flow containing all the bonus control -- @element bonus_control_set -local bonus_control_set = - Gui.element(function(_, parent, name) +local bonus_control_set = Gui.element("bonus_control_set") + :draw(function(_, parent, name) local bonus_set = parent.add{ type = "flow", direction = "vertical", name = name } - local disp = Gui.scroll_table(bonus_set, config.gui_display_width["half"] * 2, 2, "disp") + local disp = Gui.elements.scroll_table(bonus_set, config.gui_display_width["half"] * 2, 2, "disp") bonus_gui_control_pts_a(disp) bonus_gui_control_pts_a_count(disp) @@ -250,8 +254,8 @@ local bonus_control_set = --- Display group -- @element bonus_gui_slider -local bonus_gui_slider = - Gui.element(function(_definition, parent, name, caption, tooltip, bonus) +local bonus_gui_slider = Gui.element("bonus_gui_slider") + :draw(function(def, parent, name, caption, tooltip, bonus) local label = parent.add{ type = "label", caption = caption, @@ -294,7 +298,7 @@ local bonus_gui_slider = return slider end) - :on_value_changed(function(player, element, _event) + :on_value_changed(function(def, player, element) if element.tags.is_percentage then element.parent[element.tags.counter].caption = format_number(element.slider_value * 100, false) .. " %" else @@ -302,18 +306,18 @@ local bonus_gui_slider = end local r = bonus_gui_pts_needed(player) - local frame = Gui.get_left_element(player, bonus_container) - local disp = frame.container["bonus_st_1"].disp.table + local container = Gui.get_left_element(bonus_container, player) + local disp = container.frame["bonus_st_1"].disp.table disp[bonus_gui_control_pts_n_count.name].caption = r disp[bonus_gui_control_pts_r_count.name].caption = tonumber(disp[bonus_gui_control_pts_a_count.name].caption) - r end) --- A vertical flow containing all the bonus data -- @element bonus_data_set -local bonus_data_set = - Gui.element(function(_, parent, name) +local bonus_data_set = Gui.element("bonus_data_set") + :draw(function(_, parent, name) local bonus_set = parent.add{ type = "flow", direction = "vertical", name = name } - local disp = Gui.scroll_table(bonus_set, config.gui_display_width["half"] * 2, 3, "disp") + local disp = Gui.elements.scroll_table(bonus_set, config.gui_display_width["half"] * 2, 3, "disp") for k, v in pairs(config.conversion) do bonus_gui_slider(disp, "bonus_display_" .. k, { "bonus.display-" .. k }, { "bonus.display-" .. k .. "-tooltip" }, config.player_bonus[v]) @@ -327,32 +331,34 @@ local bonus_data_set = --- The main container for the bonus gui -- @element bonus_container -bonus_container = - Gui.element(function(definition, parent) - local player = Gui.get_player_from_element(parent) - local container = Gui.container(parent, definition.name, config.gui_display_width["half"] * 2) +bonus_container = Gui.element("bonus_container") + :draw(function(def, parent) + local player = Gui.get_player(parent) + local container = Gui.elements.container(parent, config.gui_display_width["half"] * 2) bonus_control_set(container, "bonus_st_1") bonus_data_set(container, "bonus_st_2") - local frame = Gui.get_left_element(player, bonus_container) - local disp = frame.container["bonus_st_1"].disp.table - local n = bonus_gui_pts_needed(player) + local disp = container["bonus_st_1"].disp.table + local n = bonus_gui_pts_needed(player, container.parent) disp[bonus_gui_control_pts_n_count.name].caption = n local r = tonumber(disp[bonus_gui_control_pts_a_count.name].caption) - n disp[bonus_gui_control_pts_r_count.name].caption = r - apply_bonus(player) return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() ---- Button on the top flow used to toggle the bonus container --- @element toggle_left_element -Gui.left_toolbar_button("item/exoskeleton-equipment", { "bonus.main-tooltip" }, bonus_container, function(player) - return Roles.player_allowed(player, "gui/bonus") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(bonus_container, false) +Gui.toolbar.create_button{ + name = "bonus_toggle", + left_element = bonus_container, + sprite = "item/exoskeleton-equipment", + tooltip = { "bonus.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/bonus") + end +} Event.add(defines.events.on_player_created, function(event) if event.player_index ~= 1 then @@ -379,8 +385,8 @@ end) --- When a player respawns re-apply bonus Event.add(defines.events.on_player_respawned, function(event) local player = game.players[event.player_index] - local frame = Gui.get_left_element(player, bonus_container) - local disp = frame.container["bonus_st_1"].disp.table + local container = Gui.get_left_element(bonus_container, player) + local disp = container.frame["bonus_st_1"].disp.table local n = bonus_gui_pts_needed(player) disp[bonus_gui_control_pts_n_count.name].caption = n local r = tonumber(disp[bonus_gui_control_pts_a_count.name].caption) - n diff --git a/exp_legacy/module/modules/gui/debug/event_view.lua b/exp_legacy/module/modules/gui/debug/event_view.lua index f358d77b..b2d84ded 100644 --- a/exp_legacy/module/modules/gui/debug/event_view.lua +++ b/exp_legacy/module/modules/gui/debug/event_view.lua @@ -1,4 +1,5 @@ local Event = require("modules/exp_legacy/utils/event") +local Storage = require("modules/exp_util/storage") local Gui = require("modules.exp_legacy.utils.gui") local Model = require("modules.exp_legacy.modules.gui.debug.model") @@ -21,6 +22,11 @@ local checkbox_name = Gui.uid_name() local filter_name = Gui.uid_name() local clear_filter_name = Gui.uid_name() +local storage = {} +Storage.register(storage, function(tbl) + storage = tbl +end) + -- storage tables local enabled = {} local last_events = {} diff --git a/exp_legacy/module/modules/gui/debug/expcore_gui_view.lua b/exp_legacy/module/modules/gui/debug/expcore_gui_view.lua index baf3d352..366157b3 100644 --- a/exp_legacy/module/modules/gui/debug/expcore_gui_view.lua +++ b/exp_legacy/module/modules/gui/debug/expcore_gui_view.lua @@ -1,6 +1,9 @@ -local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui -local ExpGui = require("modules.exp_legacy.expcore.gui") +local ExpElement = require("modules/exp_gui/prototype") +local ExpData = require("modules/exp_gui/data") +local ExpIter = require("modules/exp_gui/iter") local Color = require("modules/exp_util/include/color") + +local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model local dump = Model.dump @@ -24,9 +27,10 @@ function Public.show(container) local left_panel_style = left_panel.style left_panel_style.width = 300 - for element_id, file_path in pairs(ExpGui.file_paths) do - local header = left_panel.add{ type = "flow" }.add{ type = "label", name = header_name, caption = element_id .. " - " .. file_path } - Gui.set_data(header, element_id) + --- @diagnostic disable-next-line invisible + for element_name in pairs(ExpElement._elements) do + local header = left_panel.add{ type = "flow" }.add{ type = "label", name = header_name, caption = element_name } + Gui.set_data(header, element_name) end local right_flow = main_flow.add{ type = "flow", direction = "vertical" } @@ -70,7 +74,7 @@ Gui.on_click( header_name, function(event) local element = event.element - local element_id = Gui.get_data(element) + local element_name = Gui.get_data(element) local left_panel = element.parent.parent local data = Gui.get_data(left_panel) @@ -85,10 +89,23 @@ Gui.on_click( element.style.font_color = Color.orange data.selected_header = element - input_text_box.text = concat{ "Gui.defines[", element_id, "]" } + input_text_box.text = concat{ "ExpElement._elements[\"", element_name, "\"]" } input_text_box.style.font_color = Color.black - local content = dump(ExpGui.debug_info[element_id]) or "nil" + --- @diagnostic disable-next-line invisible + local define = ExpElement._elements[element_name] + local content = dump({ + --- @diagnostic disable-next-line invisible + debug = define._debug, + --- @diagnostic disable-next-line invisible + has_handlers = define._has_handlers, + --- @diagnostic disable-next-line invisible + track_elements = define._track_elements, + --- @diagnostic disable-next-line invisible + elements = ExpIter._scopes[element_name], + --- @diagnostic disable-next-line invisible + data = ExpData._scopes[element_name]._raw, + }) or "nil" right_panel.text = content end ) diff --git a/exp_legacy/module/modules/gui/debug/model.lua b/exp_legacy/module/modules/gui/debug/model.lua index cb867f1c..f349dcf4 100644 --- a/exp_legacy/module/modules/gui/debug/model.lua +++ b/exp_legacy/module/modules/gui/debug/model.lua @@ -1,7 +1,6 @@ local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui +local ExpUtil = require("modules/exp_util") -local gui_names = Gui.names -local type = type local concat = table.concat local inspect = table.inspect local pcall = pcall @@ -10,76 +9,7 @@ local rawset = rawset local Public = {} -local LuaObject = { "{", nil, ", name = '", nil, "'}" } -local LuaPlayer = { "{LuaPlayer, name = '", nil, "', index = ", nil, "}" } -local LuaEntity = { "{LuaEntity, name = '", nil, "', unit_number = ", nil, "}" } -local LuaGuiElement = { "{LuaGuiElement, name = '", nil, "'}" } - -local function get(obj, prop) - return obj[prop] -end - -local function get_name_safe(obj) - local s, r = pcall(get, obj, "name") - if not s then - return "nil" - else - return r or "nil" - end -end - -local function get_lua_object_type_safe(obj) - local s, r = pcall(get, obj, "help") - - if not s then - return - end - - return r():match("Lua%a+") -end - -local function inspect_process(item) - if type(item) ~= "table" or type(item.__self) ~= "userdata" then - return item - end - - local suc, valid = pcall(get, item, "valid") - if not suc then - -- no 'valid' property - return get_lua_object_type_safe(item) or "{NoHelp LuaObject}" - end - - if not valid then - return "{Invalid LuaObject}" - end - - local obj_type = get_lua_object_type_safe(item) - if not obj_type then - return "{NoHelp LuaObject}" - end - - if obj_type == "LuaPlayer" then - LuaPlayer[2] = item.name or "nil" - LuaPlayer[4] = item.index or "nil" - - return concat(LuaPlayer) - elseif obj_type == "LuaEntity" then - LuaEntity[2] = item.name or "nil" - LuaEntity[4] = item.unit_number or "nil" - - return concat(LuaEntity) - elseif obj_type == "LuaGuiElement" then - local name = item.name - LuaGuiElement[2] = gui_names and gui_names[name] or name or "nil" - - return concat(LuaGuiElement) - else - LuaObject[2] = obj_type - LuaObject[4] = get_name_safe(item) - - return concat(LuaObject) - end -end +local inspect_process = ExpUtil.safe_value local inspect_options = { process = inspect_process } function Public.dump(data) diff --git a/exp_legacy/module/modules/gui/landfill.lua b/exp_legacy/module/modules/gui/landfill.lua index 0e6415ab..bbc6061d 100644 --- a/exp_legacy/module/modules/gui/landfill.lua +++ b/exp_legacy/module/modules/gui/landfill.lua @@ -4,7 +4,7 @@ @alias landfill_container ]] -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles @@ -93,8 +93,10 @@ for i, map in ipairs(curves) do end end +--- @param blueprint LuaItemStack +--- @return table local function landfill_gui_add_landfill(blueprint) - local entities = blueprint.get_blueprint_entities() + local entities = assert(blueprint.get_blueprint_entities()) local tile_index = 0 local new_tiles = {} @@ -164,22 +166,26 @@ local function landfill_gui_add_landfill(blueprint) return { tiles = new_tiles } end --- @element toolbar_button -Gui.toolbar_button("item/landfill", { "landfill.main-tooltip" }, function(player) - return Roles.player_allowed(player, "gui/landfill") -end) - :on_click(function(player, _, _) - if player.cursor_stack and player.cursor_stack.valid_for_read then - if player.cursor_stack.type == "blueprint" and player.cursor_stack.is_blueprint_setup() then - local modified = landfill_gui_add_landfill(player.cursor_stack) +--- Add the toolbar button +Gui.toolbar.create_button{ + name = "landfill", + sprite = "item/landfill", + tooltip = { "landfill.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/landfill") + end +}:on_click(function(def, player, element) + if player.cursor_stack and player.cursor_stack.valid_for_read then + if player.cursor_stack.type == "blueprint" and player.cursor_stack.is_blueprint_setup() then + local modified = landfill_gui_add_landfill(player.cursor_stack) - if modified and next(modified.tiles) then - player.cursor_stack.set_blueprint_tiles(modified.tiles) - end + if modified and next(modified.tiles) then + player.cursor_stack.set_blueprint_tiles(modified.tiles) end - else - player.print{ "landfill.cursor-none" } end - end) + else + player.print{ "landfill.cursor-none" } + end +end) Event.add(defines.events.on_player_joined_game, landfill_init) diff --git a/exp_legacy/module/modules/gui/module.lua b/exp_legacy/module/modules/gui/module.lua index 5b7e3300..edbd3dd0 100644 --- a/exp_legacy/module/modules/gui/module.lua +++ b/exp_legacy/module/modules/gui/module.lua @@ -1,8 +1,8 @@ ---- module inserter -- @gui Module +local Gui = require("modules/exp_gui") local AABB = require("modules/exp_util/aabb") -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local config = require("modules.exp_legacy.config.module") --- @dep config.module @@ -93,8 +93,8 @@ end Selection.on_selection(SelectionModuleArea, function(event) local area = AABB.expand(event.area) local player = game.players[event.player_index] - local frame = Gui.get_left_element(player, module_container) --- @type LuaGuiElement - local scroll_table = frame.container.scroll.table + local container = Gui.get_left_element(module_container, player) + local scroll_table = container.frame.scroll.table -- Create an inventory with three upgrade planners local inventory = game.create_inventory(3) @@ -205,8 +205,8 @@ end) --- @param player LuaPlayer --- @param element_name string local function row_set(player, element_name) - local frame = Gui.get_left_element(player, module_container) --[[ @as LuaGuiElement ]] - local scroll_table = frame.container.scroll.table + local container = Gui.get_left_element(module_container, player) + local scroll_table = container.frame.scroll.table local machine_name = scroll_table[element_name .. "0"].elem_value --[[ @as string ]] if machine_name then @@ -247,12 +247,12 @@ local function row_set(player, element_name) end end -local button_apply = - Gui.element{ +local button_apply = Gui.element("button_apply") + :draw{ type = "button", caption = { "module.apply" }, style = "button", - }:on_click(function(player) + }:on_click(function(def, player, element) if Selection.is_selecting(player, SelectionModuleArea) then Selection.stop(player) else @@ -260,13 +260,15 @@ local button_apply = end end) -module_container = - Gui.element(function(definition, parent) - local container = Gui.container(parent, definition.name, (config.module_slots_per_row + 2) * 36) - Gui.header(container, "Module Inserter", "", true) +module_container = Gui.element("module_container") + :draw(function(definition, parent) + local container = Gui.elements.container(parent, (config.module_slots_per_row + 2) * 36) + Gui.elements.header(container, { + caption = "Module Inserter", + }) local slots_per_row = config.module_slots_per_row + 1 - local scroll_table = Gui.scroll_table(container, (config.module_slots_per_row + 2) * 36, slots_per_row) + local scroll_table = Gui.elements.scroll_table(container, (config.module_slots_per_row + 2) * 36, slots_per_row, "scroll") for i = 1, config.default_module_row_count do scroll_table.add{ @@ -301,12 +303,18 @@ module_container = return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() -Gui.left_toolbar_button("item/productivity-module-3", { "module.main-tooltip" }, module_container, function(player) - return Roles.player_allowed(player, "gui/module") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(module_container, false) +Gui.toolbar.create_button{ + name = "module_toggle", + left_element = module_container, + sprite = "item/productivity-module-3", + tooltip = { "module.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/module") + end +} --- @param event EventData.on_gui_elem_changed Event.add(defines.events.on_gui_elem_changed, function(event) diff --git a/exp_legacy/module/modules/gui/player-list.lua b/exp_legacy/module/modules/gui/player-list.lua index 0cb3cf7d..486f022f 100644 --- a/exp_legacy/module/modules/gui/player-list.lua +++ b/exp_legacy/module/modules/gui/player-list.lua @@ -6,7 +6,7 @@ -- luacheck:ignore 211/Colors local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event @@ -23,20 +23,20 @@ config.set_datastores(SelectedPlayer, SelectedAction) --- Button used to open the action bar -- @element open_action_bar -local open_action_bar = - Gui.element{ +local open_action_bar = Gui.element("open_action_bar") + :draw{ type = "sprite-button", sprite = "utility/expand_dots", tooltip = { "player-list.open-action-bar" }, style = "frame_button", - name = Gui.unique_static_name, + name = Gui.property_from_name, } :style{ padding = -2, width = 8, height = 14, } - :on_click(function(player, element, _) + :on_click(function(def, player, element) local selected_player_name = element.parent.name local old_selected_player_name = SelectedPlayer:get(player) if selected_player_name == old_selected_player_name then @@ -48,30 +48,40 @@ local open_action_bar = --- Button used to close the action bar -- @element close_action_bar -local close_action_bar = - Gui.element{ +local close_action_bar = Gui.element("close_action_bar") + :draw{ type = "sprite-button", sprite = "utility/close_black", tooltip = { "player-list.close-action-bar" }, style = "slot_sized_button_red", } - :style(Gui.sprite_style(30, -1, { top_margin = -1, right_margin = -1 })) - :on_click(function(player, _) + :style(Gui.styles.sprite{ + size = 20, + padding = -1, + top_margin = -1, + right_margin = -1, + }) + :on_click(function(def, player, element) SelectedPlayer:remove(player) SelectedAction:remove(player) end) --- Button used to confirm a reason -- @element reason_confirm -local reason_confirm = - Gui.element{ +local reason_confirm = Gui.element("reason_confirm") + :draw{ type = "sprite-button", sprite = "utility/confirm_slot", tooltip = { "player-list.reason-confirm" }, style = "slot_sized_button_green", } - :style(Gui.sprite_style(30, -1, { left_margin = -2, right_margin = -1 })) - :on_click(function(player, element) + :style(Gui.styles.sprite{ + size = 30, + padding = -1, + left_margin = -2, + right_margin = -1, + }) + :on_click(function(def, player, element) local reason = element.parent.entry.text local action_name = SelectedAction:get(player) local reason_callback = config.buttons[action_name].reason_callback @@ -84,8 +94,8 @@ local reason_confirm = --- Set of elements that are used to make up a row of the player table -- @element add_player_base -local add_player_base = - Gui.element(function(_, parent, player_data) +local add_player_base = Gui.element("add_player_base") + :draw(function(_, parent, player_data) -- Add the button to open the action bar local toggle_action_bar_flow = parent.add{ type = "flow", name = player_data.name } open_action_bar(toggle_action_bar_flow) @@ -101,7 +111,7 @@ local add_player_base = player_name.style.font_color = player_data.chat_color -- Add the time played label - local alignment = Gui.alignment(parent, "player-time-" .. player_data.index) + local alignment = Gui.elements.aligned_flow(parent, { name = "player-time-" .. player_data.index }) local time_label = alignment.add{ name = "label", type = "label", @@ -112,12 +122,16 @@ local add_player_base = return player_name end) - :on_click(function(player, element, event) + :on_click(function(def, player, element, event) local selected_player_name = element.caption local selected_player = game.players[selected_player_name] if event.button == defines.mouse_button_type.left then -- LMB will open the map to the selected player - event.player.set_controller{ type = defines.controllers.remote, position = selected_player.physical_position, surface = selected_player.physical_surface } + player.set_controller{ + type = defines.controllers.remote, + position = selected_player.physical_position, + surface = selected_player.physical_surface + } else -- RMB will toggle the settings local old_selected_player_name = SelectedPlayer:get(player) @@ -148,8 +162,8 @@ end --- Adds all the buttons and flows that make up the action bar -- @element add_action_bar -local add_action_bar_buttons = - Gui.element(function(_, parent) +local add_action_bar_buttons = Gui.element("add_action_bar_buttons") + :draw(function(_, parent) close_action_bar(parent) -- Loop over all the buttons in the config for action_name, button_data in pairs(config.buttons) do @@ -167,7 +181,7 @@ local add_action_bar_buttons = --- Updates the visible state of the action bar buttons local function update_action_bar(element) - local player = Gui.get_player_from_element(element) + local player = Gui.get_player(element) local selected_player_name = SelectedPlayer:get(player) if not selected_player_name then @@ -196,20 +210,20 @@ end --- Main player list container for the left flow -- @element player_list_container -local player_list_container = - Gui.element(function(definition, parent) +local player_list_container = Gui.element("player_list_container") + :draw(function(definition, parent) -- Draw the internal container - local container = Gui.container(parent, definition.name, 200) + local container = Gui.elements.container(parent, 200) -- Draw the scroll table for the players - local scroll_table = Gui.scroll_table(container, 184, 3) + local scroll_table = Gui.elements.scroll_table(container, 184, 3, "scroll") -- Change the style of the scroll table local scroll_table_style = scroll_table.style scroll_table_style.padding = { 1, 0, 1, 2 } -- Add the action bar - local action_bar = Gui.footer(container, nil, nil, false, "action_bar") + local action_bar = Gui.elements.footer(container, { name = "action_bar", no_flow = true }) -- Change the style of the action bar local action_bar_style = action_bar.style @@ -221,7 +235,7 @@ local player_list_container = add_action_bar_buttons(action_bar) -- Add the reason bar - local reason_bar = Gui.footer(container, nil, nil, false, "reason_bar") + local reason_bar = Gui.elements.footer(container, { name = "reason_bar", no_flow = true }) -- Change the style of the reason bar local reason_bar_style = reason_bar.style @@ -250,14 +264,18 @@ local player_list_container = -- Return the exteral container return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow(true) ---- Button on the top flow used to toggle the player list container --- @element toggle_player_list -Gui.left_toolbar_button("entity/character", { "player-list.main-tooltip" }, player_list_container, function(player) - return Roles.player_allowed(player, "gui/player-list") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(player_list_container, true) +Gui.toolbar.create_button{ + name = "player_list_toggle", + left_element = player_list_container, + sprite = "entity/character", + tooltip = { "player-list.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/player-list") + end +} local online_time_format = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } local afk_time_format = ExpUtil.format_time_factory_locale{ format = "long", minutes = true } @@ -347,8 +365,8 @@ end Event.on_nth_tick(1800, function() local player_times = get_player_times() for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, player_list_container) - local scroll_table = frame.container.scroll.table + local container = Gui.get_left_element(player_list_container, player) + local scroll_table = container.frame.scroll.table for _, player_time in pairs(player_times) do update_player_base(scroll_table, player_time) end @@ -359,8 +377,8 @@ end) Event.add(defines.events.on_player_left_game, function(event) local remove_player = game.players[event.player_index] for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, player_list_container) - local scroll_table = frame.container.scroll.table + local container = Gui.get_left_element(player_list_container, player) + local scroll_table = container.frame.scroll.table remove_player_base(scroll_table, remove_player) local selected_player_name = SelectedPlayer:get(player) @@ -375,8 +393,8 @@ end) local function redraw_player_list() local player_list_order = get_player_list_order() for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, player_list_container) - local scroll_table = frame.container.scroll.table + local container = Gui.get_left_element(player_list_container, player) + local scroll_table = container.frame.scroll.table scroll_table.clear() for _, next_player_data in ipairs(player_list_order) do add_player_base(scroll_table, next_player_data) @@ -391,9 +409,9 @@ Event.add(Roles.events.on_role_unassigned, redraw_player_list) --- When the action player is changed the action bar will update SelectedPlayer:on_update(function(player_name, selected_player) local player = game.players[player_name] - local frame = Gui.get_left_element(player, player_list_container) - local scroll_table = frame.container.scroll.table - update_action_bar(frame.container.action_bar) + local container = Gui.get_left_element(player_list_container, player) + local scroll_table = container.frame.scroll.table + update_action_bar(container.frame.action_bar) for _, next_player in pairs(game.connected_players) do local element = scroll_table[next_player.name][open_action_bar.name] local style = "frame_button" @@ -411,8 +429,8 @@ end) --- When the action name is changed the reason input will update SelectedAction:on_update(function(player_name, selected_action) local player = game.players[player_name] - local frame = Gui.get_left_element(player, player_list_container) - local element = frame.container.reason_bar + local container = Gui.get_left_element(player_list_container, player) + local element = container.frame.reason_bar if selected_action then -- if there is a new value then check the player is still online local selected_player_name = SelectedPlayer:get(player_name) diff --git a/exp_legacy/module/modules/gui/playerdata.lua b/exp_legacy/module/modules/gui/playerdata.lua index d1e5baa2..2ef6110c 100644 --- a/exp_legacy/module/modules/gui/playerdata.lua +++ b/exp_legacy/module/modules/gui/playerdata.lua @@ -2,7 +2,7 @@ -- @gui PlayerData local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data @@ -74,8 +74,8 @@ local computed_stats = { }, } -local label = - Gui.element(function(_, parent, width, caption, tooltip, name) +local label = Gui.element("label") + :draw(function(_, parent, width, caption, tooltip, name) local new_label = parent.add{ type = "label", caption = caption, @@ -88,10 +88,10 @@ local label = return new_label end) -local pd_data_set = - Gui.element(function(_, parent, name) +local pd_data_set = Gui.element("pd_data_set") + :draw(function(_, parent, name) local pd_data_set = parent.add{ type = "flow", direction = "vertical", name = name } - local disp = Gui.scroll_table(pd_data_set, label_width["total"], 4, "disp") + local disp = Gui.elements.scroll_table(pd_data_set, label_width["total"], 4, "disp") for _, stat_name in pairs(PlayerData.Statistics.metadata.display_order) do local child = PlayerData.Statistics[stat_name] @@ -129,32 +129,31 @@ local function pd_update(table, player_name) end end -local pd_username_player = - Gui.element(function(definition, parent, player_list) +local pd_username_player = Gui.element("pd_username_player") + :draw(function(def, parent, player_list) return parent.add{ - name = definition.name, + name = def.name, type = "drop-down", items = player_list, - selected_index = #player_list > 0 and 1, + selected_index = #player_list > 0 and 1 or nil, } end) :style{ horizontally_stretchable = true, - }:on_selection_changed(function(_, element, _) + }:on_selection_state_changed(function(def, player, element) local player_name = game.connected_players[element.selected_index] local table = element.parent.parent.parent.parent["pd_st_2"].disp.table pd_update(table, player_name) end) - :static_name(Gui.unique_static_name) -local pd_username_update = - Gui.element{ +local pd_username_update = Gui.element("pd_username_update") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = "update", }:style{ width = 128, - }:on_click(function(_, element, _) + }:on_click(function(def, player, element) local player_index = element.parent[pd_username_player.name].selected_index if player_index > 0 then @@ -164,10 +163,10 @@ local pd_username_update = end end) -local pd_username_set = - Gui.element(function(_, parent, name, player_list) +local pd_username_set = Gui.element("pd_username_set") + :draw(function(_, parent, name, player_list) local pd_username_set = parent.add{ type = "flow", direction = "vertical", name = name } - local disp = Gui.scroll_table(pd_username_set, label_width["total"], 2, "disp") + local disp = Gui.elements.scroll_table(pd_username_set, label_width["total"], 2, "disp") pd_username_player(disp, player_list) pd_username_update(disp) @@ -175,9 +174,9 @@ local pd_username_set = return pd_username_set end) -pd_container = - Gui.element(function(definition, parent) - local container = Gui.container(parent, definition.name, label_width["total"]) +pd_container = Gui.element("pd_container") + :draw(function(def, parent) + local container = Gui.elements.container(parent, label_width["total"]) local player_list = {} for _, player in pairs(game.connected_players) do @@ -189,12 +188,18 @@ pd_container = return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() -Gui.left_toolbar_button("item/power-armor-mk2", "Player Data GUI", pd_container, function(player) - return Roles.player_allowed(player, "gui/playerdata") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(pd_container, false) +Gui.toolbar.create_button{ + name = "player_data_toggle", + left_element = pd_container, + sprite = "item/power-armor-mk2", + tooltip = "Player Data GUI", + visible = function(player, element) + return Roles.player_allowed(player, "gui/playerdata") + end +} local function gui_player_list_update() local player_list = {} @@ -204,8 +209,8 @@ local function gui_player_list_update() end for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, pd_container) - frame.container["pd_st_1"].disp.table[pd_username_player.name].items = player_list + local container = Gui.get_left_element(pd_container, player) + container.frame["pd_st_1"].disp.table[pd_username_player.name].items = player_list end end diff --git a/exp_legacy/module/modules/gui/production.lua b/exp_legacy/module/modules/gui/production.lua index 5b3cf828..8da29140 100644 --- a/exp_legacy/module/modules/gui/production.lua +++ b/exp_legacy/module/modules/gui/production.lua @@ -1,7 +1,7 @@ ---- Production Data -- @gui Production -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles @@ -35,8 +35,8 @@ end --- Display group -- @element production_data_group -local production_data_group = - Gui.element(function(_definition, parent, i) +local production_data_group = Gui.element("production_data_group") + :draw(function(_def, parent, i) local item if i == 0 then @@ -93,10 +93,10 @@ local production_data_group = --- A vertical flow containing all the production data -- @element production_data_set -local production_data_set = - Gui.element(function(_, parent, name) +local production_data_set = Gui.element("production_data_set") + :draw(function(_, parent, name) local production_set = parent.add{ type = "flow", direction = "vertical", name = name } - local disp = Gui.scroll_table(production_set, 350, 4, "disp") + local disp = Gui.elements.scroll_table(production_set, 350, 4, "disp") production_data_group(disp, 0) @@ -111,31 +111,37 @@ local production_data_set = return production_set end) -production_container = - Gui.element(function(definition, parent) - local container = Gui.container(parent, definition.name, 350) +production_container = Gui.element("production_container") + :draw(function(def, parent) + local container = Gui.elements.container(parent, 350) production_data_set(container, "production_st") return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() -Gui.left_toolbar_button("entity/assembling-machine-3", { "production.main-tooltip" }, production_container, function(player) - return Roles.player_allowed(player, "gui/production") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(production_container, false) +Gui.toolbar.create_button{ + name = "production_toggle", + left_element = production_container, + sprite = "entity/assembling-machine-3", + tooltip = { "production.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/production") + end +} Event.on_nth_tick(60, function() for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, production_container) + local container = Gui.get_left_element(production_container, player) local stat = player.force.get_item_production_statistics(player.surface) -- Allow remote view - local precision_value = precision[frame.container["production_st"].disp.table["production_0_e"].selected_index] - local table = frame.container["production_st"].disp.table + local precision_value = precision[container.frame["production_st"].disp.table["production_0_e"].selected_index] + local table = container.frame["production_st"].disp.table for i = 1, 8 do local production_prefix = "production_" .. i - local item = table[production_prefix .. "_e"].elem_value + local item = table[production_prefix .. "_e"].elem_value --[[ @as string ]] if item then local add = math.floor(stat.get_flow_count{ name = item, category = "input", precision_index = precision_value, count = false } / 6) / 10 diff --git a/exp_legacy/module/modules/gui/readme.lua b/exp_legacy/module/modules/gui/readme.lua index c5a266ee..97c1d8bc 100644 --- a/exp_legacy/module/modules/gui/readme.lua +++ b/exp_legacy/module/modules/gui/readme.lua @@ -5,8 +5,8 @@ ]] local ExpUtil = require("modules/exp_util") +local Gui = require("modules/exp_gui") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local Commands = require("modules/exp_commands") --- @dep expcore.commands local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data @@ -23,9 +23,8 @@ local title_width = 270 -- controls the centering of the titles local scroll_height = 275 -- controls the height of the scrolls --- Sub content area used within the content areas --- @element sub_content -local sub_content = - Gui.element{ +local sub_content = Gui.element("readme_sub_content") + :draw{ type = "frame", direction = "vertical", style = "inside_deep_frame", @@ -38,10 +37,9 @@ local sub_content = } --- Table which has a title above it above it --- @element title_table -local title_table = - Gui.element(function(_, parent, bar_size, caption, column_count) - Gui.title_label(parent, bar_size, caption) +local title_table = Gui.element("readme_title_table") + :draw(function(_, parent, bar_size, caption, column_count) + Gui.elements.title_label(parent, bar_size, caption) return parent.add{ type = "table", @@ -56,10 +54,9 @@ local title_table = horizontally_stretchable = true, } ---- Scroll to be used with Gui.title_label tables --- @element title_table_scroll -local title_table_scroll = - Gui.element{ +--- Scroll to be used with Gui.elements.title_label tables +local title_table_scroll = Gui.element("readme_title_table_scroll") + :draw{ type = "scroll-pane", direction = "vertical", horizontal_scroll_policy = "never", @@ -73,9 +70,8 @@ local title_table_scroll = } --- Used to connect to servers in server list --- @element join_server -local join_server = - Gui.element(function(_, parent, server_id, wrong_version) +local join_server = Gui.element("readme_join_server") + :draw(function(_, parent, server_id, wrong_version) local status = External.get_server_status(server_id) or "Offline" if wrong_version then status = "Version" end local flow = parent.add{ name = server_id, type = "flow" } @@ -103,8 +99,11 @@ local join_server = return button end) - :style(Gui.sprite_style(20, -1)) - :on_click(function(player, element, _) + :style{ + size = 20, + padding = -1, + } + :on_click(function(def, player, element) local server_id = element.parent.name External.request_connection(player, server_id, true) end) @@ -112,13 +111,12 @@ local join_server = local welcome_time_format = ExpUtil.format_time_factory_locale{ format = "long", days = true, hours = true, minutes = true } --- Content area for the welcome tab --- @element welcome_content -define_tab({ "readme.welcome-tab" }, { "readme.welcome-tooltip" }, - Gui.element(function(_, parent) +define_tab({ "readme.welcome-tab" }, { "readme.welcome-tooltip" }, Gui.element("readme_welcome") + :draw(function(_, parent) local server_details = { name = "APERX S0 - Local", welcome = "Failed to load description: disconnected from external api.", reset_time = "Non Set", branch = "Unknown" } if External.valid() then server_details = External.get_current_server() end local container = parent.add{ type = "flow", direction = "vertical" } - local player = Gui.get_player_from_element(parent) + local player = Gui.get_player(parent) -- Set up the top flow with logos local top_flow = container.add{ type = "flow" } @@ -128,9 +126,9 @@ define_tab({ "readme.welcome-tab" }, { "readme.welcome-tooltip" }, top_vertical_flow.style.horizontal_align = "center" -- Add the title and description to the top flow - Gui.title_label(top_vertical_flow, 62, "Welcome to " .. server_details.name) - Gui.centered_label(top_vertical_flow, 380, server_details.welcome) - Gui.bar(container) + Gui.elements.title_label(top_vertical_flow, 62, "Welcome to " .. server_details.name) + Gui.elements.centered_label(top_vertical_flow, 380, server_details.welcome) + Gui.elements.bar(container) -- Get the names of the roles the player has local player_roles = Roles.get_player_roles(player) @@ -142,75 +140,72 @@ define_tab({ "readme.welcome-tab" }, { "readme.welcome-tooltip" }, -- Add the other information to the gui container.add{ type = "flow" }.style.height = 4 local online_time = welcome_time_format(game.tick) - Gui.centered_label(sub_content(container), frame_width, { "readme.welcome-general", server_details.reset_time, online_time }) - Gui.centered_label(sub_content(container), frame_width, { "readme.welcome-roles", table.concat(role_names, ", ") }) - Gui.centered_label(sub_content(container), frame_width, { "readme.welcome-chat" }) + Gui.elements.centered_label(sub_content(container), frame_width, { "readme.welcome-general", server_details.reset_time, online_time }) + Gui.elements.centered_label(sub_content(container), frame_width, { "readme.welcome-roles", table.concat(role_names, ", ") }) + Gui.elements.centered_label(sub_content(container), frame_width, { "readme.welcome-chat" }) return container end)) --- Content area for the rules tab --- @element rules_content -define_tab({ "readme.rules-tab" }, { "readme.rules-tooltip" }, - Gui.element(function(_, parent) +define_tab({ "readme.rules-tab" }, { "readme.rules-tooltip" }, Gui.element("readme_rules") + :draw(function(_, parent) local container = parent.add{ type = "flow", direction = "vertical" } -- Add the title and description to the content - Gui.title_label(container, title_width - 3, { "readme.rules-tab" }) - Gui.centered_label(container, frame_width, { "readme.rules-general" }) - Gui.bar(container) + Gui.elements.title_label(container, title_width - 3, { "readme.rules-tab" }) + Gui.elements.centered_label(container, frame_width, { "readme.rules-general" }) + Gui.elements.bar(container) container.add{ type = "flow" } -- Add a table for the rules - local rules = Gui.scroll_table(container, scroll_height, 1) --[[@as LuaGuiElement]] + local rules = Gui.elements.scroll_table(container, scroll_height, 1) --[[@as LuaGuiElement]] rules.style = "bordered_table" rules.style.cell_padding = 4 -- Add the rules to the table for i = 1, 15 do - Gui.centered_label(rules, 565, { "readme.rules-" .. i }) + Gui.elements.centered_label(rules, 565, { "readme.rules-" .. i }) end return container end)) --- Content area for the commands tab --- @element commands_content -define_tab({ "readme.commands-tab" }, { "readme.commands-tooltip" }, - Gui.element(function(_, parent) +define_tab({ "readme.commands-tab" }, { "readme.commands-tooltip" }, Gui.element("readme_commands") + :draw(function(_, parent) local container = parent.add{ type = "flow", direction = "vertical" } - local player = Gui.get_player_from_element(parent) + local player = Gui.get_player(parent) -- Add the title and description to the content - Gui.title_label(container, title_width - 20, { "readme.commands-tab" }) - Gui.centered_label(container, frame_width, { "readme.commands-general" }) - Gui.bar(container) + Gui.elements.title_label(container, title_width - 20, { "readme.commands-tab" }) + Gui.elements.centered_label(container, frame_width, { "readme.commands-general" }) + Gui.elements.bar(container) container.add{ type = "flow" } -- Add a table for the commands - local commands = Gui.scroll_table(container, scroll_height, 2) --[[@as LuaGuiElement]] + local commands = Gui.elements.scroll_table(container, scroll_height, 2) --[[@as LuaGuiElement]] commands.style = "bordered_table" commands.style.cell_padding = 0 -- Add the rules to the table for name, command in pairs(Commands.list_for_player(player)) do - Gui.centered_label(commands, 120, name) - Gui.centered_label(commands, 450, command.description) + Gui.elements.centered_label(commands, 120, name) + Gui.elements.centered_label(commands, 450, command.description) end return container end)) --- Content area for the servers tab --- @element servers_content -define_tab({ "readme.servers-tab" }, { "readme.servers-tooltip" }, - Gui.element(function(_, parent) +define_tab({ "readme.servers-tab" }, { "readme.servers-tooltip" }, Gui.element("readme_servers") + :draw(function(_, parent) local container = parent.add{ type = "flow", direction = "vertical" } -- Add the title and description to the content - Gui.title_label(container, title_width - 10, { "readme.servers-tab" }) - Gui.centered_label(container, frame_width, { "readme.servers-general" }) - Gui.bar(container) + Gui.elements.title_label(container, title_width - 10, { "readme.servers-tab" }) + Gui.elements.centered_label(container, frame_width, { "readme.servers-general" }) + Gui.elements.bar(container) container.add{ type = "flow" } -- Draw the scroll @@ -222,15 +217,15 @@ define_tab({ "readme.servers-tab" }, { "readme.servers-tooltip" }, local factorio_servers = title_table(scroll_pane, 225, { "readme.servers-factorio" }, 3) local current_version = External.get_current_server().version for server_id, server in pairs(External.get_servers()) do - Gui.centered_label(factorio_servers, 110, server.short_name) - Gui.centered_label(factorio_servers, 436, server.description) + Gui.elements.centered_label(factorio_servers, 110, server.short_name) + Gui.elements.centered_label(factorio_servers, 436, server.description) join_server(factorio_servers, server_id, current_version ~= server.version and server.version) end else local factorio_servers = title_table(scroll_pane, 225, { "readme.servers-factorio" }, 2) for _, i in pairs{ 1, 2, 3, 5, 6, 8 } do - Gui.centered_label(factorio_servers, 110, { "readme.servers-" .. i }) - Gui.centered_label(factorio_servers, 460, { "readme.servers-d" .. i }) + Gui.elements.centered_label(factorio_servers, 110, { "readme.servers-" .. i }) + Gui.elements.centered_label(factorio_servers, 460, { "readme.servers-d" .. i }) end end @@ -238,23 +233,22 @@ define_tab({ "readme.servers-tab" }, { "readme.servers-tooltip" }, local external_links = title_table(scroll_pane, 235, { "readme.servers-external" }, 2) for _, key in ipairs{ "website", "github" } do local upper_key = key:gsub("^%l", string.upper) - Gui.centered_label(external_links, 110, upper_key) - Gui.centered_label(external_links, 460, { "links." .. key }, { "readme.servers-open-in-browser" }) + Gui.elements.centered_label(external_links, 110, upper_key) + Gui.elements.centered_label(external_links, 460, { "links." .. key }, { "readme.servers-open-in-browser" }) end return container end)) --- Content area for the servers tab --- @element backers_content -define_tab({ "readme.backers-tab" }, { "readme.backers-tooltip" }, - Gui.element(function(_, parent) +define_tab({ "readme.backers-tab" }, { "readme.backers-tooltip" }, Gui.element("readme_backers") + :draw(function(_, parent) local container = parent.add{ type = "flow", direction = "vertical" } -- Add the title and description to the content - Gui.title_label(container, title_width - 10, { "readme.backers-tab" }) - Gui.centered_label(container, frame_width, { "readme.backers-general" }) - Gui.bar(container) + Gui.elements.title_label(container, title_width - 10, { "readme.backers-tab" }) + Gui.elements.centered_label(container, frame_width, { "readme.backers-general" }) + Gui.elements.bar(container) container.add{ type = "flow" } -- Find which players will go where @@ -296,12 +290,12 @@ define_tab({ "readme.backers-tab" }, { "readme.backers-tooltip" }, for _, players in ipairs(groups) do local table = title_table(scroll_pane, players._width, players._title, 4) for _, player_name in ipairs(players) do - Gui.centered_label(table, 140, player_name) + Gui.elements.centered_label(table, 140, player_name) end if #players < 4 then for i = 1, 4 - #players do - Gui.centered_label(table, 140) + Gui.elements.centered_label(table, 140) end end end @@ -310,11 +304,10 @@ define_tab({ "readme.backers-tab" }, { "readme.backers-tooltip" }, end)) --- Content area for the player data tab --- @element commands_content -define_tab({ "readme.data-tab" }, { "readme.data-tooltip" }, - Gui.element(function(_, parent) +define_tab({ "readme.data-tab" }, { "readme.data-tooltip" }, Gui.element("readme_data") + :draw(function(_, parent) local container = parent.add{ type = "flow", direction = "vertical" } - local player = Gui.get_player_from_element(parent) + local player = Gui.get_player(parent) local player_name = player.name local enum = PlayerData.PreferenceEnum @@ -323,24 +316,24 @@ define_tab({ "readme.data-tab" }, { "readme.data-tooltip" }, preference = enum[preference] -- Add the title and description to the content - Gui.title_label(container, title_width, { "readme.data-tab" }) - Gui.centered_label(container, frame_width, { "readme.data-general" }) - Gui.bar(container) + Gui.elements.title_label(container, title_width, { "readme.data-tab" }) + Gui.elements.centered_label(container, frame_width, { "readme.data-general" }) + Gui.elements.bar(container) container.add{ type = "flow" } local scroll_pane = title_table_scroll(container) -- Add the required area local required = title_table(scroll_pane, 250, { "readme.data-required" }, 2) - Gui.centered_label(required, 150, preference_meta.name, preference_meta.tooltip) - Gui.centered_label(required, 420, { "expcore-data.preference-" .. enum[preference] }, preference_meta.value_tooltip) + Gui.elements.centered_label(required, 150, preference_meta.name, preference_meta.tooltip) + Gui.elements.centered_label(required, 420, { "expcore-data.preference-" .. enum[preference] }, preference_meta.value_tooltip) for name, child in pairs(PlayerData.Required.children) do local metadata = child.metadata local value = child:get(player_name) if value ~= nil or metadata.show_always then if metadata.stringify then value = metadata.stringify(value) end - Gui.centered_label(required, 150, metadata.name or { "exp-required." .. name }, metadata.tooltip or { "exp-required." .. name .. "-tooltip" }) - Gui.centered_label(required, 420, tostring(value), metadata.value_tooltip or { "exp-required." .. name .. "-value-tooltip" }) + Gui.elements.centered_label(required, 150, metadata.name or { "exp-required." .. name }, metadata.tooltip or { "exp-required." .. name .. "-tooltip" }) + Gui.elements.centered_label(required, 420, tostring(value), metadata.value_tooltip or { "exp-required." .. name .. "-value-tooltip" }) end end @@ -353,8 +346,8 @@ define_tab({ "readme.data-tab" }, { "readme.data-tooltip" }, if not metadata.permission or Roles.player_allowed(player, metadata.permission) then if metadata.stringify then value = metadata.stringify(value) end if value == nil then value = "None set" end - Gui.centered_label(settings, 150, metadata.name or { "exp-settings." .. name }, metadata.tooltip or { "exp-settings." .. name .. "-tooltip" }) - Gui.centered_label(settings, 420, tostring(value), metadata.value_tooltip or { "exp-settings." .. name .. "-value-tooltip" }) + Gui.elements.centered_label(settings, 150, metadata.name or { "exp-settings." .. name }, metadata.tooltip or { "exp-settings." .. name .. "-tooltip" }) + Gui.elements.centered_label(settings, 420, tostring(value), metadata.value_tooltip or { "exp-settings." .. name .. "-value-tooltip" }) end end end @@ -374,12 +367,12 @@ define_tab({ "readme.data-tab" }, { "readme.data-tooltip" }, else value = format_number(value or 0, false) end - Gui.centered_label(statistics, 150, metadata.name or { "exp-statistics." .. name }, metadata.tooltip or { "exp-statistics." .. name .. "-tooltip" }) - Gui.centered_label(statistics, 130, { "readme.data-format", value, metadata.unit or "" }, metadata.value_tooltip or { "exp-statistics." .. name .. "-tooltip" }) + Gui.elements.centered_label(statistics, 150, metadata.name or { "exp-statistics." .. name }, metadata.tooltip or { "exp-statistics." .. name .. "-tooltip" }) + Gui.elements.centered_label(statistics, 130, { "readme.data-format", value, metadata.unit or "" }, metadata.value_tooltip or { "exp-statistics." .. name .. "-tooltip" }) end end - if count > 0 then for i = 1, count do Gui.centered_label(statistics, 140) end end + if count > 0 then for i = 1, count do Gui.elements.centered_label(statistics, 140) end end end -- Add the misc area @@ -394,8 +387,8 @@ define_tab({ "readme.data-tab" }, { "readme.data-tooltip" }, local value = child:get(player_name) if value ~= nil or metadata.show_always then if metadata.stringify then value = metadata.stringify(value) end - Gui.centered_label(misc, 150, metadata.name or name, metadata.tooltip) - Gui.centered_label(misc, 420, tostring(value), metadata.value_tooltip) + Gui.elements.centered_label(misc, 150, metadata.name or name, metadata.tooltip) + Gui.elements.centered_label(misc, 420, tostring(value), metadata.value_tooltip) end end end @@ -405,18 +398,17 @@ define_tab({ "readme.data-tab" }, { "readme.data-tooltip" }, end)) --- Main readme container for the center flow --- @element readme local readme_toggle -local readme = - Gui.element(function(definition, parent) +local readme = Gui.element("readme") + :draw(function(def, parent) local container = parent.add{ - name = definition.name, + name = def.name, type = "frame", style = "invisible_frame", } -- Add the left hand side of the frame back, removed because of frame_tabbed_pane style - local left_alignment = Gui.alignment(container, nil, nil, "bottom") + local left_alignment = Gui.elements.aligned_flow(container, { vertical_align = "bottom" }) left_alignment.style.padding = { 32, 0, 0, 0 } local left_side = @@ -443,22 +435,26 @@ local readme = return container end) - :static_name(Gui.unique_static_name) - :on_open(function(player) - Gui.toggle_toolbar_button(player, readme_toggle, true) + :on_opened(function(def, player, element) + Gui.toolbar.set_button_toggled_state(readme_toggle, player, true) end) - :on_close(function(player, element) - Gui.toggle_toolbar_button(player, readme_toggle, false) + :on_closed(function(def, player, element) + Gui.toolbar.set_button_toggled_state(readme_toggle, player, false) Gui.destroy_if_valid(element) end) --- Toggle button for the readme gui --- @element readme_toggle readme_toggle = - Gui.toolbar_button("virtual-signal/signal-info", { "readme.main-tooltip" }, function(player) - return Roles.player_allowed(player, "gui/readme") - end) - :on_click(function(player, _) + Gui.toolbar.create_button{ + name = "readme_toggle", + auto_toggle = true, + sprite = "virtual-signal/signal-info", + tooltip = { "readme.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/readme") + end + } + :on_click(function(def, player, element) local center = player.gui.center if center[readme.name] then player.opened = nil @@ -475,18 +471,13 @@ Event.add(defines.events.on_player_created, function(event) player.opened = element end) ---- When a player joins clear center unless the player has something open -Event.add(defines.events.on_player_joined_game, function(event) +local function clear_readme(event) local player = game.players[event.player_index] if not player.opened then - player.gui.center.clear() + Gui.destroy_if_valid(player.gui.center[readme.name]) end -end) +end ---- When a player respawns clear center unless the player has something open -Event.add(defines.events.on_player_respawned, function(event) - local player = game.players[event.player_index] - if not player.opened then - player.gui.center.clear() - end -end) +--- When a player joins or respawns, clear center unless the player has something open +Event.add(defines.events.on_player_joined_game, clear_readme) +Event.add(defines.events.on_player_respawned, clear_readme) diff --git a/exp_legacy/module/modules/gui/research.lua b/exp_legacy/module/modules/gui/research.lua index d96b2b94..0ccbe038 100644 --- a/exp_legacy/module/modules/gui/research.lua +++ b/exp_legacy/module/modules/gui/research.lua @@ -2,7 +2,7 @@ -- @gui Research local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Storage = require("modules/exp_util/storage") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles @@ -174,20 +174,20 @@ end --- Display label for the clock display -- @element research_gui_clock_display -local research_gui_clock = - Gui.element{ +local research_gui_clock = Gui.element("research_gui_clock") + :draw{ type = "label", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = empty_time, style = "heading_2_label", } --- A vertical flow containing the clock -- @element research_clock_set -local research_clock_set = - Gui.element(function(_, parent, name) +local research_clock_set = Gui.element("research_clock_set") + :draw(function(_, parent, name) local research_set = parent.add{ type = "flow", direction = "vertical", name = name } - local disp = Gui.scroll_table(research_set, 390, 1, "disp") + local disp = Gui.elements.scroll_table(research_set, 390, 1, "disp") research_gui_clock(disp) @@ -196,8 +196,8 @@ local research_clock_set = --- Display group -- @element research_data_group -local research_data_group = - Gui.element(function(_definition, parent, i) +local research_data_group = Gui.element("research_data_group") + :draw(function(_def, parent, i) local name = parent.add{ type = "label", name = "research_" .. i .. "_name", @@ -238,10 +238,10 @@ local research_data_group = --- A vertical flow containing the data -- @element research_data_set -local research_data_set = - Gui.element(function(_, parent, name) +local research_data_set = Gui.element("research_data_set") + :draw(function(_, parent, name) local research_set = parent.add{ type = "flow", direction = "vertical", name = name } - local disp = Gui.scroll_table(research_set, 390, 4, "disp") + local disp = Gui.elements.scroll_table(research_set, 390, 4, "disp") local res_disp = research_gui_update() research_data_group(disp, 0) @@ -265,21 +265,27 @@ local research_data_set = return research_set end) -local research_container = - Gui.element(function(definition, parent) - local container = Gui.container(parent, definition.name, 390) +local research_container = Gui.element("research_container") + :draw(function(def, parent) + local container = Gui.elements.container(parent, 390) research_clock_set(container, "research_st_1") research_data_set(container, "research_st_2") return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() -Gui.left_toolbar_button("item/space-science-pack", { "research.main-tooltip" }, research_container, function(player) - return Roles.player_allowed(player, "gui/research") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(research_container, false) +Gui.toolbar.create_button{ + name = "research_toggle", + left_element = research_container, + sprite = "item/space-science-pack", + tooltip = { "research.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/research") + end +} Event.add(defines.events.on_research_finished, function(event) research_notification(event) @@ -294,8 +300,8 @@ Event.add(defines.events.on_research_finished, function(event) local res_disp = research_gui_update() for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, research_container) - local disp = frame.container["research_st_2"].disp.table + local container = Gui.get_left_element(research_container, player) + local disp = container.frame["research_st_2"].disp.table for i = 1, 8, 1 do local research_name_i = "research_" .. i @@ -326,8 +332,8 @@ Event.on_nth_tick(60, function() local current_time = research_time_format(game.tick) for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, research_container) - local disp = frame.container["research_st_1"].disp.table + local container = Gui.get_left_element(research_container, player) + local disp = container.frame["research_st_1"].disp.table disp[research_gui_clock.name].caption = current_time end end) diff --git a/exp_legacy/module/modules/gui/rocket-info.lua b/exp_legacy/module/modules/gui/rocket-info.lua index 3be8db3f..4f4dbb66 100644 --- a/exp_legacy/module/modules/gui/rocket-info.lua +++ b/exp_legacy/module/modules/gui/rocket-info.lua @@ -5,7 +5,7 @@ ]] local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local config = require("modules.exp_legacy.config.gui.rockets") --- @dep config.gui.rockets @@ -39,15 +39,17 @@ end --- Button to toggle the auto launch on a rocket silo -- @element toggle_launch -local toggle_launch = - Gui.element{ +local toggle_launch = Gui.element("toggle_launch") + :draw{ type = "sprite-button", sprite = "utility/play", tooltip = { "rocket-info.toggle-rocket-tooltip" }, - name = Gui.unique_static_name, + name = Gui.property_from_name, } - :style(Gui.sprite_style(16)) - :on_click(function(_, element, _) + :style(Gui.styles.sprite{ + size = 16, + }) + :on_click(function(def, player, element) local rocket_silo_name = element.parent.name:sub(8) local rocket_silo = Rockets.get_silo_entity(rocket_silo_name) if rocket_silo.auto_launch then @@ -61,30 +63,10 @@ local toggle_launch = end end) ---- Button to remotely launch a rocket from a silo --- @element launch_rocket -local launch_rocket = - Gui.element{ - type = "sprite-button", - sprite = "utility/center", - tooltip = { "rocket-info.launch-tooltip" }, - name = Gui.unique_static_name, - } - :style(Gui.sprite_style(16, -1)) - :on_click(function(player, element, _) - local rocket_silo_name = element.parent.name:sub(8) - local silo_data = Rockets.get_silo_data_by_name(rocket_silo_name) - if silo_data.entity.launch_rocket() then - element.enabled = false - else - player.print({ "rocket-info.launch-failed" }, Colors.orange_red) - end - end) - --- XY cords that allow zoom to map when pressed -- @element silo_cords -local silo_cords = - Gui.element(function(definition, parent, silo_data) +local silo_cords = Gui.element("silo_cords") + :draw(function(definition, parent, silo_data) local silo_name = silo_data.silo_name local pos = silo_data.position local tooltip = config.progress.allow_zoom_to_map and { "rocket-info.progress-label-tooltip" } or nil @@ -120,11 +102,11 @@ local silo_cords = } if config.progress.allow_zoom_to_map then - definition:triggers_events(label_x) - definition:triggers_events(label_y) + definition:link_element(label_x) + definition:link_element(label_y) end end) - :on_click(function(player, element, _) + :on_click(function(def, player, element) local rocket_silo_name = element.parent.caption local rocket_silo = Rockets.get_silo_entity(rocket_silo_name) player.set_controller{ type = defines.controllers.remote, position = rocket_silo.position, surface = rocket_silo.surface } @@ -132,10 +114,10 @@ local silo_cords = --- Base element for each rocket in the progress list -- @element rocket_entry -local rocket_entry = - Gui.element(function(_, parent, silo_data) +local rocket_entry = Gui.element("rocket_entry") + :draw(function(_, parent, silo_data) local silo_name = silo_data.silo_name - local player = Gui.get_player_from_element(parent) + local player = Gui.get_player(parent) -- Add the toggle auto launch if the player is allowed it -- Auto launch was removed from the api and no 2.0 equivalent was added @@ -148,18 +130,11 @@ local rocket_entry = button.sprite = silo_data.toggle_sprite]] end - -- Add the remote launch if the player is allowed it - if check_player_permissions(player, "remote_launch") then - local flow = parent.add{ type = "flow", name = "launch-" .. silo_name } - local button = launch_rocket(flow) - button.enabled = silo_data.allow_launch - end - -- Draw the silo cords element silo_cords(parent, silo_data) -- Add a progress label - local alignment = Gui.alignment(parent, silo_name) + local alignment = Gui.elements.aligned_flow(parent, { name = silo_name }) local element = alignment.add{ type = "label", @@ -174,8 +149,8 @@ local rocket_entry = --- Data label which contains a name and a value label pair -- @element data_label -local data_label = - Gui.element(function(_, parent, label_data) +local data_label = Gui.element("data_label") + :draw(function(_, parent, label_data) local data_name = label_data.name local data_subname = label_data.subname local data_fullname = data_subname and data_name .. data_subname or data_name @@ -190,7 +165,7 @@ local data_label = name_label.style.padding = { 0, 2 } --- Right aligned label to store the data - local alignment = Gui.alignment(parent, data_fullname) + local alignment = Gui.elements.aligned_flow(parent, { name = data_fullname }) local element = alignment.add{ type = "label", @@ -305,13 +280,6 @@ local function update_build_progress(parent, progress_data) toggle_button.tooltip = silo_data.toggle_tooltip toggle_button.sprite = silo_data.toggle_sprite end - - -- Update the launch button - local launch_button = parent["launch-" .. silo_name] - if launch_button then - launch_button = launch_button[launch_rocket.name] - launch_button.enabled = silo_data.allow_launch - end end end @@ -424,18 +392,20 @@ end -- Button to toggle a section dropdown -- @element toggle_section -local toggle_section = - Gui.element{ +local toggle_section = Gui.element("rocket_info_toggle_section") + :draw{ type = "sprite-button", sprite = "utility/expand", hovered_sprite = "utility/expand", tooltip = { "rocket-info.toggle-section-tooltip" }, style = "frame_action_button", - name = Gui.unique_static_name, + name = Gui.property_from_name, } - :style(Gui.sprite_style(20)) - :on_click(function(_, element, _) - local header_flow = element.parent + :style(Gui.styles.sprite{ + size = 20, + }) + :on_click(function(def, player, element) + local header_flow = assert(element.parent) local flow_name = header_flow.caption local flow = header_flow.parent.parent[flow_name] if Gui.toggle_visible_state(flow) then @@ -449,46 +419,46 @@ local toggle_section = -- Draw a section header and main scroll -- @element rocket_list_container -local section = - Gui.element(function(definition, parent, section_name, table_size) +local section = Gui.element("rocket_info_section") + :draw(function(definition, parent, section_name, table_size) -- Draw the header for the section - local header = Gui.header( - parent, - { "rocket-info.section-caption-" .. section_name }, - { "rocket-info.section-tooltip-" .. section_name }, - true, - section_name .. "-header" - ) - definition:triggers_events(header.parent.header_label) + local header = Gui.elements.header(parent, { + name = section_name .. "-header", + caption = { "rocket-info.section-caption-" .. section_name }, + tooltip = { "rocket-info.section-tooltip-" .. section_name }, + label_name = "label", + }) + + definition:link_element(header.parent.label) -- Right aligned button to toggle the section header.caption = section_name toggle_section(header) -- Table used to store the data - local scroll_table = Gui.scroll_table(parent, 215, table_size, section_name) + local scroll_table = Gui.elements.scroll_table(parent, 215, table_size, section_name) scroll_table.parent.visible = false -- Return the flow table - return definition:no_events(scroll_table) + return definition:unlink_element(scroll_table) end) - :on_click(function(_, element, event) + :on_click(function(def, player, element, event) event.element = element.parent.alignment[toggle_section.name] toggle_section:raise_event(event) end) --- Main gui container for the left flow -- @element rocket_list_container -local rocket_list_container = - Gui.element(function(definition, parent) +local rocket_list_container = Gui.element("rocket_list_container") + :draw(function(definition, parent) -- Draw the internal container - local container = Gui.container(parent, definition.name, 200) + local container = Gui.elements.container(parent, 200) -- Set the container style local style = container.style style.padding = 0 - local player = Gui.get_player_from_element(parent) + local player = Gui.get_player(parent) local force_name = player.force.name -- Draw stats section if config.stats.show_stats then @@ -503,7 +473,6 @@ local rocket_list_container = -- Draw build progress list if config.progress.show_progress then local col_count = 3 - if check_player_permissions(player, "remote_launch") then col_count = col_count + 1 end if check_player_permissions(player, "toggle_active") then col_count = col_count + 1 end local progress = section(container, "progress", col_count) -- Label used when there are no active silos @@ -516,19 +485,23 @@ local rocket_list_container = update_build_progress(progress, get_progress_data(force_name)) end - -- Return the exteral container + -- Return the external container return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow(function(player) - return player.force.rockets_launched > 0 and Roles.player_allowed(player, "gui/rocket-info") - end) ---- Button on the top flow used to toggle the container --- @element toggle_rocket_info -Gui.left_toolbar_button("item/rocket-silo", { "rocket-info.main-tooltip" }, rocket_list_container, function(player) - return Roles.player_allowed(player, "gui/rocket-info") +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(rocket_list_container, function(player, element) + return player.force.rockets_launched > 0 and Roles.player_allowed(player, "gui/rocket-info") end) +Gui.toolbar.create_button{ + name = "rocket_list_toggle", + left_element = rocket_list_container, + sprite = "item/rocket-silo", + tooltip = { "rocket-info.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/rocket-info") + end +} --- Update the gui for all players on a force local function update_rocket_gui_all(force_name) @@ -536,11 +509,11 @@ local function update_rocket_gui_all(force_name) local milestones = get_milestone_data(force_name) local progress = get_progress_data(force_name) for _, player in pairs(game.forces[force_name].players) do - local frame = Gui.get_left_element(player, rocket_list_container) - local container = frame.container - update_data_labels(container.stats.table, stats) - update_data_labels(container.milestones.table, milestones) - update_build_progress(container.progress.table, progress) + local container = Gui.get_left_element(rocket_list_container, player) + local frame = container.frame + update_data_labels(frame.stats.table, stats) + update_data_labels(frame.milestones.table, milestones) + update_build_progress(frame.progress.table, progress) end end @@ -553,10 +526,10 @@ end) --- Update only the progress gui for a force local function update_rocket_gui_progress(force_name) local progress = get_progress_data(force_name) - for _, player in pairs(game.forces[force_name].players) do - local frame = Gui.get_left_element(player, rocket_list_container) - local container = frame.container - update_build_progress(container.progress.table, progress) + for _, player in pairs(game.forces[force_name].connected_players) do + local container = Gui.get_left_element(rocket_list_container, player) + local frame = container.frame + update_build_progress(frame.progress.table, progress) end end @@ -590,12 +563,11 @@ Event.add(defines.events.on_robot_built_entity, on_built) local function role_update_event(event) if not config.progress.show_progress then return end local player = game.players[event.player_index] - local container = Gui.get_left_element(player, rocket_list_container).container - local progress_scroll = container.progress + local container = Gui.get_left_element(rocket_list_container, player) + local progress_scroll = container.frame.progress Gui.destroy_if_valid(progress_scroll.table) local col_count = 3 - if check_player_permissions(player, "remote_launch") then col_count = col_count + 1 end if check_player_permissions(player, "toggle_active") then col_count = col_count + 1 end local progress = progress_scroll.add{ type = "table", diff --git a/exp_legacy/module/modules/gui/science-info.lua b/exp_legacy/module/modules/gui/science-info.lua index e751a258..45e0a27e 100644 --- a/exp_legacy/module/modules/gui/science-info.lua +++ b/exp_legacy/module/modules/gui/science-info.lua @@ -5,8 +5,8 @@ ]] local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui -local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.gui +local Gui = require("modules/exp_gui") +local Roles = require("modules.exp_legacy.expcore.roles") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local config = require("modules.exp_legacy.config.gui.science") --- @dep config.gui.science local Production = require("modules.exp_legacy.modules.control.production") --- @dep modules.control.production @@ -17,16 +17,23 @@ local long_time_format = ExpUtil.format_time_factory_locale{ format = "long", ho local null_time_clock = { "science-info.eta-time", clock_time_format(nil) } local null_time_long = long_time_format(nil) ---- Data label that contains the value and the surfix +--- Remove invalid science packs, this can result from a certain mod not being loaded +for i = #config, 1, -1 do + if not prototypes.item[config[i]] then + table.remove(config, i) + end +end + +--- Data label that contains the value and the suffix -- @element production_label -local production_label = - Gui.element(function(_, parent, production_label_data) +local production_label = Gui.element("science_info_production_label") + :draw(function(_, parent, production_label_data) local name = production_label_data.name local tooltip = production_label_data.tooltip local color = production_label_data.color -- Add an alignment for the number - local alignment = Gui.alignment(parent, name) + local alignment = Gui.elements.aligned_flow(parent, { name = name }) -- Add the main value label local element = @@ -40,19 +47,19 @@ local production_label = -- Change the style element.style.font_color = color - -- Add the surfix label - local surfix_element = + -- Add the suffix label + local suffix_element = parent.add{ - name = "surfix-" .. name, + name = "suffix-" .. name, type = "label", - caption = { "science-info.unit", production_label_data.surfix }, + caption = { "science-info.unit", production_label_data.suffix }, tooltip = tooltip, } -- Change the style - local surfix_element_style = surfix_element.style - surfix_element_style.font_color = color - surfix_element_style.right_margin = 1 + local suffix_element_style = suffix_element.style + suffix_element_style.font_color = color + suffix_element_style.right_margin = 1 -- Return the value label return element @@ -61,12 +68,12 @@ local production_label = -- Get the data that is used with the production label local function get_production_label_data(name, tooltip, value, cutout, secondary) local data_colour = Production.get_color(config.color_cutoff * cutout, value, secondary) - local surfix, caption = Production.format_number(value) + local suffix, caption = Production.format_number(value) return { name = name, caption = caption, - surfix = surfix, + suffix = suffix, tooltip = tooltip, color = data_colour, } @@ -84,17 +91,17 @@ local function update_production_label(parent, production_label_data) production_label_element.tooltip = production_label_data.tooltip production_label_element.style.font_color = color - -- Update the surfix label - local surfix_element = parent["surfix-" .. name] - surfix_element.caption = { "science-info.unit", production_label_data.surfix } - surfix_element.tooltip = tooltip - surfix_element.style.font_color = color + -- Update the suffix label + local suffix_element = parent["suffix-" .. name] + suffix_element.caption = { "science-info.unit", production_label_data.suffix } + suffix_element.tooltip = tooltip + suffix_element.style.font_color = color end --- Adds 4 elements that show the data for a science pack -- @element science_pack_base -local science_pack_base = - Gui.element(function(_, parent, science_pack_data) +local science_pack_base = Gui.element("science_info_science_pack_base") + :draw(function(_, parent, science_pack_data) local science_pack = science_pack_data.science_pack -- Draw the icon for the science pack @@ -262,18 +269,21 @@ end --- Main task list container for the left flow -- @element task_list_container -local science_info_container = - Gui.element(function(definition, parent) - local player = Gui.get_player_from_element(parent) +local science_info = Gui.element("science_info") + :draw(function(def, parent) + local player = Gui.get_player(parent) -- Draw the internal container - local container = Gui.container(parent, definition.name, 200) + local container = Gui.elements.container(parent, 200) -- Draw the header - Gui.header(container, { "science-info.main-caption" }, { "science-info.main-tooltip" }) + Gui.elements.header(container, { + caption = { "science-info.main-caption" }, + tooltip = { "science-info.main-tooltip" }, + }) -- Draw the scroll table for the tasks - local scroll_table = Gui.scroll_table(container, 178, 4) + local scroll_table = Gui.elements.scroll_table(container, 178, 4, "scroll") -- Draw the no packs label local no_packs_label = @@ -292,7 +302,11 @@ local science_info_container = -- Add the footer and eta if config.show_eta then -- Draw the footer - local footer = Gui.footer(container, { "science-info.eta-caption" }, { "science-info.eta-tooltip" }, true) + local footer = Gui.elements.footer(container, { + name = "footer", + caption = { "science-info.eta-caption" }, + tooltip = { "science-info.eta-tooltip" }, + }) -- Draw the eta label local eta_label = @@ -313,17 +327,21 @@ local science_info_container = update_science_pack(scroll_table, get_science_pack_data(player, science_pack)) end - -- Return the exteral container + -- Return the external container return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() ---- Button on the top flow used to toggle the task list container --- @element toggle_science_info -Gui.left_toolbar_button("entity/lab", { "science-info.main-tooltip" }, science_info_container, function(player) - return Roles.player_allowed(player, "gui/science-info") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(science_info, false) +Gui.toolbar.create_button{ + name = "science_info_toggle", + left_element = science_info, + sprite = "entity/lab", + tooltip = { "science-info.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/science-info") + end +} --- Updates the gui every 1 second Event.on_nth_tick(60, function() @@ -331,11 +349,11 @@ Event.on_nth_tick(60, function() local force_eta_data = {} for _, player in pairs(game.connected_players) do local force_name = player.force.name - local frame = Gui.get_left_element(player, science_info_container) - local container = frame.container + local container = Gui.get_left_element(science_info, player) + local frame = container.frame -- Update the science packs - local scroll_table = container.scroll.table + local scroll_table = frame.scroll.table local pack_data = force_pack_data[force_name] if not pack_data then -- No data in cache so it needs to be generated @@ -355,7 +373,7 @@ Event.on_nth_tick(60, function() -- Update the eta times if not config.show_eta then return end - local eta_label = container.footer.alignment.label + local eta_label = frame.footer.flow.label local eta_data = force_eta_data[force_name] if not eta_data then -- No data in chache so it needs to be generated diff --git a/exp_legacy/module/modules/gui/server-ups.lua b/exp_legacy/module/modules/gui/server-ups.lua index b4b110fe..98a34cad 100644 --- a/exp_legacy/module/modules/gui/server-ups.lua +++ b/exp_legacy/module/modules/gui/server-ups.lua @@ -4,7 +4,7 @@ @alias server_ups ]] -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local External = require("modules.exp_legacy.expcore.external") --- @dep expcore.external local Commands = require("modules/exp_commands") @@ -20,11 +20,11 @@ UsesServerUps:set_metadata{ --- Label to show the server ups -- @element server_ups -local server_ups = - Gui.element{ +local server_ups = Gui.element("server_ups") + :draw{ type = "label", caption = "SUPS = 60.0", - name = Gui.unique_static_name, + name = Gui.property_from_name, } :style{ font = "default-game", @@ -71,6 +71,7 @@ end -- Draw the label when the player joins Event.add(defines.events.on_player_created, set_location) +Event.add(defines.events.on_player_joined_game, set_location) -- Update the caption for all online players -- percentage of game speed diff --git a/exp_legacy/module/modules/gui/surveillance.lua b/exp_legacy/module/modules/gui/surveillance.lua index 87187d12..7f034a27 100644 --- a/exp_legacy/module/modules/gui/surveillance.lua +++ b/exp_legacy/module/modules/gui/surveillance.lua @@ -1,32 +1,31 @@ ---- module surveillance -- @gui surveillance -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event -local cctv_player = - Gui.element(function(definition, parent, player_list) +local cctv_player = Gui.element("cctv_player") + :draw(function(def, parent, player_list) return parent.add{ - name = definition.name, + name = def.name, type = "drop-down", items = player_list, - selected_index = #player_list > 0 and 1, + selected_index = #player_list > 0 and 1 or nil, } end) :style{ horizontally_stretchable = true, } - :static_name(Gui.unique_static_name) -local cctv_status = - Gui.element{ +local cctv_status = Gui.element("cctv_status") + :draw{ type = "drop-down", items = { { "surveillance.status-enable" }, { "surveillance.status-disable" } }, selected_index = 2, }:style{ width = 96, - }:on_selection_changed(function(_, element, _) + }:on_selection_state_changed(function(def, player, element) if element.selected_index == 1 then element.parent.parent.parent.cctv_display.visible = true else @@ -34,59 +33,59 @@ local cctv_status = end end) -local cctv_type = - Gui.element{ +local cctv_type = Gui.element("cctv_type") + :draw{ type = "drop-down", - name = Gui.unique_static_name, + name = Gui.property_from_name, items = { { "surveillance.type-player" }, { "surveillance.type-static" }, { "surveillance.type-player-loop" } }, selected_index = 1, }:style{ width = 96, } -local cctv_location = - Gui.element{ +local cctv_location = Gui.element("cctv_location") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "surveillance.func-set" }, }:style{ width = 48, - }:on_click(function(player, element, _) + }:on_click(function(def, player, element) element.parent.parent.parent.cctv_display.position = player.physical_position end) -local zoom_in = - Gui.element{ +local zoom_in = Gui.element("zoom_in") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = "+", }:style{ width = 32, - }:on_click(function(_, element, _) + }:on_click(function(def, player, element) local display = element.parent.parent.parent.cctv_display if display.zoom < 2.0 then display.zoom = display.zoom + 0.05 end end) -local zoom_out = - Gui.element{ +local zoom_out = Gui.element("zoom_out") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = "-", }:style{ width = 32, - }:on_click(function(_, element, _) + }:on_click(function(def, player, element) local display = element.parent.parent.parent.cctv_display if display.zoom > 0.2 then display.zoom = display.zoom - 0.05 end end) -local camera_set = - Gui.element(function(_, parent, name, player_list) +local camera_set = Gui.element("camera_set") + :draw(function(_, parent, name, player_list) local camera_set = parent.add{ type = "flow", direction = "vertical", name = name } - local buttons = Gui.scroll_table(camera_set, 480, 6, "buttons") + local buttons = Gui.elements.scroll_table(camera_set, 480, 6, "buttons") cctv_player(buttons, player_list) cctv_status(buttons) @@ -109,9 +108,9 @@ local camera_set = return camera_set end) -local cctv_container = - Gui.element(function(definition, parent) - local container = Gui.container(parent, definition.name, 480) +local cctv_container = Gui.element("cctv_container") + :draw(function(def, parent) + local container = Gui.elements.container(parent, 480) local scroll = container.add{ name = "scroll", type = "scroll-pane", direction = "vertical" } scroll.style.maximal_height = 704 local player_list = {} @@ -125,12 +124,18 @@ local cctv_container = return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() -Gui.left_toolbar_button("entity/radar", { "surveillance.main-tooltip" }, cctv_container, function(player) - return Roles.player_allowed(player, "gui/surveillance") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(cctv_container, false) +Gui.toolbar.create_button{ + name = "cctv_toggle", + left_element = cctv_container, + sprite = "entity/radar", + tooltip = { "surveillance.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/surveillance") + end +} local function gui_update() local player_list = {} @@ -140,9 +145,9 @@ local function gui_update() end for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, cctv_container) - frame.container.scroll["cctv_st_1"].buttons.table[cctv_player.name].items = player_list - frame.container.scroll["cctv_st_2"].buttons.table[cctv_player.name].items = player_list + local container = Gui.get_left_element(cctv_container, player) + container.frame.scroll["cctv_st_1"].buttons.table[cctv_player.name].items = player_list + container.frame.scroll["cctv_st_2"].buttons.table[cctv_player.name].items = player_list end end @@ -151,18 +156,18 @@ Event.add(defines.events.on_player_left_game, gui_update) Event.add(defines.events.on_tick, function(_) for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, cctv_container) + local container = Gui.get_left_element(cctv_container, player) for i = 1, 2 do local scroll_table_name = "cctv_st_" .. i - local current_camera_set = frame.container.scroll[scroll_table_name] + local current_camera_set = container.frame.scroll[scroll_table_name] local switch_index = current_camera_set.buttons.table[cctv_type.name].selected_index if (switch_index == 1) or (switch_index == 3) then local selected_index = current_camera_set.buttons.table[cctv_player.name].selected_index if selected_index ~= 0 then - selected_index = current_camera_set.buttons.table[cctv_player.name].items[selected_index] + selected_index = current_camera_set.buttons.table[cctv_player.name].items[selected_index] --[[ @as number ]] current_camera_set["cctv_display"].position = game.players[selected_index].physical_position current_camera_set["cctv_display"].surface_index = game.players[selected_index].surface_index else @@ -176,10 +181,10 @@ end) Event.on_nth_tick(600, function(_) for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, cctv_container) + local container = Gui.get_left_element(cctv_container, player) for i = 1, 2 do - local current_camera_set = frame.container.scroll["cctv_st_" .. i] + local current_camera_set = container.frame.scroll["cctv_st_" .. i] if current_camera_set.buttons.table[cctv_type.name].selected_index == 3 then local item_n = #current_camera_set.buttons.table[cctv_player.name].items diff --git a/exp_legacy/module/modules/gui/task-list.lua b/exp_legacy/module/modules/gui/task-list.lua index 5ee310a4..eb20e2ae 100644 --- a/exp_legacy/module/modules/gui/task-list.lua +++ b/exp_legacy/module/modules/gui/task-list.lua @@ -5,7 +5,7 @@ ]] local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore @@ -83,15 +83,17 @@ end --- Button displayed in the header bar, used to add a new task -- @element add_new_task -local add_new_task = - Gui.element{ +local add_new_task = Gui.element("add_new_task") + :draw{ type = "sprite-button", sprite = "utility/add", tooltip = { "task-list.add-tooltip" }, style = "tool_button", - name = Gui.unique_static_name, - }:style(Styles.sprite22):on_click( - function(player, _, _) + name = Gui.property_from_name, + } + :style(Styles.sprite22) + :on_click( + function(def, player, element) -- Disable editing PlayerIsEditing:set(player, false) -- Clear selected @@ -103,8 +105,8 @@ local add_new_task = --- Header displayed when no tasks are in the task list -- @element no_tasks_found -local no_tasks_found = - Gui.element( +local no_tasks_found = Gui.element("no_tasks_found") + :draw( function(_, parent) local header = parent.add{ @@ -113,6 +115,7 @@ local no_tasks_found = style = "negative_subheader_frame", } header.style.horizontally_stretchable = true + header.style.bottom_margin = 0 -- Flow used for centering the content in the subheader local center = header.add{ @@ -134,50 +137,43 @@ local no_tasks_found = --- Frame element with the right styling -- @element subfooter_frame -local subfooter_frame = - Gui.element( - function(_, parent, name) - return parent.add{ - type = "frame", - name = name, - direction = "vertical", - style = "subfooter_frame", - } - end - ):style{ +local subfooter_frame = Gui.element("task_list_subfooter_frame") + :draw{ + type = "frame", + name = Gui.property_from_arg(1), + direction = "vertical", + style = "subfooter_frame", + } + :style{ + height = 0, padding = 5, use_header_filler = false, - horizontally_stretchable = true, } --- Label element preset -- @element subfooter_label -local subfooter_label = - Gui.element( - function(_, parent, caption) - return parent.add{ - name = "footer_label", - type = "label", - style = "frame_title", - caption = caption, - } - end - ) +local subfooter_label = Gui.element("task_list_subfooter_label") + :draw{ + name = "footer_label", + type = "label", + style = "frame_title", + caption = Gui.property_from_arg(1), + } --- Action flow that contains action buttons -- @element subfooter_actions -local subfooter_actions = - Gui.element{ +local subfooter_actions = Gui.element("task_list_subfooter_actions") + :draw{ type = "flow", name = "actions", } --- Button element with a flow around it to fix duplicate name inside of the scroll flow -- @element task_list_item -local task_list_item = - Gui.element( - function(definition, parent, task) +local task_list_item = Gui.element("task_list_item") + :draw( + function(def, parent, task) local flow = parent.add{ type = "flow", name = "task-" .. task.task_id, @@ -187,7 +183,7 @@ local task_list_item = flow.style.horizontally_stretchable = true local button = flow.add{ - name = definition.name, + name = def.name, type = "button", style = "list_box_item", caption = task.title, @@ -199,17 +195,18 @@ local task_list_item = return button end - ):on_click( - function(player, element, _) + ) + :on_click( + function(def, player, element) local task_id = element.parent.caption PlayerSelected:set(player, task_id) end - ):static_name(Gui.unique_static_name) + ) --- Scrollable list of all tasks -- @element task_list -local task_list = - Gui.element( +local task_list = Gui.element("task_list") + :draw( function(_, parent) local scroll_pane = parent.add{ @@ -239,15 +236,16 @@ local task_list = --- Button element inside the task view footer to start editing a task -- @element task_view_edit_button -local task_view_edit_button = - Gui.element{ +local task_view_edit_button = Gui.element("task_view_edit_button") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "", "[img=utility/rename_icon] ", { "task-list.edit" } }, tooltip = { "task-list.edit-tooltip" }, style = "shortcut_bar_button", }:style(Styles.footer_button):on_click( - function(player, _, _) + function(def, event, element) + local player = Gui.get_player(event) local selected = PlayerSelected:get(player) PlayerIsEditing:set(player, true) @@ -257,30 +255,33 @@ local task_view_edit_button = --- Button to close the task view footer -- @element task_view_close_button -local task_view_close_button = - Gui.element{ +local task_view_close_button = Gui.element("task_view_close_button") + :draw{ type = "sprite-button", sprite = "utility/collapse", style = "frame_action_button", tooltip = { "task-list.close-tooltip" }, } - :style(Styles.sprite22):on_click( - function(player, _, _) + :style(Styles.sprite22) + :on_click( + function(def, player, element) PlayerSelected:set(player, nil) end ) --- Button to delete the task inside the task view footer -- @element task_view_delete_button -local task_view_delete_button = - Gui.element{ +local task_view_delete_button = Gui.element("task_view_delete_button") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "", "[img=utility/trash] ", { "task-list.delete" } }, tooltip = { "task-list.delete-tooltip" }, style = "shortcut_bar_button_red", - }:style(Styles.footer_button):on_click( - function(player, _, _) + } + :style(Styles.footer_button) + :on_click( + function(def, player, element) local selected = PlayerSelected:get(player) PlayerSelected:set(player, nil) Tasks.remove_task(selected) @@ -289,13 +290,13 @@ local task_view_delete_button = --- Subfooter inside the tasklist container that holds all the elements for viewing a task -- @element task_view_footer -local task_view_footer = - Gui.element( +local task_view_footer = Gui.element("task_view_footer") + :draw( function(_, parent) local footer = subfooter_frame(parent, "view") local flow = footer.add{ type = "flow" } subfooter_label(flow, { "task-list.view-footer-header" }) - local alignment = Gui.alignment(flow) + local alignment = Gui.elements.aligned_flow(flow) task_view_close_button(alignment) local title_label = footer.add{ @@ -345,9 +346,9 @@ local task_create_confirm_button --- Textfield element used in both the task create and edit footers -- @element task_message_textfield -local task_message_textfield = - Gui.element{ - name = Gui.unique_static_name, +local task_message_textfield = Gui.element("task_message_textfield") + :draw{ + name = Gui.property_from_name, type = "text-box", text = "", }:style{ @@ -356,7 +357,7 @@ local task_message_textfield = horizontally_stretchable = true, } :on_text_changed( - function(player, element, _) + function(def, player, element) local is_editing = PlayerIsEditing:get(player) local is_creating = PlayerIsCreating:get(player) @@ -372,15 +373,17 @@ local task_message_textfield = --- Button to confirm the changes inside the task edit footer -- @element task_edit_confirm_button -task_edit_confirm_button = - Gui.element{ +task_edit_confirm_button = Gui.element("task_edit_confirm_button") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "", "[img=utility/check_mark] ", { "task-list.confirm" } }, tooltip = { "task-list.confirm-tooltip" }, style = "shortcut_bar_button_green", - }:style(Styles.footer_button):on_click( - function(player, element, _) + } + :style(Styles.footer_button) + :on_click( + function(def, player, element) local selected = PlayerSelected:get(player) PlayerIsEditing:set(player, false) local new_message = element.parent.parent[task_message_textfield.name].text @@ -392,14 +395,16 @@ task_edit_confirm_button = --- Button to discard the changes inside the task edit footer -- @element edit_task_discard_button -local edit_task_discard_button = - Gui.element{ +local edit_task_discard_button = Gui.element("edit_task_discard_button") + :draw{ type = "button", caption = { "", "[img=utility/close_black] ", { "task-list.discard" } }, tooltip = { "task-list.discard-tooltip" }, style = "shortcut_bar_button_red", - }:style(Styles.footer_button):on_click( - function(player, _, _) + } + :style(Styles.footer_button) + :on_click( + function(def, player, element) local selected = PlayerSelected:get(player) Tasks.set_editing(selected, player.name, nil) PlayerIsEditing:set(player, false) @@ -408,8 +413,8 @@ local edit_task_discard_button = --- Subfooter inside the tasklist container that holds all the elements for editing a task -- @element task_edit_footer -local task_edit_footer = - Gui.element( +local task_edit_footer = Gui.element("task_edit_footer") + :draw( function(_, parent) local footer = subfooter_frame(parent, "edit") subfooter_label(footer, { "task-list.edit-footer-header" }) @@ -427,16 +432,18 @@ local task_edit_footer = --- Button to confirm the changes inside the task create footer -- @element task_create_confirm_button -task_create_confirm_button = - Gui.element{ +task_create_confirm_button = Gui.element("task_create_confirm_button") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "", "[img=utility/check_mark] ", { "task-list.confirm" } }, tooltip = { "task-list.confirm-tooltip" }, style = "shortcut_bar_button_green", enabled = false, - }:style(Styles.footer_button):on_click( - function(player, element, _) + } + :style(Styles.footer_button) + :on_click( + function(def, player, element) local message = element.parent.parent[task_message_textfield.name].text PlayerIsCreating:set(player, false) local parsed = parse_message(message) @@ -447,22 +454,24 @@ task_create_confirm_button = --- Button to discard the changes inside the task create footer -- @element task_create_discard_button -local task_create_discard_button = - Gui.element{ +local task_create_discard_button = Gui.element("task_create_discard_button") + :draw{ type = "button", caption = { "", "[img=utility/close_black] ", { "task-list.discard" } }, tooltip = { "task-list.discard-tooltip" }, style = "shortcut_bar_button_red", - }:style(Styles.footer_button):on_click( - function(player, _, _) + } + :style(Styles.footer_button) + :on_click( + function(def, player, element) PlayerIsCreating:set(player, false) end ) --- Subfooter inside the tasklist container that holds all the elements to create a new task -- @element task_create_footer -local task_create_footer = - Gui.element( +local task_create_footer = Gui.element("task_create_footer") + :draw( function(_, parent) local footer = subfooter_frame(parent, "create") subfooter_label(footer, { "task-list.create-footer-header" }) @@ -480,7 +489,7 @@ local task_create_footer = --- Clear and repopulate the task list with all current tasks local repopulate_task_list = function(task_list_element) - local force = Gui.get_player_from_element(task_list_element).force + local force = Gui.get_player(task_list_element).force local task_ids = Tasks.get_force_task_ids(force.name) task_list_element.clear() @@ -497,19 +506,22 @@ end --- Main task list container for the left flow -- @element task_list_container -local task_list_container = - Gui.element( - function(definition, parent) +local task_list_container = Gui.element("task_list_container") + :draw( + function(def, parent) -- Draw the internal container - local container = Gui.container(parent, definition.name, 268) + local container = Gui.elements.container(parent, 268) container.style.maximal_width = 268 - container.style.minimal_width = 268 -- Draw the header - local header = Gui.header(container, { "task-list.main-caption" }, { "task-list.sub-tooltip" }, true) + local header = Gui.elements.header(container, { + name = "header", + caption = { "task-list.main-caption" }, + tooltip = { "task-list.sub-tooltip" }, + }) -- Draw the new task button - local player = Gui.get_player_from_element(parent) + local player = Gui.get_player(parent) local add_new_task_element = add_new_task(header) add_new_task_element.visible = check_player_permissions(player) @@ -529,23 +541,22 @@ local task_list_container = -- Return the external container return container.parent end - ):static_name(Gui.unique_static_name):add_to_left_flow( - function(player) - local task_ids = Tasks.get_force_task_ids(player.force.name) - return #task_ids > 0 - end ) ---- Button on the top flow used to toggle the task list container --- @element toggle_left_element -Gui.left_toolbar_button( - "utility/not_enough_repair_packs_icon", - { "task-list.main-tooltip" }, - task_list_container, - function(player) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(task_list_container, function(player) + local task_ids = Tasks.get_force_task_ids(player.force.name) + return #task_ids > 0 +end) +Gui.toolbar.create_button{ + name = "task_list_toggle", + left_element = task_list_container, + sprite = "utility/not_enough_repair_packs_icon", + tooltip = { "task-list.main-tooltip" }, + visible = function(player, element) return Roles.player_allowed(player, "gui/task-list") end -) +} -- Function to update a single task and some of the elements inside the container local update_task = function(player, task_list_element, task_id) @@ -575,8 +586,8 @@ end -- Update the footer task edit view local update_task_edit_footer = function(player, task_id) local task = Tasks.get_task(task_id) - local frame = Gui.get_left_element(player, task_list_container) - local edit_flow = frame.container.edit + local container = Gui.get_left_element(task_list_container, player) + local edit_flow = container.frame.edit local message_element = edit_flow[task_message_textfield.name] @@ -587,8 +598,8 @@ end -- Update the footer task view local update_task_view_footer = function(player, task_id) local task = Tasks.get_task(task_id) - local frame = Gui.get_left_element(player, task_list_container) - local view_flow = frame.container.view + local container = Gui.get_left_element(task_list_container, player) + local view_flow = container.frame.view local has_permission = check_player_permissions(player, task) local title_element = view_flow.title @@ -633,8 +644,8 @@ Tasks.on_update( end end - local frame = Gui.get_left_element(player, task_list_container) - local task_list_element = frame.container.scroll.task_list + local container = Gui.get_left_element(task_list_container, player) + local task_list_element = container.frame.scroll.task_list -- Update the task that was changed update_task(player, task_list_element, task_id) @@ -647,12 +658,12 @@ PlayerIsCreating:on_update( function(player_name, curr_state, _) local player = game.players[player_name] - local frame = Gui.get_left_element(player, task_list_container) - local create = frame.container.create + local container = Gui.get_left_element(task_list_container, player) + local create = container.frame.create -- Clear the textfield - local message_element = frame.container.create[task_message_textfield.name] - local confirm_button_element = frame.container.create.actions[task_create_confirm_button.name] + local message_element = container.frame.create[task_message_textfield.name] + local confirm_button_element = container.frame.create.actions[task_create_confirm_button.name] message_element.focus() message_element.text = "" confirm_button_element.enabled = false @@ -670,10 +681,10 @@ PlayerSelected:on_update( function(player_name, curr_state, prev_state) local player = game.players[player_name] - local frame = Gui.get_left_element(player, task_list_container) - local task_list_element = frame.container.scroll.task_list - local view_flow = frame.container.view - local edit_flow = frame.container.edit + local container = Gui.get_left_element(task_list_container, player) + local task_list_element = container.frame.scroll.task_list + local view_flow = container.frame.view + local edit_flow = container.frame.edit local is_editing = PlayerIsEditing:get(player) local is_creating = PlayerIsCreating:get(player) @@ -721,9 +732,9 @@ PlayerIsEditing:on_update( function(player_name, curr_state, _) local player = game.players[player_name] - local frame = Gui.get_left_element(player, task_list_container) - local view_flow = frame.container.view - local edit_flow = frame.container.edit + local container = Gui.get_left_element(task_list_container, player) + local view_flow = container.frame.view + local edit_flow = container.frame.edit local selected = PlayerSelected:get(player) if curr_state then @@ -740,7 +751,7 @@ PlayerIsEditing:on_update( --- Makes sure the right buttons are present when roles change local function role_update_event(event) local player = game.players[event.player_index] - local container = Gui.get_left_element(player, task_list_container).container + local frame = Gui.get_left_element(task_list_container, player).frame -- Update the view task local selected = PlayerSelected:get(player) if selected then @@ -752,7 +763,7 @@ local function role_update_event(event) -- Update the new task button and create footer in case the user can now add them local has_permission = check_player_permissions(player) - local add_new_task_element = container.header.alignment[add_new_task.name] + local add_new_task_element = frame.header.flow[add_new_task.name] add_new_task_element.visible = has_permission local is_creating = PlayerIsCreating:get(player) if is_creating and not has_permission then @@ -767,8 +778,8 @@ Event.add(Roles.events.on_role_unassigned, role_update_event) local function reset_task_list(event) -- Repopulate the task list local player = game.players[event.player_index] - local frame = Gui.get_left_element(player, task_list_container) - local task_list_element = frame.container.scroll.task_list + local container = Gui.get_left_element(task_list_container, player) + local task_list_element = container.frame.scroll.task_list repopulate_task_list(task_list_element) -- Check if the selected task is still valid diff --git a/exp_legacy/module/modules/gui/tool.lua b/exp_legacy/module/modules/gui/tool.lua index eaeede89..6ccfc7ff 100644 --- a/exp_legacy/module/modules/gui/tool.lua +++ b/exp_legacy/module/modules/gui/tool.lua @@ -4,7 +4,7 @@ ]] local ExpUtil = require("modules/exp_util") -local Gui = require("modules/exp_legacy/expcore/gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local Selection = require("modules/exp_legacy/modules/control/selection") --- @dep modules.control.selection @@ -27,10 +27,10 @@ local style = { --- Arty label -- @element tool_gui_arty_l -local tool_gui_arty_l = - Gui.element{ +local tool_gui_arty_l = Gui.element("tool_gui_arty_l") + :draw{ type = "label", - name = "tool_arty_l", + name = Gui.property_from_name, caption = { "tool.artillery" }, tooltip = { "tool.artillery-tooltip" }, style = "heading_2_label" @@ -40,14 +40,14 @@ local tool_gui_arty_l = --- Arty button -- @element tool_gui_arty_b -local tool_gui_arty_b = - Gui.element{ +local tool_gui_arty_b = Gui.element("tool_gui_arty_b") + :draw{ type = "button", - name = "tool_arty_b", + name = Gui.property_from_name, caption = { "tool.apply" } }:style( style.button - ):on_click(function(player, _, _) + ):on_click(function(def, player, element) if Selection.is_selecting(player, SelectionArtyArea) then Selection.stop(player) @@ -59,10 +59,10 @@ local tool_gui_arty_b = --- Waterfill label -- @element tool_gui_waterfill_l -local tool_gui_waterfill_l = - Gui.element{ +local tool_gui_waterfill_l = Gui.element("tool_gui_waterfill_l") + :draw{ type = "label", - name = "tool_waterfill_l", + name = Gui.property_from_name, caption = { "tool.waterfill" }, tooltip = { "tool.waterfill-tooltip" }, style = "heading_2_label" @@ -72,14 +72,14 @@ local tool_gui_waterfill_l = --- Waterfill button -- @element tool_gui_waterfill_b -local tool_gui_waterfill_b = - Gui.element{ +local tool_gui_waterfill_b = Gui.element("tool_gui_waterfill_b") + :draw{ type = "button", - name = "tool_waterfill_b", + name = Gui.property_from_name, caption = { "tool.apply" } }:style( style.button - ):on_click(function(player, _, _) + ):on_click(function(def, player, element) if Selection.is_selecting(player, SelectionWaterfillArea) then Selection.stop(player) return player.print{ "exp-commands_waterfill.exit" } @@ -98,10 +98,10 @@ local tool_gui_waterfill_b = --- Train label -- @element tool_gui_train_l -local tool_gui_train_l = - Gui.element{ +local tool_gui_train_l = Gui.element("tool_gui_train_l") + :draw{ type = "label", - name = "tool_train_l", + name = Gui.property_from_name, caption = { "tool.train" }, tooltip = { "tool.train-tooltip" }, style = "heading_2_label" @@ -111,23 +111,23 @@ local tool_gui_train_l = --- Train button -- @element tool_gui_train_b -local tool_gui_train_b = - Gui.element{ +local tool_gui_train_b = Gui.element("tool_gui_train_b") + :draw{ type = "button", - name = "tool_train_b", + name = Gui.property_from_name, caption = { "tool.apply" } }:style( style.button - ):on_click(function(player, _, _) + ):on_click(function(def, player, element) addon_train.manual(player) end) --- Research label -- @element tool_gui_research_l -local tool_gui_research_l = - Gui.element{ +local tool_gui_research_l = Gui.element("tool_gui_research_l") + :draw{ type = "label", - name = "tool_research_l", + name = Gui.property_from_name, caption = { "tool.research" }, tooltip = { "tool.research-tooltip" }, style = "heading_2_label" @@ -137,18 +137,18 @@ local tool_gui_research_l = --- Research button -- @element tool_gui_research_b -local tool_gui_research_b = - Gui.element{ +local tool_gui_research_b = Gui.element("tool_gui_research_b") + :draw{ type = "button", - name = "tool_research_b", + name = Gui.property_from_name, caption = { "tool.apply" } }:style( style.button - ):on_click(function(player, _, _) + ):on_click(function(def, player, element) local enabled = addon_research.set_auto_research() if enabled then - addon_research.res_queue(player.force, true) + addon_research.res_queue(player.force --[[ @as LuaForce ]], true) end local player_name = ExpUtil.format_player_name_locale(player) @@ -157,10 +157,10 @@ local tool_gui_research_b = --- Spawn label -- @element tool_gui_spawn_l -local tool_gui_spawn_l = - Gui.element{ +local tool_gui_spawn_l = Gui.element("tool_gui_spawn_l") + :draw{ type = "label", - name = "tool_spawn_l", + name = Gui.property_from_name, caption = { "tool.spawn" }, tooltip = { "tool.spawn-tooltip" }, style = "heading_2_label" @@ -170,14 +170,14 @@ local tool_gui_spawn_l = --- Spawn button -- @element tool_gui_spawn_b -local tool_gui_spawn_b = - Gui.element{ +local tool_gui_spawn_b = Gui.element("tool_gui_spawn_b") + :draw{ type = "button", - name = "tool_spawn_b", + name = Gui.property_from_name, caption = { "tool.apply" } }:style( style.button - ):on_click(function(player, _, _) + ):on_click(function(def, player, element) if not player.character or player.character.health <= 0 or not ExpUtil.teleport_player(player, game.surfaces.nauvis, { 0, 0 }, "dismount") then @@ -185,9 +185,9 @@ local tool_gui_spawn_b = end end) -local function tool_perm(player) - local frame = Gui.get_left_element(player, tool_container) - local disp = frame.container["tool_st"].disp.table +local function tool_perm(player, container) + container = container or Gui.get_left_element(tool_container, player) + local disp = container.frame.tool_st.disp.table local allowed allowed = Roles.player_allowed(player, "command/artillery") @@ -213,10 +213,10 @@ end --- A vertical flow containing all the tool -- @element tool_set -local tool_set = - Gui.element(function(_, parent, name) +local tool_set = Gui.element("tool_set") + :draw(function(_, parent, name) local tool_set = parent.add{ type = "flow", direction = "vertical", name = name } - local disp = Gui.scroll_table(tool_set, 240, 2, "disp") + local disp = Gui.elements.scroll_table(tool_set, 240, 2, "disp") tool_gui_arty_l(disp) tool_gui_arty_b(disp) @@ -238,25 +238,29 @@ local tool_set = --- The main container for the tool gui -- @element tool_container -tool_container = - Gui.element(function(definition, parent) - local player = Gui.get_player_from_element(parent) - local container = Gui.container(parent, definition.name, 240) +tool_container = Gui.element("tool_container") + :draw(function(def, parent) + local player = Gui.get_player(parent) + local container = Gui.elements.container(parent, 240) tool_set(container, "tool_st") - tool_perm(player) + tool_perm(player, container.parent) return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() ---- Button on the top flow used to toggle the tool container --- @element toggle_left_element -Gui.left_toolbar_button("item/repair-pack", { "tool.main-tooltip" }, tool_container, function(player) - return Roles.player_allowed(player, "gui/tool") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(tool_container, false) +Gui.toolbar.create_button{ + name = "tool_toggle", + left_element = tool_container, + sprite = "item/repair-pack", + tooltip = { "tool.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/tool") + end +} Event.add(Roles.events.on_role_assigned, function(event) tool_perm(game.players[event.player_index]) diff --git a/exp_legacy/module/modules/gui/toolbar.lua b/exp_legacy/module/modules/gui/toolbar.lua deleted file mode 100644 index 19025424..00000000 --- a/exp_legacy/module/modules/gui/toolbar.lua +++ /dev/null @@ -1,528 +0,0 @@ -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui -local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data - --- Used to store the state of the toolbar when a player leaves -local ToolbarState = PlayerData.Settings:combine("ToolbarState") -ToolbarState:set_metadata{ - stringify = function(value) - local buttons, favourites = 0, 0 - for _, state in ipairs(value) do - buttons = buttons + 1 - if state.favourite then - favourites = favourites + 1 - end - end - - return string.format("Buttons: %d, Favourites: %d", buttons, favourites) - end, -} - --- Styles used for sprite buttons -local button_size = 20 -local Styles = { - header = Gui.sprite_style(22), - item = Gui.sprite_style(button_size), -} - ---- Set the style of the fake toolbar element -local function copy_style(src, dst) - dst.style = src.style.name - dst.style.height = button_size - dst.style.width = button_size - dst.style.padding = -2 -end - -local toolbar_container, move_up, move_down, toggle_toolbar - ---- Reorder the buttons relative to each other, this will update the datastore -local function move_toolbar_button(player, item, offset) - local old_index = item.get_index_in_parent() - local new_index = old_index + offset - - -- Ideally the following would all happen in on_update but this had too much latency - -- Swap the position in the list - local list = item.parent - local other_item = list.children[new_index] - list.swap_children(old_index, new_index) - - -- Swap the position in the top flow, offset by 1 because of settings button - local top_flow = Gui.get_top_flow(player) - top_flow.swap_children(old_index + 1, new_index + 1) - - -- Check if the element has a left element to move - local element_define = Gui.defines[item.tags.top_element_uid] - local other_define = Gui.defines[other_item.tags.top_element_uid] - if element_define.left_flow_element and other_define.left_flow_element then - local left_element = Gui.get_left_element(player, element_define.left_flow_element) - local other_left_element = Gui.get_left_element(player, other_define.left_flow_element) - local left_index = left_element.get_index_in_parent() - local other_index = other_left_element.get_index_in_parent() - left_element.parent.swap_children(left_index, other_index) - end - - -- If we are moving in/out of first/last place we need to update the move buttons - local last_index = #list.children - if old_index == 1 then -- Moving out of index 1 - other_item.move[move_up.name].enabled = false - item.move[move_up.name].enabled = true - elseif new_index == 1 then -- Moving into index 1 - other_item.move[move_up.name].enabled = true - item.move[move_up.name].enabled = false - elseif old_index == last_index then -- Moving out of the last index - other_item.move[move_down.name].enabled = false - item.move[move_down.name].enabled = true - elseif new_index == last_index then -- Moving into the last index - other_item.move[move_down.name].enabled = true - item.move[move_down.name].enabled = false - end - - -- Update the datastore state - ToolbarState:update(player, function(_, order) - local tmp = order[old_index] - order[old_index] = order[new_index] - order[new_index] = tmp - end) -end - ---- Reorder the toolbar buttons -local function reorder_toolbar_menu(player) - local frame = Gui.get_left_element(player, toolbar_container) - local list = frame.container.scroll.list - local order = ToolbarState:get(player) - local last_index = #order - - -- Reorder the buttons - for index, state in ipairs(order) do - local element_define = Gui.defines[state.element_uid] - - -- Switch item order - local item = list[element_define.name] - list.swap_children(index, item.get_index_in_parent()) - - -- Check if the player is allowed to see the button - local allowed = element_define.authenticator - if type(allowed) == "function" then allowed = allowed(player) end - - -- Update the checkbox state and item visibility - local toolbar_button = Gui.get_top_element(player, element_define) - toolbar_button.visible = allowed and state.favourite or false - item.checkbox.state = state.favourite - - -- Update the state if the move buttons - item.move[move_up.name].enabled = index ~= 1 - item.move[move_down.name].enabled = index ~= last_index - end - - -- Update the state of the toggle button - local button = frame.container.header.alignment[toggle_toolbar.name] - button.enabled = Gui.top_flow_has_visible_elements(player) - button.toggled = Gui.get_top_flow(player).parent.visible -end - ---- Resets the toolbar to its default state when pressed --- @element reset_toolbar -local reset_toolbar = - Gui.element{ - type = "sprite-button", - sprite = "utility/reset", - style = "shortcut_bar_button_red", - tooltip = { "toolbar.reset" }, - name = Gui.unique_static_name, - } - :style(Gui.sprite_style(Styles.header.width, -1)) - :on_click(function(player) - ToolbarState:set(player, nil) - Gui.toggle_top_flow(player, true) - reorder_toolbar_menu(player) - end) - ---- Replaces the default method for opening and closing the toolbar --- @element toggle_toolbar -toggle_toolbar = - Gui.element{ - type = "sprite-button", - sprite = "utility/bookmark", - tooltip = { "toolbar.toggle" }, - style = "tool_button", - auto_toggle = true, - name = Gui.unique_static_name, - } - :style(Styles.header) - :on_click(function(player, element) - Gui.toggle_top_flow(player, element.toggled) - end) - ---- Move an element up the list --- @element move_up -move_up = - Gui.element{ - type = "sprite-button", - sprite = "utility/speed_up", - tooltip = { "toolbar.move-up" }, - name = Gui.unique_static_name, - } - :style(Styles.item) - :on_click(function(player, element) - local item = element.parent.parent - move_toolbar_button(player, item, -1) - end) - ---- Move an element down the list --- @element move_down -move_down = - Gui.element{ - type = "sprite-button", - sprite = "utility/speed_down", - tooltip = { "toolbar.move-down" }, - name = Gui.unique_static_name, - } - :style(Styles.item) - :on_click(function(player, element) - local item = element.parent.parent - move_toolbar_button(player, item, 1) - end) - ---- A flow which represents one item in the toolbar list --- @element toolbar_list_item -local toolbar_list_item = - Gui.element(function(definition, parent, element_define) - local flow = parent.add{ - type = "frame", - style = "shortcut_selection_row", - name = element_define.name, - tags = { - top_element_uid = element_define.uid, - }, - } - flow.style.horizontally_stretchable = true - flow.style.vertical_align = "center" - - -- Add the button and the icon edit button - local element = element_define(flow) - local player = Gui.get_player_from_element(parent) - local top_element = Gui.get_top_element(player, element_define) - copy_style(top_element, element) - - -- Add the checkbox that can toggle the visibility - local checkbox = flow.add{ - type = "checkbox", - name = "checkbox", - caption = element_define.tooltip or element_define.caption or "None", - state = top_element.visible or false, - tags = { - top_element_name = element_define.name, - }, - } - definition:triggers_events(checkbox) - checkbox.style.width = 180 - - -- Add the buttons used to move the flow up and down - local move_flow = flow.add{ type = "flow", name = "move" } - move_flow.style.horizontal_spacing = 0 - move_up(move_flow) - move_down(move_flow) - - return definition:no_events(flow) - end) - :on_checked_changed(function(player, element) - local top_flow = Gui.get_top_flow(player) - local top_element = top_flow[element.tags.top_element_name] - local had_visible = Gui.top_flow_has_visible_elements(player) - top_element.visible = element.state - - -- Check if we are on the edge case between 0 and 1 visible elements - if element.state and not had_visible then - Gui.toggle_top_flow(player, true) - local container = element.parent.parent.parent.parent - local button = container.header.alignment[toggle_toolbar.name] - button.toggled = true - button.enabled = true - elseif not element.state and not Gui.top_flow_has_visible_elements(player) then - Gui.toggle_top_flow(player, false) - local container = element.parent.parent.parent.parent - local button = container.header.alignment[toggle_toolbar.name] - button.toggled = false - button.enabled = false - end - - -- Update the datastore state - ToolbarState:update(player, function(_, order) - local index = element.parent.get_index_in_parent() - order[index].favourite = element.state - end) - end) - ---- Scrollable list of all toolbar buttons --- @element toolbar_list -local toolbar_list = - Gui.element(function(_, parent) - -- This is a scroll pane for the list - local scroll_pane = parent.add{ - name = "scroll", - type = "scroll-pane", - direction = "vertical", - horizontal_scroll_policy = "never", - vertical_scroll_policy = "auto", - style = "scroll_pane_under_subheader", - } - scroll_pane.style.horizontally_stretchable = true - scroll_pane.style.padding = 0 - scroll_pane.style.maximal_height = 224 - - -- This flow is the list, we need a linear list because of get_index_in_parent - local flow = scroll_pane.add{ - name = "list", - type = "flow", - direction = "vertical", - } - flow.style.vertical_spacing = 0 - flow.style.horizontally_stretchable = true - - return flow - end) - ---- Main toolbar container for the left flow --- @element toolbar_container -toolbar_container = - Gui.element(function(definition, parent) - -- Draw the internal container - local container = Gui.container(parent, definition.name, 268) - container.style.maximal_width = 268 - container.style.minimal_width = 268 - - -- Draw the header - local player = Gui.get_player_from_element(parent) - local header = Gui.header(container, { "toolbar.main-caption" }, { "toolbar.main-tooltip" }, true) - - -- Draw the toolbar control buttons - local toggle_element = toggle_toolbar(header) - toggle_element.toggled = Gui.get_top_flow(player).visible - reset_toolbar(header) - - -- Draw toolbar list element - local list_element = toolbar_list(container) - local flow_order = Gui.get_top_flow_order(player) - - for _, element_define in ipairs(flow_order) do - -- Ensure the element exists - local element = list_element[element_define.name] - if not element then - element = toolbar_list_item(list_element, element_define) - end - - -- Set the visible state - local allowed = element_define.authenticator - if type(allowed) == "function" then allowed = allowed(player) end - element.visible = allowed or false - end - - -- Set the state of the move buttons for the first and last element - local children = list_element.children - children[1].move[move_up.name].enabled = false - children[#children].move[move_down.name].enabled = false - - -- Return the external container - return container.parent - end) - :static_name(Gui.unique_static_name) - :add_to_left_flow(false) - ---- Set the default value for the datastore -local datastore_id_map = {} -local toolbar_default_state = {} -ToolbarState:set_default(toolbar_default_state) - ---- Get the datastore id for this element define, to best of ability it should be unique between versions -local function to_datastore_id(element_define) - -- First try to use the tooltip locale string - local tooltip = element_define.tooltip - if type(tooltip) == "table" then - return tooltip[1]:gsub("%.(.+)", "") - end - - -- Then try to use the caption or sprite - return element_define.caption or element_define.sprite -end - ---- For all top element, register an on click which will copy their style -for index, element_define in ipairs(Gui.top_elements) do - -- This is a bit hacky, the gui system cant have multiple handlers registered - local prev_handler = element_define[Gui.events.on_toolbar_button_toggled] - - -- Add the handler for when the button is toggled - element_define:on_event(Gui.events.on_toolbar_button_toggled, function(player, element, event) - if prev_handler then prev_handler(player, element, event) end -- Kind of hacky but works - local frame = Gui.get_left_element(player, toolbar_container) - if not frame then return end -- Gui might not be loaded yet - if not frame.container then - log(frame.name) - log(frame.parent.name) - end - local button = frame.container.scroll.list[element_define.name][element_define.name] - local toolbar_button = Gui.get_top_element(player, element_define) - copy_style(toolbar_button, button) - end) - - -- Insert the element into the id map - local id = to_datastore_id(element_define) - if datastore_id_map[id] then - error(string.format("All toolbar elements need a unique id to be saved correctly, %d (%s) and %d (%s) share the id %s", - datastore_id_map[id].uid, datastore_id_map[id].defined_at, element_define.uid, element_define.defined_at, id - )) - end - datastore_id_map[id] = element_define - - -- Add the element to the default state - table.insert(toolbar_default_state, { - element_uid = element_define.uid, - favourite = true, - }) -end - ---- Get the top order based on the players settings -Gui.inject_top_flow_order(function(player) - local order = ToolbarState:get(player) - - local elements = {} - for index, state in ipairs(order) do - elements[index] = Gui.defines[state.element_uid] - end - - return elements -end) - ---- Get the left order based on the player settings, with toolbar menu first, and all remaining after -Gui.inject_left_flow_order(function(player) - local order = Gui.get_top_flow_order(player) - local elements, element_map = { toolbar_container }, { [toolbar_container] = true } - - -- Add the flows that have a top element - for _, element_define in ipairs(order) do - if element_define.left_flow_element then - table.insert(elements, element_define.left_flow_element) - element_map[element_define.left_flow_element] = true - end - end - - -- Add the flows that dont have a top element - for _, element_define in ipairs(Gui.left_elements) do - if not element_map[element_define] then - table.insert(elements, element_define) - end - end - - return elements -end) - ---- Overwrite the default toggle behaviour and instead toggle this menu -Gui.core_defines.hide_top_flow:on_click(function(player, _, _) - Gui.toggle_left_element(player, toolbar_container) -end) - ---- Overwrite the default toggle behaviour and instead toggle this menu -Gui.core_defines.show_top_flow:on_click(function(player, _, _) - Gui.toggle_left_element(player, toolbar_container) -end) - ---- Overwrite the default update top flow -local _update_top_flow = Gui.update_top_flow -function Gui.update_top_flow(player) - _update_top_flow(player) -- Call the original - - local order = ToolbarState:get(player) - for index, state in ipairs(order) do - local element_define = Gui.defines[state.element_uid] - local top_element = Gui.get_top_element(player, element_define) - top_element.visible = top_element.visible and state.favourite or false - end -end - ---- Uncompress the data to be more useable -ToolbarState:on_load(function(player_name, value) - -- If there is no value, do nothing - if value == nil then return end - - -- Create a hash map of the favourites - local favourites = {} - for _, id in ipairs(value[2]) do - favourites[id] = true - end - - -- Read the order from the value - local elements = {} - local element_hash = {} - for index, id in ipairs(value[1]) do - local element = datastore_id_map[id] - if element and not element_hash[element.uid] then - element_hash[element.uid] = true - elements[index] = { - element_uid = element.uid, - favourite = favourites[id] or false, - } - end - end - - -- Add any in the default state that are missing - for _, state in ipairs(toolbar_default_state) do - if not element_hash[state.element_uid] then - table.insert(elements, table.deep_copy(state)) - end - end - - -- Create a hash map of the open left flows - local left_flows = {} - for _, id in ipairs(value[3]) do - local element = datastore_id_map[id] - if element.left_flow_element then - left_flows[element.left_flow_element] = true - end - end - - -- Set the visible state of all left flows - local player = game.get_player(player_name) - for _, left_element in ipairs(Gui.left_elements) do - Gui.toggle_left_element(player, left_element, left_flows[left_element] or false) - end - - -- Set the toolbar visible state - Gui.toggle_top_flow(player, value[4]) - - -- Set the data now and update now, ideally this would be on_update but that had too large of a latency - ToolbarState:raw_set(player_name, elements) - Gui.reorder_top_flow(player) - Gui.reorder_left_flow(player) - reorder_toolbar_menu(player) - - return elements -end) - ---- Save the current state of the players toolbar menu -ToolbarState:on_save(function(player_name, value) - if value == nil then return nil end -- Don't save default - local order, favourites, left_flows = {}, {}, {} - - local player = game.get_player(player_name) - local top_flow_open = Gui.get_top_flow(player).parent.visible - - for index, state in ipairs(value) do - -- Add the element to the order array - local element_define = Gui.defines[state.element_uid] - local id = to_datastore_id(element_define) - order[index] = id - - -- If its a favourite then insert it - if state.favourite then - table.insert(favourites, id) - end - - -- If it has a left flow and its open then insert it - if element_define.left_flow_element then - local left_element = Gui.get_left_element(player, element_define.left_flow_element) - if left_element.visible then - table.insert(left_flows, id) - end - end - end - - return { order, favourites, left_flows, top_flow_open } -end) diff --git a/exp_legacy/module/modules/gui/vlayer.lua b/exp_legacy/module/modules/gui/vlayer.lua index ec694a61..559d2fd7 100644 --- a/exp_legacy/module/modules/gui/vlayer.lua +++ b/exp_legacy/module/modules/gui/vlayer.lua @@ -4,7 +4,7 @@ @alias vlayer_container ]] -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local format_number = require("util").format_number --- @dep util @@ -81,8 +81,8 @@ Selection.on_selection(SelectionConvertArea, function(event) return nil end - local frame = Gui.get_left_element(player, vlayer_container) - local disp = frame.container["vlayer_st_2"].disp.table + local container = Gui.get_left_element(vlayer_container, player) + local disp = container.frame["vlayer_st_2"].disp.table local target = vlayer_control_type_list[disp[vlayer_gui_control_type.name].selected_index] local entities @@ -143,20 +143,20 @@ end) --- Display label for the number of solar panels -- @element vlayer_gui_display_item_solar_name -local vlayer_gui_display_item_solar_name = - Gui.element{ +local vlayer_gui_display_item_solar_name = Gui.element("vlayer_gui_display_item_solar_name") + :draw{ type = "label", - name = "vlayer_display_item_solar_name", + name = Gui.property_from_name, caption = { "vlayer.display-item-solar" }, style = "heading_2_label", }:style{ width = 200, } -local vlayer_gui_display_item_solar_count = - Gui.element{ +local vlayer_gui_display_item_solar_count = Gui.element("vlayer_gui_display_item_solar_count") + :draw{ type = "progressbar", - name = "vlayer_display_item_solar_count", + name = Gui.property_from_name, caption = "", value = 0, style = "electric_satisfaction_statistics_progressbar", @@ -167,20 +167,20 @@ local vlayer_gui_display_item_solar_count = --- Display label for the number of accumulators -- @element vlayer_gui_display_item_accumulator_name -local vlayer_gui_display_item_accumulator_name = - Gui.element{ +local vlayer_gui_display_item_accumulator_name = Gui.element("vlayer_gui_display_item_accumulator_name") + :draw{ type = "label", - name = "vlayer_display_item_accumulator_name", + name = Gui.property_from_name, caption = { "vlayer.display-item-accumulator" }, style = "heading_2_label", }:style{ width = 200, } -local vlayer_gui_display_item_accumulator_count = - Gui.element{ +local vlayer_gui_display_item_accumulator_count = Gui.element("vlayer_gui_display_item_accumulator_count") + :draw{ type = "progressbar", - name = "vlayer_display_item_accumulator_count", + name = Gui.property_from_name, caption = "", value = 0, style = "electric_satisfaction_statistics_progressbar", @@ -191,10 +191,10 @@ local vlayer_gui_display_item_accumulator_count = --- Display label for the surface area -- @element vlayer_gui_display_signal_surface_area_name -local vlayer_gui_display_signal_surface_area_name = - Gui.element{ +local vlayer_gui_display_signal_surface_area_name = Gui.element("vlayer_gui_display_signal_surface_area_name") + :draw{ type = "label", - name = "vlayer_display_signal_remaining_surface_area_name", + name = Gui.property_from_name, caption = { "vlayer.display-remaining-surface-area" }, tooltip = { "vlayer.display-remaining-surface-area-tooltip" }, style = "heading_2_label", @@ -202,10 +202,10 @@ local vlayer_gui_display_signal_surface_area_name = width = 200, } -local vlayer_gui_display_signal_surface_area_count = - Gui.element{ +local vlayer_gui_display_signal_surface_area_count = Gui.element("vlayer_gui_display_signal_surface_area_count") + :draw{ type = "progressbar", - name = "vlayer_display_signal_surface_area_count", + name = Gui.property_from_name, caption = "", value = 0, style = "electric_satisfaction_statistics_progressbar", @@ -216,10 +216,10 @@ local vlayer_gui_display_signal_surface_area_count = --- Display label for the sustained energy production -- @element vlayer_gui_display_signal_sustained_name -local vlayer_gui_display_signal_sustained_name = - Gui.element{ +local vlayer_gui_display_signal_sustained_name = Gui.element("vlayer_gui_display_signal_sustained_name") + :draw{ type = "label", - name = "vlayer_display_signal_sustained_name", + name = Gui.property_from_name, caption = { "vlayer.display-sustained-production" }, tooltip = { "vlayer.display-sustained-production-tooltip" }, style = "heading_2_label", @@ -227,10 +227,10 @@ local vlayer_gui_display_signal_sustained_name = width = 200, } -local vlayer_gui_display_signal_sustained_count = - Gui.element{ +local vlayer_gui_display_signal_sustained_count = Gui.element("vlayer_gui_display_signal_sustained_count") + :draw{ type = "progressbar", - name = "vlayer_display_signal_sustained_count", + name = Gui.property_from_name, caption = "", value = 0, style = "electric_satisfaction_statistics_progressbar", @@ -241,10 +241,10 @@ local vlayer_gui_display_signal_sustained_count = --- Display label for the current energy production -- @element vlayer_gui_display_signal_production_name -local vlayer_gui_display_signal_production_name = - Gui.element{ +local vlayer_gui_display_signal_production_name = Gui.element("vlayer_gui_display_signal_production_name") + :draw{ type = "label", - name = "vlayer_display_signal_production_name", + name = Gui.property_from_name, caption = { "vlayer.display-current-production" }, tooltip = { "vlayer.display-current-production-tooltip" }, style = "heading_2_label", @@ -252,10 +252,10 @@ local vlayer_gui_display_signal_production_name = width = 200, } -local vlayer_gui_display_signal_production_count = - Gui.element{ +local vlayer_gui_display_signal_production_count = Gui.element("vlayer_gui_display_signal_production_count") + :draw{ type = "progressbar", - name = "vlayer_display_signal_production_count", + name = Gui.property_from_name, caption = "", value = 0, style = "electric_satisfaction_statistics_progressbar", @@ -266,10 +266,10 @@ local vlayer_gui_display_signal_production_count = --- Display label for the sustained energy capacity -- @element vlayer_gui_display_signal_capacity_name -local vlayer_gui_display_signal_capacity_name = - Gui.element{ +local vlayer_gui_display_signal_capacity_name = Gui.element("vlayer_gui_display_signal_capacity_name") + :draw{ type = "label", - name = "vlayer_display_signal_capacity_name", + name = Gui.property_from_name, caption = { "vlayer.display-current-capacity" }, tooltip = { "vlayer.display-current-capacity-tooltip" }, style = "heading_2_label", @@ -277,10 +277,10 @@ local vlayer_gui_display_signal_capacity_name = width = 200, } -local vlayer_gui_display_signal_capacity_count = - Gui.element{ +local vlayer_gui_display_signal_capacity_count = Gui.element("vlayer_gui_display_signal_capacity_count") + :draw{ type = "progressbar", - name = "vlayer_display_signal_capacity_count", + name = Gui.property_from_name, caption = "", value = 0, style = "electric_satisfaction_statistics_progressbar", @@ -291,10 +291,10 @@ local vlayer_gui_display_signal_capacity_count = --- A vertical flow containing all the displays labels and their counts -- @element vlayer_display_set -local vlayer_display_set = - Gui.element(function(_, parent, name) +local vlayer_display_set = Gui.element("vlayer_display_set") + :draw(function(_, parent, name) local vlayer_set = parent.add{ type = "flow", direction = "vertical", name = name } - local disp = Gui.scroll_table(vlayer_set, 400, 2, "disp") + local disp = Gui.elements.scroll_table(vlayer_set, 400, 2, "disp") vlayer_gui_display_item_solar_name(disp) vlayer_gui_display_item_solar_count(disp) @@ -313,8 +313,8 @@ local vlayer_display_set = end) local function vlayer_gui_list_refresh(player) - local frame = Gui.get_left_element(player, vlayer_container) - local disp = frame.container["vlayer_st_2"].disp.table + local container = Gui.get_left_element(vlayer_container, player) + local disp = container.frame["vlayer_st_2"].disp.table local target = disp[vlayer_gui_control_type.name].selected_index local full_list = {} @@ -331,58 +331,59 @@ end --- A drop down list filter by this type -- @element vlayer_gui_control_type -vlayer_gui_control_type = - Gui.element{ +vlayer_gui_control_type = Gui.element("vlayer_gui_control_type") + :draw{ type = "drop-down", - name = Gui.unique_static_name, + name = Gui.property_from_name, items = { { "vlayer.control-type-energy" }, { "vlayer.control-type-circuit" }, { "vlayer.control-type-storage-input" }, { "vlayer.control-type-storage-output" } }, selected_index = 1, }:style{ width = 200, - }:on_selection_changed(function(player, _, _) + }:on_selection_state_changed(function(def, player, element) vlayer_gui_list_refresh(player) end) --- A drop down list to see the exact item to remove -- @element vlayer_gui_control_list -vlayer_gui_control_list = - Gui.element{ +vlayer_gui_control_list = Gui.element("vlayer_gui_control_list") + :draw{ type = "drop-down", - name = Gui.unique_static_name, + name = Gui.property_from_name, }:style{ width = 200, } --- A button to refresh the remove list -- @element vlayer_gui_control_refresh -local vlayer_gui_control_refresh = - Gui.element{ +local vlayer_gui_control_refresh = Gui.element("vlayer_gui_control_refresh") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "vlayer.control-refresh" }, }:style{ width = 200, - }:on_click(function(player, _, _) + }:on_click(function(def, player, element) vlayer_gui_list_refresh(player) end) --- A button to check if the item is the one wanted to remove -- @element vlayer_gui_control_see -local vlayer_gui_control_see = - Gui.element{ +local vlayer_gui_control_see = Gui.element("vlayer_gui_control_see") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "vlayer.control-see" }, }:style{ width = 200, - }:on_click(function(player, element, _) + }:on_click(function(def, player, element, event) local target = element.parent[vlayer_gui_control_type.name].selected_index local n = element.parent[vlayer_gui_control_list.name].selected_index - + if target and vlayer_control_type_list[target] and n > 0 then local i = vlayer.get_interfaces() local entity = i[vlayer_control_type_list[target]][n] if entity and entity.valid then + local player = Gui.get_player(event) player.set_controller{ type = defines.controllers.remote, position = entity.position, surface = entity.surface } player.print{ "vlayer.result-interface-location", { "vlayer.control-type-" .. vlayer_control_type_list[target]:gsub("_", "-") }, pos_to_gps_string(entity.position, entity.surface.name) } end @@ -391,14 +392,14 @@ local vlayer_gui_control_see = --- A button used to build the vlayer interface -- @element vlayer_gui_control_build -local vlayer_gui_control_build = - Gui.element{ +local vlayer_gui_control_build = Gui.element("vlayer_gui_control_build") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "vlayer.control-build" }, }:style{ width = 200, - }:on_click(function(player, _, _) + }:on_click(function(def, player, element) if Selection.is_selecting(player, SelectionConvertArea) then Selection.stop(player) player.print{ "vlayer.exit" } @@ -412,14 +413,14 @@ local vlayer_gui_control_build = --- A button used to remove the vlayer interface -- @element vlayer_gui_control_remove -local vlayer_gui_control_remove = - Gui.element{ +local vlayer_gui_control_remove = Gui.element("vlayer_gui_control_remove") + :draw{ type = "button", - name = Gui.unique_static_name, + name = Gui.property_from_name, caption = { "vlayer.control-remove" }, }:style{ width = 200, - }:on_click(function(player, element, _) + }:on_click(function(def, player, element) local target = element.parent[vlayer_gui_control_type.name].selected_index local n = element.parent[vlayer_gui_control_list.name].selected_index @@ -440,10 +441,10 @@ local vlayer_gui_control_remove = --- A vertical flow containing all the control buttons -- @element vlayer_control_set -local vlayer_control_set = - Gui.element(function(_, parent, name) +local vlayer_control_set = Gui.element("vlayer_control_set") + :draw(function(_, parent, name) local vlayer_set = parent.add{ type = "flow", direction = "vertical", name = name } - local disp = Gui.scroll_table(vlayer_set, 400, 2, "disp") + local disp = Gui.elements.scroll_table(vlayer_set, 400, 2, "disp") vlayer_gui_control_type(disp) vlayer_gui_control_list(disp) @@ -457,10 +458,10 @@ local vlayer_control_set = --- The main container for the vlayer gui -- @element vlayer_container -vlayer_container = - Gui.element(function(definition, parent) - local player = Gui.get_player_from_element(parent) - local container = Gui.container(parent, definition.name, 400) +vlayer_container = Gui.element("vlayer_container") + :draw(function(definition, parent) + local player = Gui.get_player(parent) + local container = Gui.elements.container(parent, 400) vlayer_display_set(container, "vlayer_st_1") local control_set = vlayer_control_set(container, "vlayer_st_2") @@ -468,21 +469,25 @@ vlayer_container = return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() ---- Button on the top flow used to toggle the task list container --- @element toggle_left_element -Gui.left_toolbar_button("entity/solar-panel", { "vlayer.main-tooltip" }, vlayer_container, function(player) - return Roles.player_allowed(player, "gui/vlayer") -end) +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(vlayer_container, false) +Gui.toolbar.create_button{ + name = "vlayer_toggle", + left_element = vlayer_container, + sprite = "entity/solar-panel", + tooltip = { "vlayer.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/vlayer") + end +} --- Update the visibly of the buttons based on a players roles local function role_update_event(event) local player = game.players[event.player_index] local visible = Roles.player_allowed(player, "gui/vlayer-edit") - local frame = Gui.get_left_element(player, vlayer_container) - frame.container["vlayer_st_2"].visible = visible + local container = Gui.get_left_element(vlayer_container, player) + container.frame["vlayer_st_2"].visible = visible end Event.add(Roles.events.on_role_assigned, role_update_event) @@ -521,8 +526,8 @@ Event.on_nth_tick(config.update_tick_gui, function(_) } for _, player in pairs(game.connected_players) do - local frame = Gui.get_left_element(player, vlayer_container) - local disp = frame.container["vlayer_st_1"].disp.table + local container = Gui.get_left_element(vlayer_container, player) + local disp = container.frame["vlayer_st_1"].disp.table for k, v in pairs(vlayer_display) do disp[k].caption = v.cap diff --git a/exp_legacy/module/modules/gui/warp-list.lua b/exp_legacy/module/modules/gui/warp-list.lua index e96de051..cda5ea21 100644 --- a/exp_legacy/module/modules/gui/warp-list.lua +++ b/exp_legacy/module/modules/gui/warp-list.lua @@ -5,7 +5,7 @@ ]] local ExpUtil = require("modules/exp_util") -local Gui = require("modules.exp_legacy.expcore.gui") --- @dep expcore.gui +local Gui = require("modules/exp_gui") local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore local Storage = require("modules/exp_util/storage") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event @@ -78,18 +78,18 @@ end --- Will add a new warp to the list, checks if the player is too close to an existing one -- @element add_new_warp -local add_new_warp = - Gui.element{ +local add_new_warp = Gui.element("add_new_warp") + :draw{ type = "sprite-button", sprite = "utility/add", tooltip = { "warp-list.add-tooltip" }, style = "shortcut_bar_button", - name = Gui.unique_static_name, + name = Gui.property_from_name, } :style(Styles.sprite22) - :on_click(function(player, _) - -- Add the new warp + :on_click(function(def, player, element) if player.controller_type ~= defines.controllers.character then return end + -- Add the new warp local force_name = player.force.name local surface = player.physical_surface local position = player.physical_position @@ -158,8 +158,8 @@ local add_new_warp = --- Warp icon button, this will trigger a warp when the player is able to -- @element warp_icon_button -local warp_icon_button = - Gui.element(function(definition, parent, warp) +local warp_icon_button = Gui.element("warp_icon_button") + :draw(function(def, parent, warp) local warp_position = warp.position -- The SpritePath type is not the same as the SignalID type @@ -172,14 +172,13 @@ local warp_icon_button = return parent.add{ type = "sprite-button", sprite = sprite, - name = definition.name, + name = def.name, tooltip = { "warp-list.goto-tooltip", warp_position.x, warp_position.y }, style = "slot_button", } end) :style(Styles.sprite32) - :static_name(Gui.unique_static_name) - :on_click(function(player, element, _) + :on_click(function(def, player, element) if element.type == "choose-elem-button" then return end local warp_id = element.parent.caption Warps.teleport_player(warp_id, player) @@ -194,23 +193,22 @@ local warp_icon_button = --- The button that is visible when the warp is in edit state -- @element warp_icon_editing -local warp_icon_editing = - Gui.element(function(definition, parent, warp) +local warp_icon_editing = Gui.element("warp_icon_editing") + :draw(function(def, parent, warp) return parent.add{ - name = definition.name, + name = def.name, type = "choose-elem-button", elem_type = "signal", signal = { type = warp.icon.type, name = warp.icon.name }, tooltip = { "warp-list.goto-edit" }, } end) - :static_name(Gui.unique_static_name) :style(Styles.sprite32) --- Warp label, visible if the player is not in edit state -- @element warp_label -local warp_label = - Gui.element(function(definition, parent, warp) +local warp_label = Gui.element("warp_label") + :draw(function(def, parent, warp) local last_edit_name = warp.last_edit_name local last_edit_time = warp.last_edit_time -- Draw the element @@ -218,7 +216,7 @@ local warp_label = type = "label", caption = warp.name, tooltip = { "warp-list.last-edit", last_edit_name, format_time(last_edit_time) }, - name = definition.name, + name = def.name, } end) :style{ @@ -227,21 +225,20 @@ local warp_label = right_padding = 2, horizontally_stretchable = true, } - :on_click(function(player, element, _) + :on_click(function(def, player, element) local warp_id = element.parent.caption local warp = Warps.get_warp(warp_id) player.set_controller{ type = defines.controllers.remote, position = warp.position, surface = warp.surface } end) - :static_name(Gui.unique_static_name) --- Warp status, visible if the player is not in edit state --- This will show if the warp is connected or not -- @element warp_status -local warp_status = - Gui.element{ +local warp_status = Gui.element("warp_status") + :draw{ type = "label", caption = "[img=utility/electricity_icon_unplugged]", -- Temporary icon - name = Gui.unique_static_name, + name = Gui.property_from_name, } :style{ -- When editing mode because textbox is larger the icon would move up. @@ -251,14 +248,14 @@ local warp_status = --- Warp textfield, visible if the player is in edit state -- @element warp_textfield -local warp_textfield = - Gui.element(function(definition, parent, warp) +local warp_textfield = Gui.element("warp_textfield") + :draw(function(def, parent, warp) -- Draw the element return parent.add{ type = "textfield", text = warp.name, clear_and_focus_on_right_click = true, - name = definition.name, + name = def.name, } end) :style{ @@ -273,46 +270,47 @@ local warp_textfield = left_margin = 2, right_margin = 2, } - :on_confirmed(function(player, element, _) + :on_confirmed(function(def, player, element) local warp_id = element.parent.caption local warp_name = element.text - local warp_icon = element.parent.parent["icon-" .. warp_id][warp_icon_editing.name].elem_value + local warp_icon = element.parent.parent["icon-" .. warp_id][warp_icon_editing.name].elem_value --[[ @as SignalID ]] + if warp_icon.type == nil then warp_icon.type = "item" end Warps.set_editing(warp_id, player.name) Warps.update_warp(warp_id, warp_name, warp_icon, player.name) end) - :static_name(Gui.unique_static_name) --- Confirms the edit to name or icon of the warp -- @element confirm_edit_button -local confirm_edit_button = - Gui.element{ +local confirm_edit_button = Gui.element("confirm_edit_button") + :draw{ type = "sprite-button", sprite = "utility/confirm_slot", tooltip = { "warp-list.confirm-tooltip" }, style = "shortcut_bar_button_green", - name = Gui.unique_static_name, + name = Gui.property_from_name, } :style(Styles.sprite22) - :on_click(function(player, element) + :on_click(function(def, player, element) local warp_id = element.parent.caption local warp_name = element.parent.parent["name-" .. warp_id][warp_textfield.name].text - local warp_icon = element.parent.parent["icon-" .. warp_id][warp_icon_editing.name].elem_value + local warp_icon = element.parent.parent["icon-" .. warp_id][warp_icon_editing.name].elem_value --[[ @as SignalID ]] + if warp_icon.type == nil then warp_icon.type = "item" end Warps.set_editing(warp_id, player.name) Warps.update_warp(warp_id, warp_name, warp_icon, player.name) end) --- Cancels the editing changes of the selected warp name or icon -- @element cancel_edit_button -local cancel_edit_button = - Gui.element{ +local cancel_edit_button = Gui.element("cancel_edit_button") + :draw{ type = "sprite-button", sprite = "utility/close_black", tooltip = { "warp-list.cancel-tooltip" }, style = "shortcut_bar_button_red", - name = Gui.unique_static_name, + name = Gui.property_from_name, } :style(Styles.sprite22) - :on_click(function(player, element) + :on_click(function(def, player, element) local warp_id = element.parent.caption -- Check if this is the first edit, if so remove the warp. local warp = Warps.get_warp(warp_id) @@ -325,32 +323,32 @@ local cancel_edit_button = --- Removes a warp from the list, including the physical area and map tag -- @element remove_warp_button -local remove_warp_button = - Gui.element{ +local remove_warp_button = Gui.element("remove_warp_button") + :draw{ type = "sprite-button", sprite = "utility/trash", tooltip = { "warp-list.remove-tooltip" }, style = "shortcut_bar_button_red", - name = Gui.unique_static_name, + name = Gui.property_from_name, } :style(Styles.sprite22) - :on_click(function(_, element) + :on_click(function(def, player, element) local warp_id = element.parent.caption Warps.remove_warp(warp_id) end) --- Opens edit mode for the warp -- @element edit_warp_button -local edit_warp_button = - Gui.element{ +local edit_warp_button = Gui.element("edit_warp_button") + :draw{ type = "sprite-button", sprite = "utility/rename_icon", tooltip = { "warp-list.edit-tooltip-none" }, style = "shortcut_bar_button", - name = Gui.unique_static_name, + name = Gui.property_from_name, } :style(Styles.sprite22) - :on_click(function(player, element) + :on_click(function(def, player, element) local warp_id = element.parent.caption Warps.set_editing(warp_id, player.name, true) end) @@ -358,8 +356,8 @@ local edit_warp_button = local update_all_warp_elements --- Set of three elements which make up each row of the warp table -- @element add_warp_elements -local add_warp_elements = - Gui.element(function(_, parent, warp) +local add_warp_elements = Gui.element("add_warp_elements") + :draw(function(_, parent, warp) -- Add icon flow, this will contain the warp button and warp icon edit button local icon_flow = parent.add{ name = "icon-" .. warp.warp_id, @@ -398,9 +396,6 @@ local add_warp_elements = cancel_edit_button(button_flow) edit_warp_button(button_flow) remove_warp_button(button_flow) - - -- Return the warp flow elements - return { icon_flow, name_flow, button_flow } end) -- Removes the three elements that are added as part of the warp base @@ -412,10 +407,10 @@ end --- This timer controls when a player is able to warp, eg every 60 seconds -- @element warp_timer -local warp_timer = - Gui.element{ +local warp_timer = Gui.element("warp_timer") + :draw{ type = "progressbar", - name = Gui.unique_static_name, + name = Gui.property_from_name, tooltip = { "warp-list.timer-tooltip-zero", config.cooldown_duration }, minimum_value = 0, maximum_value = config.cooldown_duration * config.update_smoothing, @@ -499,10 +494,10 @@ end --- Update the warp buttons for a player function update_all_warp_elements(player, timer, warp_id) -- Get the warp table - local frame = Gui.get_left_element(player, warp_list_container) - local scroll_table = frame.container.scroll.table + local container = Gui.get_left_element(warp_list_container, player) + local scroll_table = container.frame.scroll.table - -- Check if the player is currenty on cooldown + -- Check if the player is currently on cooldown timer = timer or PlayerCooldown:get(player) local on_cooldown = timer > 0 -- Get the warp the player is on @@ -627,8 +622,8 @@ end local function update_all_warp_force(force) local warp_ids = Warps.get_force_warp_ids(force.name) for _, player in pairs(force.connected_players) do - local frame = Gui.get_left_element(player, warp_list_container) - local warp_table = frame.container.scroll.table + local container = Gui.get_left_element(warp_list_container, player) + local warp_table = container.frame.scroll.table warp_table.clear() -- Needed to re-sort the warps for _, warp_id in ipairs(warp_ids) do @@ -639,20 +634,20 @@ end --- Main warp list container for the left flow -- @element warp_list_container -warp_list_container = - Gui.element(function(definition, parent) - local player = Gui.get_player_from_element(parent) +warp_list_container = Gui.element("warp_list_container") + :draw(function(def, parent) + local player = Gui.get_player(parent) -- Check if user has permission to add warps local allow_add_warp = check_player_permissions(player, "allow_add_warp") -- Draw the internal container - local container = Gui.container(parent, definition.name, allow_add_warp and 268 or 220) + local container = Gui.elements.container(parent, allow_add_warp and 268 or 220) -- Draw the header - local header = Gui.header( - container, - { "warp-list.main-caption" }, - { + local header = Gui.elements.header(container, { + name = "header", + caption = { "warp-list.main-caption" }, + tooltip = { "warp-list.sub-tooltip", config.cooldown_duration, config.standard_proximity_radius, @@ -663,15 +658,14 @@ warp_list_container = { "warp-list.sub-tooltip-not_available", warp_status_icons.not_available }, { "warp-list.sub-tooltip-bypass", warp_status_icons.bypass }, }, - true - ) + }) -- Draw the new warp button local add_new_warp_element = add_new_warp(header) add_new_warp_element.visible = allow_add_warp -- Draw the scroll table for the warps - local scroll_table = Gui.scroll_table(container, 250, 3) + local scroll_table = Gui.elements.scroll_table(container, 250, 3, "scroll") -- Set the scroll panel to always show the scrollbar (not doing this will result in a changing gui size) scroll_table.parent.vertical_scroll_policy = "always" @@ -699,18 +693,21 @@ warp_list_container = -- Return the external container return container.parent end) - :static_name(Gui.unique_static_name) - :add_to_left_flow() ---- Button on the top flow used to toggle the warp list container --- @element toggle_warp_list -Gui.left_toolbar_button(config.default_icon.type .. "/" .. config.default_icon.name, { "warp-list.main-tooltip" }, warp_list_container, function(player) - return Roles.player_allowed(player, "gui/warp-list") +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(warp_list_container, false) +Gui.toolbar.create_button{ + name = "warp_list_toggle", + left_element = warp_list_container, + sprite = config.default_icon.type .. "/" .. config.default_icon.name, + tooltip = { "warp-list.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/warp-list") + end +}:on_click(function(def, player, element) + -- Set gui keep open state for player that clicked the button: true if visible, false if invisible + keep_gui_open[player.name] = Gui.toolbar.get_button_toggled_state(def, player) end) - :on_event(Gui.events.on_visibility_changed_by_click, function(player, _, event) - -- Set gui keep open state for player that clicked the button: true if visible, false if invisible - keep_gui_open[player.name] = event.state - end) --- When the name of a warp is updated this is triggered Warps.on_update(function(_, warp, old_warp) @@ -728,7 +725,7 @@ PlayerInRange:on_update(function(player_name, warp_id) -- Change if the frame is visible based on if the player is in range if not keep_gui_open[player.name] then - Gui.toggle_left_element(player, warp_list_container, warp_id ~= nil) + Gui.toolbar.set_left_element_visible_state(warp_list_container, player, warp_id ~= nil) end update_all_warp_elements(player, nil, warp_id) @@ -738,8 +735,8 @@ end) PlayerCooldown:on_update(function(player_name, player_cooldown) -- Get the progress bar element local player = game.players[player_name] - local frame = Gui.get_left_element(player, warp_list_container) - local warp_timer_element = frame.container[warp_timer.name] + local container = Gui.get_left_element(warp_list_container, player) + local warp_timer_element = container.frame[warp_timer.name] -- Set the progress if player_cooldown and player_cooldown > 0 then @@ -818,8 +815,8 @@ Event.on_nth_tick(math.floor(60 / config.update_smoothing), function() end -- Change the enabled state of the add warp button - local frame = Gui.get_left_element(player, warp_list_container) - local add_warp_element = frame.container.header.alignment[add_new_warp.name] + local container = Gui.get_left_element(warp_list_container, player) + local add_warp_element = container.frame.header.flow[add_new_warp.name] local old_closest_warp_name = add_warp_element.tooltip[2] or closest_warp and closest_warp.name local was_able_to_make_warp = add_warp_element.enabled local can_make_warp = closest_distance == nil or closest_distance > mr2 @@ -858,27 +855,27 @@ end) --- Update the warps when the player joins Event.add(defines.events.on_player_joined_game, function(event) local player = game.players[event.player_index] - local frame = Gui.get_left_element(player, warp_list_container) - local scroll_table = frame.container.scroll.table + local container = Gui.get_left_element(warp_list_container, player) + local scroll_table = container.frame.scroll.table update_all_warps(player, scroll_table) end) --- Makes sure the right buttons are present when roles change local function role_update_event(event) local player = game.players[event.player_index] - local container = Gui.get_left_element(player, warp_list_container).container + local frame = Gui.get_left_element(warp_list_container, player).frame -- Check if user has permission to add warps local allow_add_warp = check_player_permissions(player, "allow_add_warp") -- Update container size depending on whether the player is allowed to add warps - container.parent.style.width = allow_add_warp and 268 or 220 + frame.parent.style.width = allow_add_warp and 268 or 220 - -- Update the warps, incase the user can now edit them - local scroll_table = container.scroll.table + -- Update the warps, in case the user can now edit them + local scroll_table = frame.scroll.table update_all_warps(player, scroll_table) - -- Update the new warp button incase the user can now add them - local add_new_warp_element = container.header.alignment[add_new_warp.name] + -- Update the new warp button in case the user can now add them + local add_new_warp_element = frame.header.flow[add_new_warp.name] add_new_warp_element.visible = allow_add_warp end diff --git a/exp_legacy/module/utils/gui.lua b/exp_legacy/module/utils/gui.lua index 7ea8347b..c20da522 100644 --- a/exp_legacy/module/utils/gui.lua +++ b/exp_legacy/module/utils/gui.lua @@ -1,6 +1,6 @@ local Storage = require("modules/exp_util/storage") -local Event = require("modules/exp_legacy/utils/event") --- @dep expcore.gui -local mod_gui = require "mod-gui" --- @dep mod-gui +local Event = require("modules/exp_legacy/utils/event") +local mod_gui = require "mod-gui" local Gui = {} local data = {} diff --git a/exp_scenario/module/commands/_rcon.lua b/exp_scenario/module/commands/_rcon.lua index e29ecb11..7ce7ed0d 100644 --- a/exp_scenario/module/commands/_rcon.lua +++ b/exp_scenario/module/commands/_rcon.lua @@ -5,8 +5,9 @@ Adds rcon interfaces for the legacy exp core local Commands = require("modules/exp_commands") local add_static, add_dynamic = Commands.add_rcon_static, Commands.add_rcon_dynamic +add_static("Gui", require("modules/exp_gui")) + add_static("Group", require("modules.exp_legacy.expcore.permission_groups")) add_static("Roles", require("modules.exp_legacy.expcore.roles")) -add_static("Gui", require("modules.exp_legacy.expcore.gui")) add_static("Datastore", require("modules.exp_legacy.expcore.datastore")) add_static("External", require("modules.exp_legacy.expcore.external")) diff --git a/exp_util/module/flying_text.lua b/exp_util/module/flying_text.lua index 3a1bc43f..f7ecaec3 100644 --- a/exp_util/module/flying_text.lua +++ b/exp_util/module/flying_text.lua @@ -36,16 +36,18 @@ end --- @class FlyingText.create_above_entity_param:FlyingText.create_param --- @field target_entity? LuaEntity The entity to create the text above +--- @field offset? { x: number, y: number } Offset to move the text by --- Create flying above an entity, overrides the position option of FlyingText.create --- @param options FlyingText.create_above_entity_param function FlyingText.create_above_entity(options) local entity = assert(options.target_entity, "A target entity is required") local size_y = entity.bounding_box.left_top.y - entity.bounding_box.right_bottom.y + local offset = options.offset or { x = 0, y = 0 } options.position = { - x = entity.position.x, - y = entity.position.y - size_y * 0.25, + x = offset.x + entity.position.x, + y = offset.y + entity.position.y + size_y * 0.25, } FlyingText.create(options) @@ -53,6 +55,7 @@ end --- @class FlyingText.create_above_player_param:FlyingText.create_param --- @field target_player? LuaPlayer The player to create the text above +--- @field offset? { x: number, y: number } Offset to move the text by --- Create flying above a player, overrides the position option of FlyingText.create --- @param options FlyingText.create_above_player_param @@ -60,10 +63,11 @@ function FlyingText.create_above_player(options) local player = assert(options.target_player, "A target player is required") local entity = player.character; if not entity then return end local size_y = entity.bounding_box.left_top.y - entity.bounding_box.right_bottom.y + local offset = options.offset or { x = 0, y = 0 } options.position = { - x = entity.position.x, - y = entity.position.y - size_y * 0.25, + x = offset.x + entity.position.x, + y = offset.y + entity.position.y + size_y * 0.25, } FlyingText.create(options) @@ -71,6 +75,7 @@ end --- @class FlyingText.create_as_player_param:FlyingText.create_param --- @field target_player? LuaPlayer The player to create the text above +--- @field offset? { x: number, y: number } Offset to move the text by --- Create flying above a player, overrides the position and color option of FlyingText.create --- @param options FlyingText.create_as_player_param @@ -78,11 +83,12 @@ function FlyingText.create_as_player(options) local player = assert(options.target_player, "A target player is required") local entity = player.character; if not entity then return end local size_y = entity.bounding_box.left_top.y - entity.bounding_box.right_bottom.y + local offset = options.offset or { x = 0, y = 0 } options.color = player.chat_color options.position = { - x = entity.position.x, - y = entity.position.y - size_y * 0.25, + x = offset.x + entity.position.x, + y = offset.y + entity.position.y + size_y * 0.25, } FlyingText.create(options) diff --git a/exp_util/module/module_exports.lua b/exp_util/module/module_exports.lua index b484d54c..e1deb308 100644 --- a/exp_util/module/module_exports.lua +++ b/exp_util/module/module_exports.lua @@ -167,6 +167,18 @@ function ExpUtil.get_function_name(func, raw) return "<" .. file_name .. ":" .. func_name .. ">" end +--- Returns a desync sale filepath and current line for a given stack frame, default is the current file +--- @param level number? The level of the stack to get the file of, a value of 1 is the caller of this function +--- @param raw boolean? When true there will not be any < > around the name +--- @return string # The relative filepath of the given stack frame +function ExpUtil.get_current_line(level, raw) + local debug_info = getinfo((level or 1) + 1, "Snl") + local safe_source = debug_info.source:find("@__level__") + local file_path = safe_source == 1 and debug_info.short_src:sub(10, -5) or debug_info.source + if raw then return file_path .. ":" .. debug_info.currentline end + return "<" .. file_path .. ":" .. debug_info.currentline .. ">" +end + --- Attempt a simple autocomplete search from a set of options --- @param options table The table representing the possible options which can be selected --- @param input string The user input string which should be matched to an option diff --git a/exp_util/module/storage.lua b/exp_util/module/storage.lua index f064040c..5ec30318 100644 --- a/exp_util/module/storage.lua +++ b/exp_util/module/storage.lua @@ -49,15 +49,16 @@ local ExpUtil = require("modules/exp_util") --- @class ExpUtil_Storage local Storage = { - _registered = {}, --- @type table Map of all registered values and their initial values + _registered = {}, --- @type table } --- Register a new table to be stored in storage, can only be called once per file, can not be called during runtime --- @generic T:table --- @param tbl T The initial value for the table you are registering, this should be a local variable --- @param callback fun(tbl: T) The callback used to replace local references and metatables +--- @param on_init fun(tbl: T)? The callback used to setup/validate storage if a static value is not enough -- This function does not return the table because the callback can't access the local it would be assigned to -function Storage.register(tbl, callback) +function Storage.register(tbl, callback, on_init) ExpUtil.assert_not_runtime() ExpUtil.assert_argument_type(tbl, "table", 1, "tbl") ExpUtil.assert_argument_type(callback, "function", 2, "callback") @@ -70,6 +71,7 @@ function Storage.register(tbl, callback) Storage._registered[name] = { init = tbl, callback = callback, + on_init = on_init, } end @@ -110,6 +112,9 @@ function Storage.on_init() if exp_storage[name] == nil then exp_storage[name] = info.init end + if info.on_init then + info.on_init(exp_storage[name]) + end info.callback(exp_storage[name]) end end diff --git a/tsconfig.json b/tsconfig.json index 754a5810..e6634ddf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "references": [ { "path": "./exp_commands/" }, { "path": "./exp_groups/" }, + { "path": "./exp_gui/" }, { "path": "./exp_legacy/" }, { "path": "./exp_scenario/" }, { "path": "./exp_util/" },