Add toolbar saving and better consistency

This commit is contained in:
Cooldude2606
2025-01-29 23:03:54 +00:00
parent e34a320d97
commit 824c5fe772
5 changed files with 120 additions and 238 deletions

View File

@@ -130,18 +130,17 @@ end
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 = define(parent)
element = assert(define(parent), "Element define did not return an element: " .. define.name)
elements[define.name] = element
assert(element, "Element define did not return an element: " .. define.name)
end
if type(visible) == "function" then
visible = visible(player, element)
end
element.visible = visible
done[define.name] = true
end
end
for name, element in pairs(elements) do
@@ -154,7 +153,7 @@ end
--- Ensure all elements have been created
--- @param event EventData.on_player_created | EventData.on_player_joined_game
function ExpGui._ensure_elements(event)
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
@@ -171,7 +170,7 @@ function ExpGui._ensure_elements(event)
ensure_elements(player, ExpGui.relative_elements, elements.relative, player.gui.relative)
--- @diagnostic disable-next-line invisible
ExpGui.toolbar._ensure_elements(player)
ExpGui.toolbar._create_elements(player)
--- @diagnostic disable-next-line invisible
ExpGui.toolbar._ensure_consistency(player)
end
@@ -202,8 +201,8 @@ end
local e = defines.events
local events = {
[e.on_player_created] = ExpGui._ensure_elements,
[e.on_player_joined_game] = ExpGui._ensure_elements,
[e.on_player_created] = ExpGui._ensure_consistency,
[e.on_player_joined_game] = ExpGui._ensure_consistency,
[e.on_gui_opened] = on_gui_opened,
}

View File

@@ -16,6 +16,7 @@ ExpGui.toolbar = Toolbar
local elements = {}
Toolbar.elements = elements
local toolbar_buttons = {} --- @type ExpElement[]
local left_elements_with_button = {} --- @type table<ExpElement, ExpElement>
local buttons_with_left_element = {} --- @type table<string, ExpElement>
@@ -214,38 +215,11 @@ function Toolbar.create_button(options)
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
--- 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{
@@ -388,13 +362,6 @@ local function move_toolbar_button(player, item, offset)
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 }[]
@@ -404,35 +371,35 @@ end
--- @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())
-- Update the item visibility
-- Switch the toolbar button order
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
-- 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
@@ -470,11 +437,35 @@ function Toolbar.set_state(player, state)
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._ensure_elements(player)
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)
@@ -482,14 +473,64 @@ function Toolbar._ensure_elements(player)
end
end
-- Set the state of the move buttons for the first and last element
-- 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
@@ -542,7 +583,6 @@ elements.reset_toolbar = ExpGui.element("reset_toolbar")
})
: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)
@@ -616,12 +656,6 @@ elements.set_favourite = ExpGui.element("set_favourite")
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")
@@ -703,7 +737,7 @@ elements.toolbar_settings = ExpGui.element("toolbar_settings")
elements.reset_toolbar(header)
def.data[player] = elements.toolbar_list(frame)
Toolbar._ensure_elements(player)
Toolbar._create_elements(player)
return frame.parent
end)

View File

@@ -61,6 +61,7 @@ return {
"modules.gui.production",
"modules.gui.playerdata",
"modules.gui.surveillance",
"modules.gui._role_updates",
"modules.graftorio.require", -- graftorio
--- Config Files

View File

@@ -1,190 +1,31 @@
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
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(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)
stringify = function()
return "Toolbar is saved on exit"
end,
}
--- Set the default value for the datastore
local datastore_id_map = {} --- @type table<string, ExpElement>
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 ]
-- Old format, we discard it [ string[], string[], string[], boolean ]
if type(value) ~= "string" 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.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 decompressed = helpers.json_to_table(assert(helpers.decode_string(value), "Failed String Decode"))
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
Gui.toolbar.set_state(player, decompressed --[[ @as ExpGui.ToolbarState ]])
-- 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
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, value)
if value == nil then return nil end -- Don't save default
local order, favourites, left_flows = {}, {}, {}
ToolbarState:on_save(function(player_name, _)
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 }
local value = Gui.toolbar.get_state(player)
return helpers.encode_string(helpers.table_to_json(value))
end)

View File

@@ -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)