1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-01-08 00:39:30 +02:00
ComfyFactorio/modules/fjei/gui.lua
2021-03-24 20:14:55 +01:00

774 lines
27 KiB
Lua

--luacheck: ignore
local Functions = require 'modules.fjei.functions'
local math_ceil = math.ceil
local math_abs = math.abs
local table_remove = table.remove
local table_insert = table.insert
local main_window_width = 228
local recipe_window_width = 480
local recipe_window_amount_width = 38
local recipe_window_item_name_width = 128
local recipe_window_position = 'screen'
local column_count = 5
local line_count = 5
local items_per_page = column_count * line_count
local Public = {}
local function save_window_location(player, frame)
if not global.fjei.player_data then
return
end
if not global.fjei.player_data[player.index] then
return
end
global.fjei.player_data[player.index].gui_position = {frame.location.x, frame.location.y}
end
local function get_total_page_count(player)
if not global.fjei.player_data[player.index].filtered_list then
Functions.set_filtered_list(player)
end
local count = math_ceil(global.fjei.player_data[player.index].size_of_filtered_list / items_per_page)
if count < 1 then
count = 1
end
return count
end
local function set_page_count_caption(player)
if not global.fjei.player_data[player.index].filtered_list then
Functions.set_filtered_list(player)
end
local active_page = global.fjei.player_data[player.index].active_page
local element = player.gui.left.fjei_main_window.fjei_main_window_control_table.fjei_main_window_page_counter
local caption = active_page .. '/' .. get_total_page_count(player)
element.caption = caption
--set textfield width
local style = player.gui.left.fjei_main_window.fjei_main_window_control_table.fjei_main_window_search_textfield.style
local width = 112 - string.len(caption) * 6
style.minimal_width = width
style.maximal_width = width
end
local function get_formatted_amount(amount)
if amount < 1000 then
amount = amount .. ' x '
return amount
end
if amount >= 1000000 then
amount = math.floor(amount * 0.00001) * 0.1 .. 'm '
return amount
end
if amount >= 1000 then
amount = math.floor(amount * 0.01) * 0.1 .. 'k '
return amount
end
end
local function get_localised_name(name)
local item = game.item_prototypes[name]
if item then
return item.localised_name
end
local fluid = game.fluid_prototypes[name]
if fluid then
return fluid.localised_name
end
local recipe = game.recipe_prototypes[name]
if recipe then
return recipe.localised_name
end
return name
end
local function get_sprite_type(name)
if game.item_prototypes[name] then
return 'item'
end
if game.fluid_prototypes[name] then
return 'fluid'
end
if game.entity_prototypes[name] then
return 'entity'
end
if game.recipe_prototypes[name] then
return 'recipe'
end
return false
end
local function get_active_recipe_name(player)
local fjei_recipe_window = player.gui[recipe_window_position]['fjei_recipe_window']
if not fjei_recipe_window then
return
end
if not fjei_recipe_window.recipe_container then
return
end
local container = fjei_recipe_window.recipe_container.children[1]
if not container then
return
end
return container.children[1].name
end
local function add_choose_elem_button(element, name, is_recipe)
local elem_type
if is_recipe then
elem_type = 'recipe'
else
elem_type = get_sprite_type(name)
end
if not elem_type then
return
end
local elem_type = get_sprite_type(name)
local choose_elem_button = element.add({type = 'choose-elem-button', name = name, elem_type = elem_type})
choose_elem_button.locked = true
choose_elem_button.elem_value = name
choose_elem_button.style.minimal_width = 36
choose_elem_button.style.minimal_height = 36
choose_elem_button.style.maximal_width = 36
choose_elem_button.style.maximal_height = 36
choose_elem_button.style.margin = -1
choose_elem_button.style.right_margin = 2
choose_elem_button.style.padding = 2
end
local function add_sprite_icon(element, name, is_recipe, use_localised_name)
local sprite_type
if is_recipe then
sprite_type = 'recipe'
else
sprite_type = get_sprite_type(name)
end
if not sprite_type then
return
end
local sprite
if use_localised_name then
sprite = element.add({type = 'sprite', name = name, sprite = sprite_type .. '/' .. name, tooltip = get_localised_name(name)})
else
sprite = element.add({type = 'sprite', name = name, sprite = sprite_type .. '/' .. name, tooltip = name})
end
sprite.style.minimal_width = 32
sprite.style.minimal_height = 32
sprite.style.maximal_width = 32
sprite.style.maximal_height = 32
sprite.style.margin = 4
sprite.style.padding = 0
end
local function display_item_list(player)
if not player.gui.left.fjei_main_window then
return
end
if not global.fjei.player_data[player.index].filtered_list then
Functions.set_filtered_list(player)
end
local active_page = global.fjei.player_data[player.index].active_page
local starting_index = 1 + (active_page - 1) * items_per_page
local sorted_item_list = global.fjei.sorted_item_list
local filtered_list = global.fjei.player_data[player.index].filtered_list
local item_list_table = player.gui.left.fjei_main_window.fjei_main_window_item_list_table
item_list_table.clear()
for i = starting_index, starting_index + items_per_page - 1, 1 do
if not filtered_list[i] then
return
end
add_sprite_icon(item_list_table, sorted_item_list[filtered_list[i]], false)
end
end
local function display_history(player)
if not player.gui.left.fjei_main_window then
return
end
local player_data = global.fjei.player_data[player.index]
local history = player_data.history
if not history then
return
end
local history_table = player.gui.left.fjei_main_window.fjei_main_window_history_table
history_table.clear()
for i = player_data.size_of_history - column_count, player_data.size_of_history, 1 do
local name = history[i]
if name then
add_sprite_icon(history_table, name, false)
end
end
end
function Public.refresh_main_window(player)
if not player.gui.left.fjei_main_window then
return
end
set_page_count_caption(player)
display_item_list(player)
display_history(player)
end
local function draw_main_window(player)
if player.gui.left['fjei_main_window'] then
player.gui.left['fjei_main_window'].destroy()
end
local frame = player.gui.left.add({type = 'frame', name = 'fjei_main_window', direction = 'vertical'})
frame.style.minimal_width = main_window_width
frame.style.maximal_width = main_window_width
frame.style.padding = 4
frame.style.margin = 2
local t = frame.add({type = 'table', name = 'fjei_main_window_control_table', column_count = 4})
local element = t.add({type = 'label', name = 'fjei_main_window_page_counter', caption = ' '})
element.style.font = 'heading-2'
element.style.font_color = {222, 222, 222}
element.style.right_margin = 4
local element = t.add({type = 'sprite-button', name = 'fjei_main_window_previous_page', caption = ''})
element.style.font = 'heading-1'
element.style.maximal_height = 32
element.style.maximal_width = 32
local element = t.add({type = 'sprite-button', name = 'fjei_main_window_next_page', caption = ''})
element.style.font = 'heading-1'
element.style.maximal_height = 32
element.style.maximal_width = 32
local text = global.fjei.player_data[player.index].active_filter
if not text then
text = ''
end
local textfield = t.add({type = 'textfield', name = 'fjei_main_window_search_textfield', text = text})
textfield.style.left_margin = 4
frame.add({type = 'line'})
local t = frame.add({type = 'table', name = 'fjei_main_window_item_list_table', column_count = column_count})
frame.add({type = 'line'})
local t = frame.add({type = 'table', name = 'fjei_main_window_history_table', column_count = column_count})
Public.refresh_main_window(player)
end
local function refresh_recipe_bar(player, selected_recipe)
local old_recipe_name = get_active_recipe_name(player)
if not old_recipe_name then
return
end
if not player.gui[recipe_window_position]['fjei_recipe_window'] then
return
end
if not player.gui[recipe_window_position]['fjei_recipe_window'].header_container then
return
end
local fjei_recipe_window_select_table = player.gui[recipe_window_position]['fjei_recipe_window'].header_container.header_table.scroll_pane.fjei_recipe_window_select_table
local container = fjei_recipe_window_select_table[old_recipe_name]
container.clear()
add_sprite_icon(container, old_recipe_name, true)
local container = fjei_recipe_window_select_table[selected_recipe]
container.clear()
local element = container.add({type = 'frame', name = 'fjei_recipe_window_selected_recipe'})
element.style.margin = 0
element.style.padding = -4
add_sprite_icon(element, selected_recipe, true)
end
local function draw_recipe_window_header(player, container, item_name, recipes, mode, recipe_name)
if mode == 1 then
container.parent.caption = 'Product of recipe: '
else
container.parent.caption = 'Ingredient in recipe: '
end
local t = container.add({type = 'table', name = 'header_table', column_count = 3})
local element = t.add({type = 'label', name = item_name, caption = ' '})
element.visible = false
local scroll_pane = t.add({type = 'scroll-pane', name = 'scroll_pane', horizontal_scroll_policy = 'always', vertical_scroll_policy = 'never'})
scroll_pane.style.minimal_width = recipe_window_width - 66
scroll_pane.style.maximal_width = recipe_window_width - 66
scroll_pane.style.minimal_height = 60
scroll_pane.style.maximal_height = 60
local tt = scroll_pane.add({type = 'table', name = 'fjei_recipe_window_select_table', column_count = 8192})
for key, name in pairs(recipes) do
if not tt[name] then
local ttt = tt.add({type = 'table', name = name, column_count = 1})
if recipe_name == name then
local element = ttt.add({type = 'frame', name = 'fjei_recipe_window_selected_recipe'})
element.style.margin = 0
element.style.padding = -4
add_sprite_icon(element, name, true)
else
add_sprite_icon(ttt, name, true)
end
end
end
local element = t.add {type = 'sprite-button', sprite = 'utility/close_fat', name = 'fjei_close_recipe_window'}
element.style.left_margin = 5
local element = container.add({type = 'line'})
element.style.right_margin = 6
end
local function draw_recipe(player, container, recipe_name)
local recipe = game.recipe_prototypes[recipe_name]
local t = container.add({type = 'table', column_count = 2})
add_choose_elem_button(t, recipe.name, true)
local tt = t.add({type = 'table', column_count = 1})
local ttt = tt.add({type = 'table', column_count = 2})
local element_1 = ttt.add({type = 'label', caption = recipe.localised_name})
element_1.style.font = 'heading-1'
element_1.style.single_line = false
local element_2 = ttt.add({type = 'label', caption = ' '})
element_2.style.font = 'heading-1'
if not player.force.recipes[recipe_name].enabled then
element_2.style.font_color = {200, 0, 0}
element_2.caption = '*'
local str = 'Further research is required to unlock this recipe.'
element_2.tooltip = str
element_1.tooltip = str
end
local element = tt.add({type = 'label', caption = '' .. math.round(recipe.energy, 2) .. ' seconds crafting time'})
element.style.font = 'default'
element.style.font_color = {215, 215, 215}
element.style.top_margin = -4
container.add({type = 'line'})
local element = container.add({type = 'label', caption = 'Products:'})
element.style.font = 'heading-2'
local t = container.add({type = 'table', column_count = 2})
for _, product in pairs(recipe.products) do
local tt = t.add({type = 'table', column_count = 3})
local amount = product.amount
if not amount then
amount = 1
end
amount = amount * product.probability
local element = tt.add({type = 'label', caption = get_formatted_amount(amount)})
element.style.minimal_width = recipe_window_amount_width
element.style.maximal_width = recipe_window_amount_width
element.style.single_line = false
element.style.horizontal_align = 'right'
add_choose_elem_button(tt, product.name)
if product.temperature then
local ttt = tt.add({type = 'table', column_count = 1})
local element = ttt.add({type = 'label', caption = get_localised_name(product.name)})
element.style.minimal_width = recipe_window_item_name_width
element.style.maximal_width = recipe_window_item_name_width
element.style.single_line = false
element.style.font = 'default'
element.style.bottom_margin = -3
local element = ttt.add({type = 'label', caption = product.temperature .. ' °C'})
element.style.top_margin = -3
else
local element = tt.add({type = 'label', caption = get_localised_name(product.name)})
element.style.minimal_width = recipe_window_item_name_width
element.style.maximal_width = recipe_window_item_name_width
element.style.single_line = false
element.style.font = 'default'
end
end
local element = container.add({type = 'label', caption = 'Ingredients:'})
element.style.font = 'heading-2'
local t = container.add({type = 'table', column_count = 2})
for key, ingredient in pairs(recipe.ingredients) do
local tt = t.add({type = 'table', column_count = 3})
local element = tt.add({type = 'label', caption = get_formatted_amount(ingredient.amount)})
element.style.minimal_width = recipe_window_amount_width
element.style.maximal_width = recipe_window_amount_width
element.style.single_line = false
element.style.horizontal_align = 'right'
add_choose_elem_button(tt, ingredient.name)
if ingredient.minimum_temperature and math_abs(ingredient.minimum_temperature) < 4294967296 then
local ttt = tt.add({type = 'table', column_count = 1})
local element = ttt.add({type = 'label', caption = get_localised_name(ingredient.name)})
element.style.minimal_width = recipe_window_item_name_width
element.style.maximal_width = recipe_window_item_name_width
element.style.single_line = false
element.style.font = 'default'
element.style.bottom_margin = -3
local element = ttt.add({type = 'label', caption = ingredient.minimum_temperature .. ' °C'})
element.style.top_margin = -3
else
local element = tt.add({type = 'label', caption = get_localised_name(ingredient.name)})
element.style.minimal_width = recipe_window_item_name_width
element.style.maximal_width = recipe_window_item_name_width
element.style.single_line = false
element.style.font = 'default'
end
end
local machines = Functions.get_crafting_machines_for_recipe(player.force.name, recipe)
local element = container.add({type = 'line'})
element.style.top_margin = 2
element.style.bottom_margin = 2
if #machines == 0 then
local t = container.add({type = 'table', column_count = 10})
local element = t.add({type = 'label', caption = 'Made by:'})
element.style.font = 'heading-2'
local element = t.add({type = 'label', caption = 'Crafting method unknown.'})
element.style.font = 'heading-2'
element.style.font_color = {150, 150, 150}
return
end
local t = container.add({type = 'table', column_count = 10})
local element = t.add({type = 'label', caption = 'Made by:'})
element.style.right_margin = 2
element.style.font = 'heading-2'
for key, machine in pairs(machines) do
local prototype = game.entity_prototypes[machine]
if prototype then
add_choose_elem_button(t, machine)
end
end
end
local function create_recipe_window(item_name, player, button, selected_recipe)
local mode
if button == defines.mouse_button_type.left then
mode = 1
else
mode = 2
end
if selected_recipe and player.gui[recipe_window_position]['fjei_recipe_window'] then
refresh_recipe_bar(player, selected_recipe)
local container = player.gui[recipe_window_position]['fjei_recipe_window'].recipe_container
container.clear()
draw_recipe(player, container, selected_recipe)
return
end
if not global.fjei.item_list[item_name] then
return
end
--shift researched recipes forward in the list
local recipes = {}
local size_of_recipes = 0
local researched_recipes = {}
local size_of_researched_recipes = 0
local unknown_recipes = {}
local size_of_unknown_recipes = 0
local force_recipes = player.force.recipes
for k, recipe_name in pairs(global.fjei.item_list[item_name][mode]) do
if k > 512 then
break
end
if force_recipes[recipe_name] then
if force_recipes[recipe_name].enabled then
size_of_researched_recipes = size_of_researched_recipes + 1
researched_recipes[size_of_researched_recipes] = recipe_name
else
size_of_unknown_recipes = size_of_unknown_recipes + 1
unknown_recipes[size_of_unknown_recipes] = recipe_name
end
end
end
for k, recipe_name in pairs(researched_recipes) do
size_of_recipes = size_of_recipes + 1
recipes[size_of_recipes] = recipe_name
end
for k, recipe_name in pairs(unknown_recipes) do
size_of_recipes = size_of_recipes + 1
recipes[size_of_recipes] = recipe_name
end
if size_of_recipes == 0 then
return
end
if not selected_recipe then
for key, recipe_name in pairs(recipes) do
if #Functions.get_crafting_machines_for_recipe(player.force.name, game.recipe_prototypes[recipe_name]) > 0 then
selected_recipe = recipe_name
break
end
if key > 16 then
break
end
end
end
local recipe_name = recipes[1]
if selected_recipe then
for k, v in pairs(recipes) do
if v == selected_recipe then
recipe_name = recipes[k]
end
end
end
if player.gui[recipe_window_position]['fjei_recipe_window'] then
save_window_location(player, player.gui[recipe_window_position]['fjei_recipe_window'])
player.gui[recipe_window_position]['fjei_recipe_window'].destroy()
end
local frame = player.gui[recipe_window_position].add({type = 'frame', name = 'fjei_recipe_window', direction = 'vertical', caption = ' '})
frame.style.minimal_width = recipe_window_width
frame.style.maximal_width = recipe_window_width
frame.style.padding = 4
frame.style.left_padding = 6
frame.style.right_padding = 6
local player_data = global.fjei.player_data
local position
if player_data[player.index] and player_data[player.index].gui_position then
position = {x = player_data[player.index].gui_position[1], y = player_data[player.index].gui_position[2]}
else
position = {x = 2, y = 370}
end
frame.location = position
local container = frame.add({type = 'table', name = 'header_container', column_count = 1})
draw_recipe_window_header(player, container, item_name, recipes, mode, recipe_name)
local container = frame.add({type = 'table', name = 'recipe_container', column_count = 1})
draw_recipe(player, container, recipe_name)
return true
end
local function add_to_history(item_name, player)
if not game.item_prototypes[item_name] and not game.fluid_prototypes[item_name] then
return
end
local player_data = global.fjei.player_data[player.index]
if not player_data.history then
player_data.history = {item_name}
player_data.size_of_history = 1
return
end
--shift existing item forward
for k, v in pairs(player_data.history) do
if v == item_name then
table_remove(player_data.history, k)
table_insert(player_data.history, item_name)
return
end
end
--remove first item, add new item
if player_data.size_of_history >= column_count then
table_remove(player_data.history, 1)
player_data.history[column_count] = item_name
return
end
player_data.size_of_history = player_data.size_of_history + 1
player_data.history[player_data.size_of_history] = item_name
end
local function show_cursor_stack_item(element, player, button)
local cursor_stack = player.cursor_stack
if not cursor_stack then
return
end
if not cursor_stack.valid_for_read then
return
end
if not global.fjei then
Functions.build_tables()
end
if not global.fjei.player_data[player.index] then
global.fjei.player_data[player.index] = {}
end
if not global.fjei.item_list[cursor_stack.name] then
return
end
add_to_history(cursor_stack.name, player)
draw_main_window(player)
create_recipe_window(cursor_stack.name, player, button)
return true
end
local function toggle_main_window(element, player, button)
if show_cursor_stack_item(element, player, button) then
return true
end
if player.gui.left.fjei_main_window then
player.gui.left.fjei_main_window.destroy()
if player.gui[recipe_window_position].fjei_recipe_window then
player.gui[recipe_window_position].fjei_recipe_window.destroy()
end
else
draw_main_window(player)
end
return true
end
local function main_window_next_page(element, player, button)
local player_data = global.fjei.player_data[player.index]
if button == defines.mouse_button_type.right then
for _ = 1, 5, 1 do
if player_data.active_page == get_total_page_count(player) then
player_data.active_page = 1
else
player_data.active_page = player_data.active_page + 1
end
end
else
if player_data.active_page == get_total_page_count(player) then
player_data.active_page = 1
else
player_data.active_page = player_data.active_page + 1
end
end
Public.refresh_main_window(player)
return true
end
local function main_window_previous_page(element, player, button)
local player_data = global.fjei.player_data[player.index]
if button == defines.mouse_button_type.right then
for _ = 1, 5, 1 do
if player_data.active_page == 1 then
player_data.active_page = get_total_page_count(player)
else
player_data.active_page = player_data.active_page - 1
end
end
else
if player_data.active_page == 1 then
player_data.active_page = get_total_page_count(player)
else
player_data.active_page = player_data.active_page - 1
end
end
Public.refresh_main_window(player)
return true
end
local function clear_search_textfield(element, player, button)
if show_cursor_stack_item(element, player, button) then
return true
end
if button ~= defines.mouse_button_type.right then
return
end
global.fjei.player_data[player.index].active_filter = false
element.text = ''
Functions.set_filtered_list(player)
Public.refresh_main_window(player)
return true
end
local function close_recipe_window(element, player, button)
local recipe_window = player.gui[recipe_window_position]['fjei_recipe_window']
save_window_location(player, recipe_window)
if recipe_window then
recipe_window.destroy()
end
return true
end
local gui_actions = {
['fjei_toggle_button'] = toggle_main_window,
['fjei_main_window_next_page'] = main_window_next_page,
['fjei_main_window_previous_page'] = main_window_previous_page,
['fjei_main_window_search_textfield'] = clear_search_textfield,
['fjei_close_recipe_window'] = close_recipe_window
}
function Public.gui_click_actions(element, player, button)
if not gui_actions[element.name] then
return
end
if not global.fjei then
Functions.build_tables()
end
if not global.fjei.player_data[player.index] then
global.fjei.player_data[player.index] = {}
end
gui_actions[element.name](element, player, button)
return true
end
function Public.draw_top_toggle_button(player)
if player.gui.top['fjei_toggle_button'] then
return
end
local button = player.gui.top.add({type = 'sprite-button', name = 'fjei_toggle_button', caption = 'FJEI'})
button.style.font = 'heading-1'
button.style.font_color = {222, 222, 222}
button.style.minimal_height = 38
button.style.minimal_width = 50
button.style.padding = -2
end
function Public.open_recipe(element, player, button)
if not global.fjei then
Functions.build_tables()
end
if not global.fjei.player_data[player.index] then
global.fjei.player_data[player.index] = {}
end
local item_name = element.name
local selected_recipe = false
if element.parent.name == 'fjei_recipe_window_selected_recipe' then
return
end
if element.parent then
if element.parent.parent then
if element.parent.parent.name == 'fjei_recipe_window_select_table' then
selected_recipe = item_name
end
end
end
if element.parent.name == 'fjei_main_window_item_list_table' or element.parent.name == 'fjei_main_window_history_table' then
local recipe_window = player.gui[recipe_window_position]['fjei_recipe_window']
if recipe_window then
local active_item = recipe_window.header_container.header_table.children[1].name
if active_item == element.name and global.fjei.player_data[player.index].last_button == button then
save_window_location(player, recipe_window)
recipe_window.destroy()
return
end
end
end
add_to_history(item_name, player)
if not create_recipe_window(item_name, player, button, selected_recipe) then
return
end
display_history(player)
global.fjei.player_data[player.index].last_button = button
end
return Public