654 lines
20 KiB
Lua

local gui = require("__flib__.gui")
local table = require("__flib__.table")
local constants = require("constants")
local database = require("scripts.database")
local formatter = require("scripts.formatter")
local player_data = require("scripts.player-data")
local util = require("scripts.util")
local components = {
list_box = require("scripts.gui.info.list-box"),
table = require("scripts.gui.info.table"),
}
local function tool_button(sprite, tooltip, ref, action, style_mods)
return {
type = "sprite-button",
style = "tool_button",
style_mods = style_mods,
sprite = sprite,
tooltip = tooltip,
mouse_button_filter = { "left" },
ref = ref,
actions = {
on_click = action,
},
}
end
--- @class InfoGui
local Gui = {}
local actions = require("scripts.gui.info.actions")
function Gui:dispatch(msg, e)
-- Mark this GUI as the active one whenever we do anything
self.player_table.guis.info._active_id = self.id
if type(msg) == "string" then
actions[msg](self, msg, e)
else
actions[msg.action](self, msg, e)
end
end
function Gui:destroy()
self.refs.window.destroy()
self.player_table.guis.info[self.id] = nil
if self.state.docked and not self.state.search_info then
self.player_table.guis.info._relative_id = nil
end
end
function Gui:get_context()
local history = self.state.history
return history[history._index]
end
function Gui:update_contents(options)
options = options or {}
local new_context = options.new_context
local refresh = options.refresh
local state = self.state
local refs = self.refs
-- HISTORY
-- Add new history if needed
local history = state.history
if new_context then
-- Remove all entries after this
for i = history._index + 1, #history do
history[i] = nil
end
-- Insert new entry
local new_index = #history + 1
history[new_index] = new_context
history._index = new_index
-- Limit the length
local max_size = constants.session_history_size
if new_index > max_size then
history._index = max_size
for _ = max_size + 1, new_index do
table.remove(history, 1)
end
end
end
local context = new_context or history[history._index]
if not refresh then
player_data.update_global_history(self.player_table.global_history, context)
local SearchGui = util.get_gui(self.player.index, "search")
if SearchGui then
SearchGui:dispatch("update_history")
end
end
-- COMMON DATA
local obj_data = database[context.class][context.name]
local player_data = formatter.build_player_data(self.player, self.player_table)
local gui_translations = player_data.translations.gui
-- TECH LEVEL
if not refresh and obj_data.research_unit_count_formula then
state.selected_tech_level = player_data.force.technologies[context.name].level
end
-- TITLEBAR
-- Nav buttons
-- Generate tooltips
local history_index = history._index
local history_len = #history
local entries = {}
for i, history_context in ipairs(history) do
local obj_data = database[history_context.class][history_context.name]
local info = formatter(obj_data, player_data, { always_show = true, label_only = true })
local caption = info.caption
if not info.researched then
caption = formatter.rich_text("color", "unresearched", caption)
end
entries[history_len - (i - 1)] = formatter.rich_text(
"font",
"default-semibold",
formatter.rich_text("color", history_index == i and "green" or "invisible", ">")
) .. " " .. caption
end
local entries = table.concat(entries, "\n")
local base_tooltip = formatter.rich_text(
"font",
"default-bold",
formatter.rich_text("color", "heading", gui_translations.session_history)
) .. "\n" .. entries
-- Apply button properties
local nav_backward_button = refs.titlebar.nav_backward_button
if history._index == 1 then
nav_backward_button.enabled = false
nav_backward_button.sprite = "rb_nav_backward_disabled"
else
nav_backward_button.enabled = true
nav_backward_button.sprite = "rb_nav_backward_white"
end
nav_backward_button.tooltip = base_tooltip
.. formatter.control(gui_translations.click, gui_translations.go_backward)
.. formatter.control(gui_translations.shift_click, gui_translations.go_to_the_back)
local nav_forward_button = refs.titlebar.nav_forward_button
if history._index == #history then
nav_forward_button.enabled = false
nav_forward_button.sprite = "rb_nav_forward_disabled"
else
nav_forward_button.enabled = true
nav_forward_button.sprite = "rb_nav_forward_white"
end
nav_forward_button.tooltip = base_tooltip
.. formatter.control(gui_translations.click, gui_translations.go_forward)
.. formatter.control(gui_translations.shift_click, gui_translations.go_to_the_front)
-- Label
local label = refs.titlebar.label
label.caption = gui_translations[context.class]
-- Reset search when moving pages
if not options.refresh and state.search_opened then
state.search_opened = false
local search_button = refs.titlebar.search_button
local search_textfield = refs.titlebar.search_textfield
search_button.sprite = "utility/search_white"
search_button.style = "frame_action_button"
search_textfield.visible = false
if state.search_query ~= "" then
-- Reset query
search_textfield.text = ""
state.search_query = ""
end
end
-- HEADER
-- List navigation
-- List nav is kind of weird because it doesn't respect your settings, but making it respect the settings would be
-- too much work
local list_context = context.list
if list_context then
local source = list_context.context
local source_data = database[source.class][source.name]
local list = source_data[list_context.source]
local list_len = #list
local index = list_context.index
local list_refs = refs.header.list_nav
list_refs.flow.visible = true
-- Labels
local source_info = formatter(source_data, player_data, { always_show = true })
local source_label = list_refs.source_label
source_label.caption = formatter.rich_text("color", "heading", source_info.caption)
.. " - "
.. gui_translations[list_context.source]
local position_label = list_refs.position_label
position_label.caption = " (" .. index .. " / " .. list_len .. ")"
-- Buttons
for delta, button in pairs({ [-1] = list_refs.back_button, [1] = list_refs.forward_button }) do
local new_index = index + delta
if new_index < 1 then
new_index = list_len
elseif new_index > list_len then
new_index = 1
end
local ident = list[new_index]
gui.set_action(button, "on_click", {
gui = "info",
id = self.id,
action = "navigate_to_plain",
context = {
class = ident.class,
name = ident.name,
list = {
context = source,
index = new_index,
source = list_context.source,
},
},
})
end
refs.header.line.visible = true
else
refs.header.list_nav.flow.visible = false
refs.header.line.visible = false
end
-- Label
local title_info = formatter(obj_data, player_data, { always_show = true, is_label = true })
local label = refs.header.label
label.caption = title_info.caption
label.tooltip = title_info.tooltip
label.style = title_info.researched and "rb_toolbar_label" or "rb_unresearched_toolbar_label"
-- Buttons
if context.class == "technology" then
refs.header.open_in_tech_window_button.visible = true
else
refs.header.open_in_tech_window_button.visible = false
end
if context.class == "fluid" and obj_data.temperature_ident then
local base_fluid_button = refs.header.go_to_base_fluid_button
base_fluid_button.visible = true
gui.set_action(base_fluid_button, "on_click", {
gui = "info",
id = self.id,
action = "navigate_to_plain",
context = obj_data.base_fluid,
})
else
refs.header.go_to_base_fluid_button.visible = false
end
if context.class == "recipe" then
local button = refs.header.quick_ref_button
button.visible = true
local is_selected = self.player_table.guis.quick_ref[context.name]
button.style = is_selected and "flib_selected_tool_button" or "tool_button"
button.tooltip = { "gui.rb-" .. (is_selected and "close" or "open") .. "-quick-ref-window" }
else
refs.header.quick_ref_button.visible = false
end
local favorite_button = refs.header.favorite_button
if self.player_table.favorites[context.class .. "." .. context.name] then
favorite_button.style = "flib_selected_tool_button"
favorite_button.tooltip = { "gui.rb-remove-from-favorites" }
else
favorite_button.style = "tool_button"
favorite_button.tooltip = { "gui.rb-add-to-favorites" }
end
-- PAGE
local pane = refs.page_scroll_pane
local page_refs = refs.page_components
local page_settings = self.player_table.settings.pages[context.class]
local i = 0
local visible = false
local component_variables = {
context = context,
gui_id = self.id,
search_query = state.search_query,
selected_tech_level = state.selected_tech_level,
}
-- Add or update relevant components
for _, component_ident in pairs(constants.pages[context.class]) do
i = i + 1
local component = components[component_ident.type]
local component_refs = page_refs[i]
if not component_refs or component_refs.type ~= component_ident.type then
-- Destroy old elements
if component_refs then
component_refs.root.destroy()
end
-- Create new elements
component_refs = component.build(pane, i, component_ident, component_variables)
component_refs.type = component_ident.type
page_refs[i] = component_refs
end
local component_settings = page_settings[component_ident.label or component_ident.source]
if not refresh then
state.components[i] = component.default_state(component_settings)
end
component_variables.component_index = i
component_variables.component_state = state.components[i]
local comp_visible =
component.update(component_ident, component_refs, obj_data, player_data, component_settings, component_variables)
visible = visible or comp_visible
end
-- Destroy extraneous components
for j = i + 1, #page_refs do
page_refs[j].root.destroy()
page_refs[j] = nil
end
-- Show error frame if nothing is visible
if not visible and not state.warning_shown then
state.warning_shown = true
pane.visible = false
refs.page_frame.style = "rb_inside_warning_frame"
refs.page_frame.style.vertically_stretchable = state.docked and state.search_info
refs.warning_flow.visible = true
if state.search_query == "" then
refs.warning_text.caption = { "gui.rb-no-content-warning" }
else
refs.warning_text.caption = { "gui.rb-no-results" }
end
elseif visible and state.warning_shown then
state.warning_shown = false
pane.visible = true
refs.page_frame.style = "inside_shallow_frame"
refs.page_frame.style.vertically_stretchable = state.docked and state.search_info
refs.warning_flow.visible = false
end
end
local index = {}
--- @param player LuaPlayer
--- @param player_table PlayerTable
--- @param context Context
--- @param options table?
function index.build(player, player_table, context, options)
options = options or {}
local id = player_table.guis.info._next_id
player_table.guis.info._next_id = id + 1
local root_elem = options.parent or player.gui.screen
local search_info = root_elem.name == "rb_search_window" or root_elem.name == "rb_visual_search_window"
local relative = options.parent and not search_info
local refs = gui.build(root_elem, {
{
type = "frame",
style_mods = { minimal_width = 430, maximal_width = 600 },
direction = "vertical",
ref = { "window" },
anchor = options.anchor,
actions = {
on_click = { gui = "info", id = id, action = "set_as_active" },
on_closed = { gui = "info", id = id, action = "close" },
},
{
type = "flow",
style = "flib_titlebar_flow",
ref = { "titlebar", "flow" },
actions = {
on_click = not relative and {
gui = search_info and "search" or "info",
id = not search_info and id or nil,
action = "reset_location",
} or nil,
},
util.frame_action_button(
"rb_nav_backward",
nil,
{ "titlebar", "nav_backward_button" },
{ gui = "info", id = id, action = "navigate", delta = -1 }
),
util.frame_action_button(
"rb_nav_forward",
nil,
{ "titlebar", "nav_forward_button" },
{ gui = "info", id = id, action = "navigate", delta = 1 }
),
{
type = "label",
style = "frame_title",
style_mods = { left_margin = 4 },
ignored_by_interaction = true,
ref = { "titlebar", "label" },
},
{
type = "empty-widget",
style = relative and "flib_horizontal_pusher" or "flib_titlebar_drag_handle",
ignored_by_interaction = true,
},
{
type = "textfield",
style_mods = {
top_margin = -3,
right_padding = 3,
width = 120,
},
clear_and_focus_on_right_click = true,
visible = false,
ref = { "titlebar", "search_textfield" },
actions = {
on_text_changed = { gui = "info", id = id, action = "update_search_query" },
},
},
util.frame_action_button(
"utility/search",
{ "gui.rb-search-instruction" },
{ "titlebar", "search_button" },
{ gui = "info", id = id, action = "toggle_search" }
),
options.parent and util.frame_action_button(
"rb_detach",
{ "gui.rb-detach-instruction" },
nil,
{ gui = "info", id = id, action = "detach_window" }
) or {},
util.frame_action_button(
"utility/close",
{ "gui.close" },
{ "titlebar", "close_button" },
{ gui = "info", id = id, action = "close" }
),
},
{
type = "frame",
style = "inside_shallow_frame",
style_mods = { vertically_stretchable = search_info },
direction = "vertical",
ref = { "page_frame" },
action = {
on_click = { gui = "info", id = id, action = "set_as_active" },
},
{
type = "frame",
style = "rb_subheader_frame",
direction = "vertical",
{
type = "flow",
style_mods = { vertical_align = "center" },
visible = false,
ref = { "header", "list_nav", "flow" },
action = {
on_click = { gui = "info", id = id, action = "set_as_active" },
},
tool_button(
"rb_list_nav_backward_black",
{ "gui.rb-go-backward" },
{ "header", "list_nav", "back_button" },
nil,
{ padding = 3 }
),
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{
type = "label",
style = "bold_label",
style_mods = { horizontally_squashable = true },
ref = { "header", "list_nav", "source_label" },
},
{
type = "label",
style = "bold_label",
style_mods = { font_color = constants.colors.info.tbl },
ref = { "header", "list_nav", "position_label" },
},
{ type = "empty-widget", style = "flib_horizontal_pusher" },
tool_button(
"rb_list_nav_forward_black",
{ "gui.rb-go-forward" },
{ "header", "list_nav", "forward_button" },
nil,
{ padding = 3 }
),
},
{
type = "line",
style = "rb_dark_line",
direction = "horizontal",
visible = false,
ref = { "header", "line" },
},
{
type = "flow",
style_mods = { vertical_align = "center" },
{ type = "label", style = "rb_toolbar_label", ref = { "header", "label" } },
{ type = "empty-widget", style = "flib_horizontal_pusher" },
__DebugAdapter and tool_button(nil, "Print", nil, { gui = "info", id = id, action = "print_object" }) or {},
tool_button(
"rb_technology_gui_black",
{ "gui.rb-open-in-technology-window" },
{ "header", "open_in_tech_window_button" },
{ gui = "info", id = id, action = "open_in_tech_window" }
),
tool_button(
"rb_fluid_black",
{ "gui.rb-view-base-fluid" },
{ "header", "go_to_base_fluid_button" },
{ gui = "info", id = id, action = "go_to_base_fluid" }
),
tool_button(
"rb_clipboard_black",
{ "gui.rb-toggle-quick-ref-window" },
{ "header", "quick_ref_button" },
{ gui = "info", id = id, action = "toggle_quick_ref" }
),
tool_button(
"rb_favorite_black",
{ "gui.rb-add-to-favorites" },
{ "header", "favorite_button" },
{ gui = "info", id = id, action = "toggle_favorite" }
),
},
},
{
type = "scroll-pane",
style = "rb_page_scroll_pane",
style_mods = { maximal_height = 900 },
ref = { "page_scroll_pane" },
action = {
on_click = { gui = "info", id = id, action = "set_as_active" },
},
},
{
type = "flow",
style = "rb_warning_flow",
direction = "vertical",
visible = false,
ref = { "warning_flow" },
{
type = "label",
style = "bold_label",
caption = { "gui.rb-no-content-warning" },
ref = { "warning_text" },
},
},
},
},
})
if options.parent then
refs.root = root_elem
else
refs.root = refs.window
refs.root.force_auto_center()
end
if not options.parent or search_info then
refs.titlebar.flow.drag_target = refs.root
end
refs.page_components = {}
--- @class InfoGui
local self = {
id = id,
player = player,
player_table = player_table,
refs = refs,
state = {
components = {},
docked = options.parent and true or false,
history = { _index = 0 },
search_info = search_info,
search_opened = false,
search_query = "",
selected_tech_level = 0,
warning_shown = false,
},
}
index.load(self)
player_table.guis.info[id] = self
player_table.guis.info._active_id = id
if options.anchor then
player_table.guis.info._relative_id = id
end
self:update_contents({ new_context = context })
end
function index.load(self)
setmetatable(self, { __index = Gui })
end
--- Find all info GUIs that are viewing the given context.
--- @param player_table PlayerTable
--- @param context Context
--- @return table<number, InfoGui>
function index.find_open_context(player_table, context)
local open = {}
for id, Gui in pairs(player_table.guis.info) do
if not constants.ignored_info_ids[id] then
local state = Gui.state
local opened_context = state.history[state.history._index]
if opened_context and opened_context.class == context.class and opened_context.name == context.name then
open[id] = Gui
end
end
end
return open
end
-- function root.update_all(player, player_table)
-- for id in pairs(player_table.guis.info) do
-- if not constants.ignored_info_ids[id] then
-- root.update_contents(player, player_table, id, { refresh = true })
-- end
-- end
-- end
-- function root.bring_all_to_front(player_table)
-- for id, gui_data in pairs(player_table.guis.info) do
-- if not constants.ignored_info_ids[id] then
-- if gui_data.state.docked then
-- if gui_data.state.search_info then
-- gui_data.refs.root.bring_to_front()
-- end
-- else
-- gui_data.refs.window.bring_to_front()
-- end
-- end
-- end
-- end
return index