486 lines
21 KiB
Lua
486 lines
21 KiB
Lua
-- This dialog works as the product picker currently, but could also work as an ingredient picker down the line
|
|
-- ** ITEM PICKER **
|
|
local function select_item_group(modal_data, new_group_id)
|
|
modal_data.selected_group_id = new_group_id
|
|
|
|
for group_id, group_elements in pairs(modal_data.modal_elements.groups) do
|
|
local selected_group = (group_id == new_group_id)
|
|
group_elements.button.enabled = not selected_group
|
|
group_elements.scroll_pane.visible = selected_group
|
|
end
|
|
end
|
|
|
|
local function search_picker_items(player, search_term)
|
|
local modal_data = util.globals.modal_data(player)
|
|
local modal_elements = modal_data.modal_elements
|
|
|
|
-- Groups are indexed continuously, so using ipairs here is fine
|
|
local first_visible_group_id = nil
|
|
for group_id, group in ipairs(modal_elements.groups) do
|
|
local any_item_visible = false
|
|
|
|
for _, subgroup_table in pairs(group.subgroup_tables) do
|
|
for item_data, element in pairs(subgroup_table) do
|
|
-- Can only get to this if translations are complete, as the textfield is disabled otherwise
|
|
local visible = (search_term == item_data.name)
|
|
or (string.find(item_data.translated_name, search_term, 1, true) ~= nil)
|
|
element.visible = visible
|
|
any_item_visible = any_item_visible or visible
|
|
end
|
|
end
|
|
|
|
group.button.visible = any_item_visible
|
|
first_visible_group_id = first_visible_group_id or ((any_item_visible) and group_id or nil)
|
|
end
|
|
|
|
local any_result_found = (first_visible_group_id ~= nil)
|
|
modal_elements.warning_label.visible = not any_result_found
|
|
modal_elements.filter_frame.visible = any_result_found
|
|
|
|
if first_visible_group_id ~= nil then
|
|
local selected_group_id = modal_data.selected_group_id
|
|
local is_selected_group_visible = modal_elements.groups[selected_group_id].button.visible
|
|
local group_id_to_select = is_selected_group_visible and selected_group_id or first_visible_group_id
|
|
select_item_group(modal_data, group_id_to_select)
|
|
end
|
|
end
|
|
|
|
local function add_item_picker(parent_flow, player)
|
|
local player_table = util.globals.player_table(player)
|
|
local ui_state = player_table.ui_state
|
|
local modal_elements = ui_state.modal_data.modal_elements
|
|
local translations = player_table.translation_tables
|
|
|
|
local label_warning = parent_flow.add{type="label", caption={"fp.error_message", {"fp.no_item_found"}}}
|
|
label_warning.style.font = "heading-2"
|
|
label_warning.style.margin = 12
|
|
label_warning.visible = false -- There can't be a warning upon first opening of the dialog
|
|
modal_elements["warning_label"] = label_warning
|
|
|
|
-- Item picker (optimized for performance, so not everything is done in the obvious way)
|
|
local groups_per_row = MAGIC_NUMBERS.groups_per_row
|
|
local table_item_groups = parent_flow.add{type="table", style="filter_group_table", column_count=groups_per_row}
|
|
table_item_groups.style.width = 71 * groups_per_row
|
|
table_item_groups.style.horizontal_spacing = 0
|
|
table_item_groups.style.vertical_spacing = 0
|
|
|
|
local frame_filters = parent_flow.add{type="frame", style="fp_frame_slot_table"}
|
|
modal_elements["filter_frame"] = frame_filters
|
|
|
|
local group_id_cache, group_flow_cache, subgroup_table_cache = {}, {}, {}
|
|
modal_elements.groups = {}
|
|
|
|
local existing_products = {}
|
|
if not ui_state.modal_data.create_subfactory then -- check if this is for a new subfactory or not
|
|
for _, product in pairs(Subfactory.get_in_order(ui_state.context.subfactory, "Product")) do
|
|
existing_products[product.proto.name] = true
|
|
end
|
|
end
|
|
|
|
local items_per_row = MAGIC_NUMBERS.items_per_row
|
|
local current_item_rows, max_item_rows = 0, 0
|
|
local current_items_in_table_count = 0
|
|
for _, item_proto in ipairs(SORTED_ITEMS) do
|
|
if not item_proto.hidden and not item_proto.ingredient_only then
|
|
local group_name = item_proto.group.name
|
|
local group_id = group_id_cache[group_name]
|
|
local flow_subgroups, subgroup_tables = nil, nil
|
|
|
|
if group_id == nil then
|
|
local cache_count = table_size(group_id_cache) + 1
|
|
group_id_cache[group_name] = cache_count
|
|
group_id = cache_count
|
|
|
|
local button_group = table_item_groups.add{type="sprite-button", sprite=("item-group/" .. group_name),
|
|
tags={mod="fp", on_gui_click="select_picker_item_group", group_id=group_id},
|
|
style="fp_sprite-button_group_tab", tooltip=item_proto.group.localised_name,
|
|
mouse_button_filter={"left"}}
|
|
|
|
-- This only exists when button_group also exists
|
|
local scroll_pane_subgroups = frame_filters.add{type="scroll-pane",
|
|
style="fp_scroll-pane_slot_table"}
|
|
scroll_pane_subgroups.style.vertically_stretchable = true
|
|
|
|
local frame_subgroups = scroll_pane_subgroups.add{type="frame", style="slot_button_deep_frame"}
|
|
frame_subgroups.style.vertically_stretchable = true
|
|
|
|
-- This flow is only really needed to set the correct vertical spacing
|
|
flow_subgroups = frame_subgroups.add{type="flow", name="flow_group", direction="vertical"}
|
|
flow_subgroups.style.vertical_spacing = 0
|
|
group_flow_cache[group_id] = flow_subgroups
|
|
|
|
modal_elements.groups[group_id] = {
|
|
button = button_group,
|
|
frame = frame_subgroups,
|
|
scroll_pane = scroll_pane_subgroups,
|
|
subgroup_tables = {}
|
|
}
|
|
subgroup_tables = modal_elements.groups[group_id].subgroup_tables
|
|
|
|
-- Catch up on adding the last item flow's row count
|
|
current_item_rows = current_item_rows + math.ceil(current_items_in_table_count / items_per_row)
|
|
current_items_in_table_count = 0
|
|
|
|
max_item_rows = math.max(current_item_rows, max_item_rows)
|
|
current_item_rows = 0
|
|
else
|
|
flow_subgroups = group_flow_cache[group_id]
|
|
subgroup_tables = modal_elements.groups[group_id].subgroup_tables
|
|
end
|
|
|
|
local subgroup_name = item_proto.subgroup.name
|
|
local table_subgroup = subgroup_table_cache[subgroup_name]
|
|
local subgroup_table = nil
|
|
|
|
if table_subgroup == nil then
|
|
table_subgroup = flow_subgroups.add{type="table", column_count=items_per_row,
|
|
style="filter_slot_table"}
|
|
table_subgroup.style.horizontally_stretchable = true
|
|
subgroup_table_cache[subgroup_name] = table_subgroup
|
|
|
|
subgroup_tables[subgroup_name] = {}
|
|
subgroup_table = subgroup_tables[subgroup_name]
|
|
|
|
current_item_rows = current_item_rows + math.ceil(current_items_in_table_count / items_per_row)
|
|
current_items_in_table_count = 0
|
|
else
|
|
subgroup_table = subgroup_tables[subgroup_name]
|
|
end
|
|
|
|
current_items_in_table_count = current_items_in_table_count + 1
|
|
|
|
local item_name = item_proto.name
|
|
local existing_product = existing_products[item_name]
|
|
local button_style = (existing_product) and "flib_slot_button_red" or "flib_slot_button_default"
|
|
|
|
local button_item = table_subgroup.add{type="sprite-button", sprite=item_proto.sprite, style=button_style,
|
|
tags={mod="fp", on_gui_click="select_picker_item", item_id=item_proto.id,
|
|
category_id=item_proto.category_id}, enabled=(existing_product == nil),
|
|
tooltip=item_proto.localised_name, mouse_button_filter={"left"}}
|
|
|
|
-- Figure out the translated name here so search doesn't have to repeat the work for every character
|
|
local translated_name = (translations) and translations[item_proto.type][item_name] or nil
|
|
translated_name = (translated_name) and translated_name:lower() or item_name
|
|
subgroup_table[{name=item_name, translated_name=translated_name}] = button_item
|
|
end
|
|
end
|
|
|
|
-- Catch up on addding the last item flow and groups row counts
|
|
current_item_rows = current_item_rows + math.ceil(current_items_in_table_count / items_per_row)
|
|
max_item_rows = math.max(current_item_rows, max_item_rows)
|
|
frame_filters.style.natural_height = max_item_rows * 40 + (2*12)
|
|
|
|
-- Select the previously selected item group if possible
|
|
local group_to_select, previous_selection = 1, ui_state.last_selected_picker_group
|
|
if previous_selection ~= nil and modal_elements.groups[previous_selection] ~= nil then
|
|
group_to_select = previous_selection
|
|
end
|
|
select_item_group(ui_state.modal_data, group_to_select)
|
|
end
|
|
|
|
|
|
-- ** PICKER DIALOG **
|
|
local function set_appropriate_focus(modal_data)
|
|
if modal_data.amount_defined_by == "amount" then
|
|
util.gui.select_all(modal_data.modal_elements["item_amount_textfield"])
|
|
else -- "belts"/"lanes"
|
|
util.gui.select_all(modal_data.modal_elements["belt_amount_textfield"])
|
|
end
|
|
end
|
|
|
|
-- Is only called when defined_by ~= "amount"
|
|
local function sync_amounts(modal_data)
|
|
local modal_elements = modal_data.modal_elements
|
|
|
|
local belt_amount = tonumber(modal_elements.belt_amount_textfield.text)
|
|
if belt_amount == nil then
|
|
modal_elements.item_amount_textfield.text = ""
|
|
else
|
|
local belt_proto = modal_data.belt_proto
|
|
local throughput = belt_proto.throughput * ((modal_data.lob == "belts") and 1 or 0.5)
|
|
local item_amount = belt_amount * throughput * modal_data.timescale
|
|
modal_elements.item_amount_textfield.text = util.format.number(item_amount, 6)
|
|
end
|
|
end
|
|
|
|
local function set_belt_proto(modal_data, belt_proto)
|
|
modal_data.belt_proto = belt_proto
|
|
|
|
local modal_elements = modal_data.modal_elements
|
|
modal_elements.item_amount_textfield.enabled = (belt_proto == nil)
|
|
modal_elements.belt_amount_textfield.enabled = (belt_proto ~= nil)
|
|
|
|
if belt_proto == nil then
|
|
modal_elements.belt_choice_button.elem_value = nil
|
|
modal_elements.belt_amount_textfield.text = ""
|
|
modal_data.amount_defined_by = "amount"
|
|
else
|
|
-- Might double set the choice button, but it doesn't matter
|
|
modal_elements.belt_choice_button.elem_value = belt_proto.name
|
|
modal_data.amount_defined_by = modal_data.lob
|
|
|
|
local item_amount = tonumber(modal_elements.item_amount_textfield.text)
|
|
if item_amount ~= nil then
|
|
local throughput = belt_proto.throughput * ((modal_data.lob == "belts") and 1 or 0.5)
|
|
local belt_amount = item_amount / throughput / modal_data.timescale
|
|
modal_elements.belt_amount_textfield.text = util.format.number(belt_amount, 6)
|
|
end
|
|
sync_amounts(modal_data)
|
|
end
|
|
end
|
|
|
|
local function set_item_proto(modal_data, item_proto)
|
|
local modal_elements = modal_data.modal_elements
|
|
modal_data.item_proto = item_proto
|
|
|
|
local item_choice_button = modal_elements.item_choice_button
|
|
item_choice_button.sprite = (item_proto) and item_proto.sprite or nil
|
|
item_choice_button.tooltip = (item_proto) and item_proto.tooltip or ""
|
|
|
|
-- Disable definition by belt for fluids
|
|
local is_fluid = item_proto and item_proto.type == "fluid"
|
|
modal_elements.belt_choice_button.enabled = (not is_fluid)
|
|
|
|
-- Clear the belt-related fields if needed
|
|
if is_fluid then set_belt_proto(modal_data, nil) end
|
|
end
|
|
|
|
local function update_dialog_submit_button(modal_elements)
|
|
local item_choice_button = modal_elements.item_choice_button
|
|
local item_amount_textfield = modal_elements.item_amount_textfield
|
|
|
|
local message = nil
|
|
if item_choice_button.sprite == "" then
|
|
message = {"fp.picker_issue_select_item"}
|
|
-- The item amount will be filled even if the item is defined_by ~= "amount"
|
|
elseif tonumber(item_amount_textfield.text) == nil then
|
|
message = {"fp.picker_issue_enter_amount"}
|
|
end
|
|
|
|
modal_dialog.set_submit_button_state(modal_elements, (message == nil), message)
|
|
end
|
|
|
|
|
|
local function add_item_pane(parent_flow, modal_data, item_category, item)
|
|
local function create_flow()
|
|
local flow = parent_flow.add{type="flow", direction="horizontal"}
|
|
flow.style.vertical_align = "center"
|
|
flow.style.horizontal_spacing = 8
|
|
flow.style.bottom_margin = 6
|
|
return flow
|
|
end
|
|
|
|
local modal_elements = modal_data.modal_elements
|
|
local defined_by = (item) and item.required_amount.defined_by or "amount"
|
|
modal_data.amount_defined_by = defined_by
|
|
|
|
|
|
local flow_amount = create_flow()
|
|
flow_amount.add{type="label", caption={"fp.pu_" .. item_category, 1}}
|
|
|
|
local item_choice_button = flow_amount.add{type="sprite-button", style="fp_sprite-button_inset_tiny"}
|
|
item_choice_button.style.right_margin = 12
|
|
modal_elements["item_choice_button"] = item_choice_button
|
|
|
|
flow_amount.add{type="label", caption={"fp.amount"}}
|
|
|
|
local item_amount = (item and defined_by == "amount") and tostring(item.required_amount.amount) or ""
|
|
local textfield_amount = flow_amount.add{type="textfield", text=item_amount,
|
|
tags={mod="fp", on_gui_text_changed="picker_item_amount"}}
|
|
util.gui.setup_numeric_textfield(textfield_amount, true, false)
|
|
textfield_amount.style.width = 90
|
|
modal_elements["item_amount_textfield"] = textfield_amount
|
|
|
|
|
|
local flow_belts = create_flow()
|
|
flow_belts.add{type="label", caption={"fp.amount_by", {"fp.pl_" .. modal_data.lob:sub(1, -2), 2}}}
|
|
|
|
local belt_amount = (item and defined_by ~= "amount") and tostring(item.required_amount.amount) or ""
|
|
local textfield_belts = flow_belts.add{type="textfield", text=belt_amount,
|
|
tags={mod="fp", on_gui_text_changed="picker_belt_amount"}}
|
|
util.gui.setup_numeric_textfield(textfield_belts, true, false)
|
|
textfield_belts.style.width = 85
|
|
textfield_belts.style.left_margin = 4
|
|
modal_elements["belt_amount_textfield"] = textfield_belts
|
|
|
|
flow_belts.add{type="label", caption="x"}
|
|
|
|
local belt_filter = {{filter="type", type="transport-belt"}, {filter="flag", flag="hidden",
|
|
invert=true, mode="and"}}
|
|
local choose_belt_button = flow_belts.add{type="choose-elem-button", elem_type="entity",
|
|
tags={mod="fp", on_gui_elem_changed="picker_choose_belt"}, elem_filters=belt_filter,
|
|
style="fp_sprite-button_inset_tiny"}
|
|
modal_elements["belt_choice_button"] = choose_belt_button
|
|
|
|
|
|
local item_proto = (item) and item.proto or nil
|
|
set_item_proto(modal_data, item_proto)
|
|
|
|
local belt_proto = (defined_by ~= "amount") and item.required_amount.belt_proto or nil
|
|
set_belt_proto(modal_data, belt_proto)
|
|
|
|
if (item) then set_appropriate_focus(modal_data)
|
|
else modal_elements.search_textfield.focus() end
|
|
update_dialog_submit_button(modal_elements)
|
|
end
|
|
|
|
|
|
local function handle_item_pick(player, tags, _)
|
|
local modal_data = util.globals.modal_data(player)
|
|
|
|
local item_proto = global.prototypes.items[tags.category_id].members[tags.item_id]
|
|
set_item_proto(modal_data, item_proto) -- no need for sync in this case
|
|
|
|
set_appropriate_focus(modal_data)
|
|
update_dialog_submit_button(modal_data.modal_elements)
|
|
end
|
|
|
|
local function handle_belt_pick(player, _, event)
|
|
local belt_name = event.element.elem_value
|
|
local belt_proto = prototyper.util.find_prototype("belts", belt_name, nil)
|
|
|
|
local modal_data = util.globals.modal_data(player)
|
|
set_belt_proto(modal_data, belt_proto) -- syncs amounts itself
|
|
|
|
set_appropriate_focus(modal_data)
|
|
update_dialog_submit_button(modal_data.modal_elements)
|
|
end
|
|
|
|
|
|
local function open_picker_dialog(player, modal_data)
|
|
-- Create a blank subfactory if requested
|
|
local settings = util.globals.settings(player)
|
|
modal_data.timescale = settings.default_timescale
|
|
modal_data.lob = settings.belts_or_lanes
|
|
|
|
local subfactory = util.globals.context(player).subfactory
|
|
if subfactory then
|
|
local class_name = modal_data.item_category:gsub("^%l", string.upper)
|
|
modal_data.item = Subfactory.get(subfactory, class_name, modal_data.item_id)
|
|
end
|
|
|
|
local dialog_flow = modal_data.modal_elements.dialog_flow
|
|
dialog_flow.style.vertical_spacing = 12
|
|
|
|
local item_content_frame = dialog_flow.add{type="frame", direction="vertical", style="inside_shallow_frame"}
|
|
item_content_frame.style.minimal_width = 325
|
|
item_content_frame.style.padding = {12, 12, 6, 12}
|
|
add_item_pane(item_content_frame, modal_data, modal_data.item_category, modal_data.item)
|
|
|
|
-- The item picker only needs to show when adding a new item
|
|
if modal_data.item == nil then
|
|
local picker_content_frame = dialog_flow.add{type="frame", direction="vertical", style="inside_deep_frame"}
|
|
add_item_picker(picker_content_frame, player)
|
|
end
|
|
end
|
|
|
|
local function close_picker_dialog(player, action)
|
|
local player_table = util.globals.player_table(player)
|
|
local ui_state = player_table.ui_state
|
|
local modal_data = ui_state.modal_data
|
|
local subfactory = ui_state.context.subfactory
|
|
|
|
if action == "submit" then
|
|
local defined_by = modal_data.amount_defined_by
|
|
local relevant_textfield_name = ((defined_by == "amount") and "item" or "belt") .. "_amount_textfield"
|
|
local relevant_amount = tonumber(modal_data.modal_elements[relevant_textfield_name].text)
|
|
|
|
local req_amount = {defined_by=defined_by, amount=relevant_amount, belt_proto=modal_data.belt_proto}
|
|
|
|
local refresh_scope = "subfactory"
|
|
if modal_data.item ~= nil then -- ie. this is an edit
|
|
modal_data.item.required_amount = req_amount
|
|
else
|
|
local class_name = modal_data.item_category:gsub("^%l", string.upper)
|
|
local item_proto = modal_data.item_proto
|
|
local top_level_item = Item.init(item_proto, class_name, 0, req_amount)
|
|
|
|
if modal_data.create_subfactory then -- if this flag is set, create a subfactory to put the item into
|
|
local translations = player_table.translation_tables
|
|
local translated_name = (translations) and translations[item_proto.type][item_proto.name] or ""
|
|
local icon = (not player_table.preferences.attach_subfactory_products)
|
|
and "[img=" .. top_level_item.proto.sprite .. "] " or ""
|
|
subfactory = subfactory_list.add_subfactory(player, (icon .. translated_name))
|
|
end
|
|
|
|
Subfactory.add(subfactory, top_level_item)
|
|
refresh_scope = "all" -- need to refresh subfactory list too
|
|
end
|
|
|
|
solver.update(player, subfactory)
|
|
util.raise.refresh(player, refresh_scope, nil)
|
|
|
|
elseif action == "delete" then
|
|
Subfactory.remove(subfactory, modal_data.item)
|
|
solver.update(player, subfactory)
|
|
util.raise.refresh(player, "subfactory", nil)
|
|
end
|
|
|
|
-- Remember selected group so it can be re-applied when the dialog is re-opened
|
|
ui_state.last_selected_picker_group = modal_data.selected_group_id
|
|
end
|
|
|
|
|
|
-- ** EVENTS **
|
|
local listeners = {}
|
|
|
|
listeners.gui = {
|
|
on_gui_click = {
|
|
{
|
|
name = "select_picker_item_group",
|
|
handler = (function(player, tags, _)
|
|
local modal_data = util.globals.modal_data(player)
|
|
select_item_group(modal_data, tags.group_id)
|
|
end)
|
|
},
|
|
{
|
|
name = "select_picker_item",
|
|
handler = handle_item_pick
|
|
}
|
|
},
|
|
on_gui_elem_changed = {
|
|
{
|
|
name = "picker_choose_belt",
|
|
handler = handle_belt_pick
|
|
}
|
|
},
|
|
on_gui_text_changed = {
|
|
{
|
|
name = "picker_item_amount",
|
|
handler = (function(player, _, _)
|
|
local modal_data = util.globals.modal_data(player)
|
|
update_dialog_submit_button(modal_data.modal_elements)
|
|
end)
|
|
},
|
|
{
|
|
name = "picker_belt_amount",
|
|
handler = (function(player, _, _)
|
|
local modal_data = util.globals.modal_data(player)
|
|
sync_amounts(modal_data) -- defined_by ~= "amount"
|
|
update_dialog_submit_button(modal_data.modal_elements)
|
|
end)
|
|
}
|
|
}
|
|
}
|
|
|
|
listeners.dialog = {
|
|
dialog = "picker",
|
|
metadata = (function(modal_data)
|
|
local action = (modal_data.item_id) and {"fp.edit"} or {"fp.add"}
|
|
return {
|
|
caption = {"", action, " ", {"fp.pl_" .. modal_data.item_category, 1}},
|
|
search_handler_name = (not modal_data.item_id) and "search_picker_items" or nil,
|
|
show_submit_button = true,
|
|
show_delete_button = (modal_data.item_id ~= nil)
|
|
}
|
|
end),
|
|
open = open_picker_dialog,
|
|
close = close_picker_dialog
|
|
}
|
|
|
|
listeners.global = {
|
|
search_picker_items = search_picker_items
|
|
}
|
|
|
|
return { listeners }
|