diff --git a/exp_gui/module/control.lua b/exp_gui/module/control.lua index 9aaa52cf..5aad01b2 100644 --- a/exp_gui/module/control.lua +++ b/exp_gui/module/control.lua @@ -103,7 +103,7 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function ExpGui.get_top_element(define, player) - return player_elements[player.index].top[define.name] + 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 @@ -111,7 +111,7 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function ExpGui.get_left_element(define, player) - return player_elements[player.index].left[define.name] + 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 @@ -119,14 +119,7 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function ExpGui.get_relative_element(define, player) - return player_elements[player.index].relative[define.name] -end - ---- Return all top level elements for a player ----@param player LuaPlayer ----@return ExpGui.player_elements -function ExpGui._get_player_elements(player) - return player_elements[player.index] + 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 @@ -177,10 +170,10 @@ function ExpGui._ensure_elements(event) ensure_elements(player, ExpGui.left_elements, elements.left, ExpGui.get_left_flow(player)) ensure_elements(player, ExpGui.relative_elements, elements.relative, player.gui.relative) - -- Check technically not needed, but makes it easier to use this lib without the core defines - if ExpGui.apply_consistency_checks then - ExpGui.apply_consistency_checks(player, true) - end + --- @diagnostic disable-next-line invisible + ExpGui.toolbar._ensure_elements(player) + --- @diagnostic disable-next-line invisible + ExpGui.toolbar._ensure_consistency(player) end --- Rerun the visible check for relative elements diff --git a/exp_gui/module/core_elements.lua b/exp_gui/module/core_elements.lua deleted file mode 100644 index a1879036..00000000 --- a/exp_gui/module/core_elements.lua +++ /dev/null @@ -1,306 +0,0 @@ - ---- @class ExpGui -local ExpGui = require("modules/exp_gui") -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 elements = {} --- @type table -local buttons_with_left_element = {} --- @type table -local left_elements_with_button = {} --- @type table - -ExpGui.on_toolbar_button_toggled = script.generate_event_name() - ---- @class EventData.on_toolbar_button_toggled: EventData ---- @field element LuaGuiElement ---- @field state boolean - ---- Set the style of a toolbar button ---- @param element LuaGuiElement ---- @param state boolean? ---- @return boolean -function ExpGui.set_toolbar_button_style(element, state) - if state == nil then state = element.style.name == toolbar_button_default_style end - element.style = state and toolbar_button_active_style or toolbar_button_default_style - - local style = element.style - style.minimal_width = toolbar_button_size - style.height = toolbar_button_size - if element.type == "sprite-button" then - style.padding = -2 - else - style.font = "default-semibold" - style.padding = 0 - end - - return state -end - ---- Get the state of a toolbar button ---- @param element LuaGuiElement ---- @return boolean -function ExpGui.get_toolbar_button_state(element) - return element.style.name == toolbar_button_active_style -end - ---- Set the visible state of the top flow for a player ---- @param player LuaPlayer ---- @param state boolean? -function ExpGui.set_top_flow_visible(player, state) - local top_flow = assert(ExpGui.get_top_flow(player).parent, player.name) - local show_top_flow = elements.core_button_flow.data[player].show_top_flow --- @type LuaGuiElement - - if state == nil then - state = not top_flow.visible - end - - top_flow.visible = state - show_top_flow.visible = not state -end - ---- Set the visible state of the left element for a player ---- @param define ExpElement ---- @param player LuaPlayer ---- @param state boolean? -function ExpGui.set_left_element_visible(define, player, state) - local element = assert(ExpGui.get_left_element(define, player), "Define is not added to the left flow") - local clear_left_flow = elements.core_button_flow.data[player].clear_left_flow --- @type LuaGuiElement - - -- Update the visible state - if state == nil then state = not element.visible end - element.visible = state - - -- Check if there is a toolbar button linked to this element - local toolbar_button = left_elements_with_button[define.name] - if toolbar_button then - ExpGui.set_toolbar_button_style(ExpGui.get_top_element(toolbar_button, player), state) - end - - -- If visible state is true, then we don't need to check all elements - if state then - clear_left_flow.visible = true - return - end - - -- Check if any left elements are visible - --- @diagnostic disable-next-line invisible - local player_elements = ExpGui._get_player_elements(player) - local flow_name = elements.core_button_flow.name - for name, left_element in pairs(player_elements.left) do - if left_element.visible and name ~= flow_name then - clear_left_flow.visible = true - return - end - end - - -- There are no visible left elements, so can hide the button - clear_left_flow.visible = false -end - ---- @class ExpGui.create_toolbar_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.create_toolbar_button__param ---- @return ExpElement -function ExpGui.create_toolbar_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) - :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.name] = 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, event) - local state = ExpGui.set_toolbar_button_style(event.element) - if left_element then - local player = ExpGui.get_player(event) - ExpGui.set_left_element_visible(left_element, player, state) - end - script.raise_event(ExpGui.on_toolbar_button_toggled, { - element = event.element, - state = state, - }) - end) - end - - -- Add the define to the top flow and return - ExpGui.add_top_element(toolbar_button, visible) - return toolbar_button -end - ---- Update the consistency of the core elements and registered elements ---- @param player LuaPlayer ---- @param skip_ensure boolean? -function ExpGui.apply_consistency_checks(player, skip_ensure) - if not skip_ensure then - --- @diagnostic disable-next-line invisible - ExpGui._ensure_elements{ player_index = player.index } - end - - -- Get the core buttons for the player - local core_button_data = elements.core_button_flow.data[player] - local hide_top_flow = ExpGui.get_top_element(elements.hide_top_flow, player) - local show_top_flow = core_button_data.show_top_flow --- @type LuaGuiElement - local clear_left_flow = core_button_data.clear_left_flow --- @type LuaGuiElement - - -- Check if any top elements are visible, this includes ones not controlled by this module - local has_top_elements = false - local top_flow = ExpGui.get_top_flow(player) - for _, element in pairs(top_flow.children) do - if element.visible and element ~= hide_top_flow then - has_top_elements = true - break - end - end - - -- The show button is only visible when the flow isn't visible but does have visible children - show_top_flow.visible = has_top_elements and not top_flow.visible or false - - --- @diagnostic disable-next-line invisible - local player_elements = ExpGui._get_player_elements(player) - local left_elements, top_elements = player_elements.left, player_elements.top - - --- Update the styles of toolbar buttons with left elements - for name, top_element in pairs(top_elements) do - local left_element = buttons_with_left_element[name] - if left_element then - local element = assert(left_elements[left_element.name], left_element.name) - ExpGui.set_toolbar_button_style(top_element, element.visible) - end - end - - -- Check if any left elements are visible - local flow_name = elements.core_button_flow.name - for name, left_element in pairs(left_elements) do - if left_element.visible and name ~= flow_name then - clear_left_flow.visible = true - return - end - end - - -- There are no visible left elements, so can hide the button - clear_left_flow.visible = false -end - ---- Hides the top flow when clicked -elements.hide_top_flow = ExpGui.element("hide_top_flow") - :draw{ - type = "sprite-button", - sprite = "utility/preset", - style = "tool_button", - tooltip = { "exp-gui.hide-top-flow" }, - } - :style{ - padding = -2, - width = 18, - height = 36, - } - :on_click(function(def, event) - local player = ExpGui.get_player(event) - ExpGui.set_top_flow_visible(player, false) - end) - ---- Shows the top flow when clicked -elements.show_top_flow = ExpGui.element("show_top_flow") - :draw{ - type = "sprite-button", - sprite = "utility/preset", - style = "tool_button", - tooltip = { "exp-gui.show-top-flow" }, - } - :style{ - padding = -2, - width = 18, - height = 20, - } - :on_click(function(def, event) - local player = ExpGui.get_player(event) - ExpGui.set_top_flow_visible(player, true) - end) - ---- Hides all left elements when clicked -elements.clear_left_flow = ExpGui.element("clear_left_flow") - :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, event) - local player = ExpGui.get_player(event) - event.element.visible = false - - --- @diagnostic disable-next-line invisible - local player_elements = ExpGui._get_player_elements(player) - local flow_name = elements.core_button_flow.name - for name, left_element in pairs(player_elements.left) do - if name ~= flow_name then - left_element.visible = false - local toolbar_button = left_elements_with_button[name] - if toolbar_button then - ExpGui.set_toolbar_button_style(ExpGui.get_top_element(toolbar_button, player), false) - end - 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", - } - - local player = ExpGui.get_player(parent) - def.data[player] = { - show_top_flow = elements.show_top_flow(flow), - clear_left_flow = elements.clear_left_flow(flow), - } - - return flow - end) - -ExpGui.add_top_element(elements.hide_top_flow, true) -ExpGui.add_left_element(elements.core_button_flow, true) - -return elements diff --git a/exp_gui/module/iter.lua b/exp_gui/module/iter.lua index a6fb57c7..450a7a52 100644 --- a/exp_gui/module/iter.lua +++ b/exp_gui/module/iter.lua @@ -6,7 +6,7 @@ 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 ExpGui_GuiIter.ReturnType +--- @alias ExpGui_GuiIter.ReturnType fun(): LuaPlayer?, LuaGuiElement? --- @type table>> local registered_scopes = {} @@ -25,6 +25,7 @@ end) --- @class ExpGui_GuiIter local GuiIter = { + _scopes = registered_scopes, } local function nop() return nil, nil end @@ -215,10 +216,10 @@ function GuiIter.add_element(scope, element) registered_scopes[scope] = scope_elements end - local player_elements = registered_scopes[element.player_index] + local player_elements = scope_elements[element.player_index] if not player_elements then player_elements = {} - registered_scopes[element.player_index] = player_elements + scope_elements[element.player_index] = player_elements end player_elements[element.index] = element diff --git a/exp_gui/module/locale/en.cfg b/exp_gui/module/locale/en.cfg index 29321c42..931539da 100644 --- a/exp_gui/module/locale/en.cfg +++ b/exp_gui/module/locale/en.cfg @@ -1,5 +1,13 @@ [exp-gui] -hide-top-flow=Hide Toolbar. -show-top-flow=Show Toolbar. -clear-left-flow=Hide all open windows. \ No newline at end of file +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 index 2566ff90..c30eede0 100644 --- a/exp_gui/module/module.json +++ b/exp_gui/module/module.json @@ -7,9 +7,9 @@ "control.lua" ], "require": [ - "core_elements.lua", "elements.lua", - "styles.lua" + "styles.lua", + "toolbar.lua" ], "dependencies": { "clusterio": "*", diff --git a/exp_gui/module/prototype.lua b/exp_gui/module/prototype.lua index 32dddf3a..c7353ccc 100644 --- a/exp_gui/module/prototype.lua +++ b/exp_gui/module/prototype.lua @@ -98,7 +98,7 @@ function ExpElement.create(name) data = GuiData.create(element_name), _events = {}, _debug = { - defined_at = ExpUtil.safe_file_path(2), + defined_at = ExpUtil.get_current_line(2), }, } @@ -106,6 +106,13 @@ function ExpElement.create(name) 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 @@ -179,6 +186,15 @@ function ExpElement._prototype:track_all_elements() 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 diff --git a/exp_gui/module/toolbar.lua b/exp_gui/module/toolbar.lua new file mode 100644 index 00000000..277f3745 --- /dev/null +++ b/exp_gui/module/toolbar.lua @@ -0,0 +1,714 @@ + +--- @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 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, event) + local player = ExpGui.get_player(event) + 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 + ExpGui.add_top_element(toolbar_button, visible) + return toolbar_button +end + +--- Ensure all the toolbar buttons are in a consistent state +--- @param player LuaPlayer +function Toolbar._ensure_consistency(player) + -- 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 + + -- Update the state of buttons with left elements + for left_element, button in pairs(left_elements_with_button) do + local element = ExpGui.get_left_element(left_element, player) + Toolbar.set_button_toggled_state(button, player, element.visible) + end +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, event, element) + local player = ExpGui.get_player(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, event, element) + local player = ExpGui.get_player(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, event, element) + element.visible = false + local player = ExpGui.get_player(event) + 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 + + --[[ 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 + +--- @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 top_flow = ExpGui.get_top_flow(player) + + -- Reorder the buttons + 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()) + + -- Update the item visibility + local element_define = ExpElement.get(item_state.name) + local allowed = ExpGui.top_elements[element_define] + local toolbar_button = ExpGui.get_top_element(element_define, player) + if type(allowed) == "function" then allowed = allowed(player, toolbar_button) end + top_flow.swap_children(index + 1, toolbar_button.get_index_in_parent()) + toolbar_button.visible = allowed and item_state.favourite or false + item.visible = toolbar_button.visible + + -- 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 + end + + -- Update the state of the toggle button + 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 + +--- @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 + +--- Ensure the toolbar settings gui has all its elements +--- @param player LuaPlayer +function Toolbar._ensure_elements(player) + -- Add any missing items to the gui + local toolbar_list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]] + 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 + + -- Set the state of the move buttons for the first and last element + local children = toolbar_list.children + 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 + +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, event, element) + local player = ExpGui.get_player(event) + 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, event, element) + local player = ExpGui.get_player(event) + --ToolbarState:set(player, nil) + 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, event, element) + local player = ExpGui.get_player(event) + 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, event, element) + local player = ExpGui.get_player(event) + 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, event, element) + local player = ExpGui.get_player(event) + 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 + + --[[ Update the datastore state + ToolbarState:update(player, function(_, order) + local index = element.parent.get_index_in_parent() + order[index].favourite = element.state + 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._ensure_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_legacy/module/config/_file_loader.lua b/exp_legacy/module/config/_file_loader.lua index 90d07169..2410ba8c 100644 --- a/exp_legacy/module/config/_file_loader.lua +++ b/exp_legacy/module/config/_file_loader.lua @@ -41,6 +41,7 @@ return { -- 'modules.data.bonus', "modules.data.personal-logistic", "modules.data.language", + --"modules.data.toolbar", --- GUI "modules.gui.readme", @@ -60,7 +61,6 @@ return { "modules.gui.production", "modules.gui.playerdata", "modules.gui.surveillance", - --"modules.gui.toolbar", -- must be loaded last to register toolbar handlers "modules.graftorio.require", -- graftorio --- Config Files diff --git a/exp_legacy/module/locale/en/gui.cfg b/exp_legacy/module/locale/en/gui.cfg index 729196f9..6ac641bb 100644 --- a/exp_legacy/module/locale/en/gui.cfg +++ b/exp_legacy/module/locale/en/gui.cfg @@ -320,14 +320,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/modules/addons/tree-decon.lua b/exp_legacy/module/modules/addons/tree-decon.lua index 15c2c020..0fa81f11 100644 --- a/exp_legacy/module/modules/addons/tree-decon.lua +++ b/exp_legacy/module/modules/addons/tree-decon.lua @@ -35,7 +35,7 @@ end local HasEnabledDecon = PlayerData.Settings:combine("HasEnabledDecon") HasEnabledDecon:set_default(false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "toggle-tree-decon", sprite = "entity/tree-01", tooltip = { "tree-decon.main-tooltip" }, @@ -43,11 +43,11 @@ Gui.create_toolbar_button{ visible = function(player, _) return Roles.player_allowed(player, "fast-tree-decon") end -}:on_event(Gui.on_toolbar_button_toggled, function(def, event) - --- @cast event EventData.on_toolbar_button_toggled +}:on_click(function(def, event, element) local player = Gui.get_player(event) - HasEnabledDecon:set(player, event.state) - player.print{ "tree-decon.toggle-msg", event.state and { "tree-decon.enabled" } or { "tree-decon.disabled" } } + 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) -- Add trees to queue when marked, only allows simple entities and for players with role permission diff --git a/exp_legacy/module/modules/data/toolbar.lua b/exp_legacy/module/modules/data/toolbar.lua new file mode 100644 index 00000000..be1b8dd8 --- /dev/null +++ b/exp_legacy/module/modules/data/toolbar.lua @@ -0,0 +1,190 @@ +local Gui = require("modules/exp_gui") +local ExpElement = require("modules/exp_gui/prototype") +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, +} + +--- Set the default value for the datastore +local datastore_id_map = {} --- @type table +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 + -- Insert the element into the id map + datastore_id_map[to_datastore_id(element_define)] = element_define -- Backwards Compatibility + datastore_id_map[element_define.name] = element_define + + -- Add the element to the default state + table.insert(toolbar_default_state, { + element = 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] + 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 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] + 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 + --- @cast value [ string[], string[], string[], boolean ] + + -- 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.name] then + element_hash[element.name] = true + elements[index] = { + element = element, + 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] then + table.insert(elements, table.deep_copy(state)) + end + end + + -- Create a hash map of the open left flows + local open_left_elements = {} + for _, id in ipairs(value[3]) do + local element = datastore_id_map[id] + local left_element = element.left_flow_element + if left_element then + open_left_elements[left_element] = true + end + end + + -- Set the visible state of all left flows + local player = assert(game.get_player(player_name)) + for left_element in pairs(Gui.left_elements) do + Gui.set_left_element_visible(left_element, player, open_left_elements[left_element] or false) + end + + -- Set the toolbar visible state + Gui.set_top_flow_visible(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 = assert(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 + --- @diagnostic disable-next-line invisible + local element_define = ExpElement._elements[state.element] + 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(element_define.left_flow_element, player) + 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/autofill.lua b/exp_legacy/module/modules/gui/autofill.lua index 5d4f690c..ffe5446b 100644 --- a/exp_legacy/module/modules/gui/autofill.lua +++ b/exp_legacy/module/modules/gui/autofill.lua @@ -296,7 +296,7 @@ autofill_container = Gui.element("autofill_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(autofill_container, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "autofill_toggle", left_element = autofill_container, sprite = config.icon, diff --git a/exp_legacy/module/modules/gui/bonus.lua b/exp_legacy/module/modules/gui/bonus.lua index a80ac57b..cfb880da 100644 --- a/exp_legacy/module/modules/gui/bonus.lua +++ b/exp_legacy/module/modules/gui/bonus.lua @@ -343,7 +343,7 @@ bonus_container = Gui.element("bonus_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(bonus_container, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "bonus_toggle", left_element = bonus_container, sprite = "item/exoskeleton-equipment", 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 a152305f..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,4 +1,6 @@ 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 @@ -87,11 +89,23 @@ Gui.on_click( element.style.font_color = Color.orange data.selected_header = element - input_text_box.text = concat{ "ExpElement._elements[", element_name, "]" } + input_text_box.text = concat{ "ExpElement._elements[\"", element_name, "\"]" } input_text_box.style.font_color = Color.black --- @diagnostic disable-next-line invisible - local content = dump(ExpElement._elements[element_name]) or "nil" + 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/landfill.lua b/exp_legacy/module/modules/gui/landfill.lua index 35643061..0ea1d193 100644 --- a/exp_legacy/module/modules/gui/landfill.lua +++ b/exp_legacy/module/modules/gui/landfill.lua @@ -167,7 +167,7 @@ local function landfill_gui_add_landfill(blueprint) end --- Add the toolbar button -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "landfill", sprite = "item/landfill", tooltip = { "landfill.main-tooltip" }, diff --git a/exp_legacy/module/modules/gui/module.lua b/exp_legacy/module/modules/gui/module.lua index a7fd0e51..f9cc6977 100644 --- a/exp_legacy/module/modules/gui/module.lua +++ b/exp_legacy/module/modules/gui/module.lua @@ -306,7 +306,7 @@ module_container = Gui.element("module_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(module_container, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "module_toggle", left_element = module_container, sprite = "item/productivity-module-3", diff --git a/exp_legacy/module/modules/gui/player-list.lua b/exp_legacy/module/modules/gui/player-list.lua index 0fb2858a..d56bdd8b 100644 --- a/exp_legacy/module/modules/gui/player-list.lua +++ b/exp_legacy/module/modules/gui/player-list.lua @@ -271,7 +271,7 @@ local player_list_container = Gui.element("player_list_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(player_list_container, true) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "player_list_toggle", left_element = player_list_container, sprite = "entity/character", diff --git a/exp_legacy/module/modules/gui/playerdata.lua b/exp_legacy/module/modules/gui/playerdata.lua index 13c47fc4..c73bb7a5 100644 --- a/exp_legacy/module/modules/gui/playerdata.lua +++ b/exp_legacy/module/modules/gui/playerdata.lua @@ -191,7 +191,7 @@ pd_container = Gui.element("pd_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(pd_container, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "player_data_toggle", left_element = pd_container, sprite = "item/power-armor-mk2", diff --git a/exp_legacy/module/modules/gui/production.lua b/exp_legacy/module/modules/gui/production.lua index f957f6d0..8da29140 100644 --- a/exp_legacy/module/modules/gui/production.lua +++ b/exp_legacy/module/modules/gui/production.lua @@ -122,7 +122,7 @@ production_container = Gui.element("production_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(production_container, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "production_toggle", left_element = production_container, sprite = "entity/assembling-machine-3", diff --git a/exp_legacy/module/modules/gui/readme.lua b/exp_legacy/module/modules/gui/readme.lua index b5657aaa..c5b4d717 100644 --- a/exp_legacy/module/modules/gui/readme.lua +++ b/exp_legacy/module/modules/gui/readme.lua @@ -438,19 +438,17 @@ local readme = Gui.element("readme") end) :on_opened(function(def, event) local player = Gui.get_player(event) - local button = Gui.get_top_element(readme_toggle, player) - Gui.set_toolbar_button_style(button, true) + Gui.toolbar.set_button_toggled_state(readme_toggle, player, true) end) :on_closed(function(def, event, element) local player = Gui.get_player(event) - local button = Gui.get_top_element(readme_toggle, player) - Gui.set_toolbar_button_style(button, false) + Gui.toolbar.set_button_toggled_state(readme_toggle, player, false) Gui.destroy_if_valid(element) end) --- Toggle button for the readme gui readme_toggle = - Gui.create_toolbar_button{ + Gui.toolbar.create_button{ name = "readme_toggle", auto_toggle = true, sprite = "virtual-signal/signal-info", diff --git a/exp_legacy/module/modules/gui/research.lua b/exp_legacy/module/modules/gui/research.lua index 7defc66b..79aad0b9 100644 --- a/exp_legacy/module/modules/gui/research.lua +++ b/exp_legacy/module/modules/gui/research.lua @@ -277,7 +277,7 @@ local research_container = Gui.element("research_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(research_container, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "research_toggle", left_element = research_container, sprite = "item/space-science-pack", diff --git a/exp_legacy/module/modules/gui/rocket-info.lua b/exp_legacy/module/modules/gui/rocket-info.lua index b543b2c2..c6b12a55 100644 --- a/exp_legacy/module/modules/gui/rocket-info.lua +++ b/exp_legacy/module/modules/gui/rocket-info.lua @@ -494,7 +494,7 @@ local rocket_list_container = Gui.element("rocket_list_container") 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.create_toolbar_button{ +Gui.toolbar.create_button{ name = "rocket_list_toggle", left_element = rocket_list_container, sprite = "item/rocket-silo", diff --git a/exp_legacy/module/modules/gui/science-info.lua b/exp_legacy/module/modules/gui/science-info.lua index 65ee9c28..0da0451f 100644 --- a/exp_legacy/module/modules/gui/science-info.lua +++ b/exp_legacy/module/modules/gui/science-info.lua @@ -333,7 +333,7 @@ local science_info = Gui.element("science_info") --- Add the element to the left flow with a toolbar button Gui.add_left_element(science_info, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "science_info_toggle", left_element = science_info, sprite = "entity/lab", diff --git a/exp_legacy/module/modules/gui/surveillance.lua b/exp_legacy/module/modules/gui/surveillance.lua index dadb2965..b3ef6ef7 100644 --- a/exp_legacy/module/modules/gui/surveillance.lua +++ b/exp_legacy/module/modules/gui/surveillance.lua @@ -128,7 +128,7 @@ local cctv_container = Gui.element("cctv_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(cctv_container, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "cctv_toggle", left_element = cctv_container, sprite = "entity/radar", diff --git a/exp_legacy/module/modules/gui/task-list.lua b/exp_legacy/module/modules/gui/task-list.lua index 15fad44f..cf310d74 100644 --- a/exp_legacy/module/modules/gui/task-list.lua +++ b/exp_legacy/module/modules/gui/task-list.lua @@ -557,7 +557,7 @@ 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.create_toolbar_button{ +Gui.toolbar.create_button{ name = "task_list_toggle", left_element = task_list_container, sprite = "utility/not_enough_repair_packs_icon", diff --git a/exp_legacy/module/modules/gui/tool.lua b/exp_legacy/module/modules/gui/tool.lua index ef453c7e..c21e15d1 100644 --- a/exp_legacy/module/modules/gui/tool.lua +++ b/exp_legacy/module/modules/gui/tool.lua @@ -252,7 +252,7 @@ tool_container = Gui.element("tool_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(tool_container, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "tool_toggle", left_element = tool_container, sprite = "item/repair-pack", 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 fec2ab7c..3b5ada8b 100644 --- a/exp_legacy/module/modules/gui/vlayer.lua +++ b/exp_legacy/module/modules/gui/vlayer.lua @@ -469,7 +469,7 @@ vlayer_container = Gui.element("vlayer_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(vlayer_container, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "vlayer_toggle", left_element = vlayer_container, sprite = "entity/solar-panel", diff --git a/exp_legacy/module/modules/gui/warp-list.lua b/exp_legacy/module/modules/gui/warp-list.lua index 376612f7..64da174b 100644 --- a/exp_legacy/module/modules/gui/warp-list.lua +++ b/exp_legacy/module/modules/gui/warp-list.lua @@ -703,7 +703,7 @@ warp_list_container = Gui.element("warp_list_container") --- Add the element to the left flow with a toolbar button Gui.add_left_element(warp_list_container, false) -Gui.create_toolbar_button{ +Gui.toolbar.create_button{ name = "warp_list_toggle", left_element = warp_list_container, sprite = config.default_icon.type .. "/" .. config.default_icon.name, @@ -714,7 +714,7 @@ Gui.create_toolbar_button{ }:on_click(function(def, event, element) -- Set gui keep open state for player that clicked the button: true if visible, false if invisible local player = Gui.get_player(event) - keep_gui_open[player.name] = Gui.get_toolbar_button_state(element) + keep_gui_open[player.name] = Gui.toolbar.get_button_toggled_state(def, player) end) --- When the name of a warp is updated this is triggered @@ -733,7 +733,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.set_left_element_visible(warp_list_container, player, 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) 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