Convert toolbar minus datastore

This commit is contained in:
Cooldude2606
2025-01-28 02:03:06 +00:00
parent a2b4fd119b
commit e34a320d97
30 changed files with 999 additions and 895 deletions

View File

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

View File

@@ -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",

View File

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

View File

@@ -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" },

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

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

View File

@@ -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",

View File

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