Первый фикс

Пачки некоторых позиций увеличены
This commit is contained in:
2024-03-01 20:53:32 +03:00
commit 7c9c708c92
23653 changed files with 767936 additions and 0 deletions

View File

@@ -0,0 +1,304 @@
local math = require("__flib__.math")
local on_tick_n = require("__flib__.on-tick-n")
local constants = require("constants")
local database = require("scripts.database")
local gui_util = require("scripts.gui.util")
local util = require("scripts.util")
local actions = {}
--- @param Gui InfoGui
function actions.set_as_active(Gui, _, _)
Gui.player_table.guis.info._active_id = Gui.id
end
--- @param Gui InfoGui
--- @param e on_gui_click
function actions.reset_location(Gui, _, e)
if e.button == defines.mouse_button_type.middle then
Gui.refs.root.force_auto_center()
end
end
--- @param Gui InfoGui
function actions.close(Gui, _, _)
Gui:destroy()
end
--- @param Gui InfoGui
function actions.bring_to_front(Gui, _, _)
if not Gui.state.docked then
Gui.refs.root.bring_to_front()
end
end
--- @param Gui InfoGui
function actions.toggle_search(Gui, _, _)
local state = Gui.state
local refs = Gui.refs
local opened = state.search_opened
state.search_opened = not opened
local search_button = refs.titlebar.search_button
local search_textfield = refs.titlebar.search_textfield
if opened then
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 = ""
-- Refresh page
Gui:update_contents()
end
else
-- Show search textfield
search_button.sprite = "utility/search_black"
search_button.style = "flib_selected_frame_action_button"
search_textfield.visible = true
search_textfield.focus()
end
end
--- @param Gui InfoGui
--- @param msg table
--- @param e on_gui_click
function actions.navigate(Gui, msg, e)
-- Update position in history
local delta = msg.delta
local history = Gui.state.history
if e.shift then
if delta < 0 then
history._index = 1
else
history._index = #history
end
else
history._index = math.clamp(history._index + delta, 1, #history)
end
Gui:update_contents()
end
--- @param Gui InfoGui
--- @param msg table
--- @param e on_gui_text_changed
function actions.update_search_query(Gui, msg, e)
local state = Gui.state
local id = msg.id
local query = string.lower(e.element.text)
-- Fuzzy search
if Gui.player_table.settings.general.search.fuzzy_search then
query = string.gsub(query, ".", "%1.*")
end
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
-- Save query
state.search_query = query
-- Remove scheduled update if one exists
if state.update_results_ident then
on_tick_n.remove(state.update_results_ident)
state.update_results_ident = nil
end
if query == "" then
-- Update now
Gui:update_contents({ refresh = true })
else
-- Update in a while
state.update_results_ident = on_tick_n.add(
game.tick + constants.search_timeout,
{ gui = "info", id = id, action = "update_search_results", player_index = e.player_index }
)
end
end
--- @param Gui InfoGui
function actions.update_search_results(Gui, _, _)
-- Update based on query
Gui:update_contents({ refresh = true })
end
--- @param Gui InfoGui
--- @param e on_gui_click
function actions.navigate_to(Gui, _, e)
local context = gui_util.navigate_to(e)
if context then
if e.button == defines.mouse_button_type.middle then
INFO_GUI.build(Gui.player, Gui.player_table, context)
else
Gui:update_contents({ new_context = context })
end
end
end
--- @param Gui InfoGui
--- @param msg table
function actions.navigate_to_plain(Gui, msg, _)
Gui:update_contents({ new_context = msg.context })
end
--- @param Gui InfoGui
function actions.open_in_tech_window(Gui, _, _)
Gui.player_table.flags.technology_gui_open = true
Gui.player.open_technology_gui(Gui:get_context().name)
end
--- @param Gui InfoGui
function actions.go_to_base_fluid(Gui, _, _)
local base_fluid = database.fluid[Gui:get_context().name].prototype_name
Gui:update_contents({ new_context = { class = "fluid", name = base_fluid } })
end
--- @param Gui InfoGui
function actions.toggle_quick_ref(Gui, _, _)
local player = Gui.player
-- Toggle quick ref GUI
local name = Gui:get_context().name
--- @type QuickRefGui?
local QuickRefGui = util.get_gui(player.index, "quick_ref", name)
local to_state = false
if QuickRefGui then
QuickRefGui:destroy()
else
to_state = true
QUICK_REF_GUI.build(player, Gui.player_table, name)
end
-- Update all quick ref buttons
for _, InfoGui in pairs(INFO_GUI.find_open_context(Gui.player_table, Gui:get_context())) do
InfoGui:dispatch({
action = "update_header_button",
button = "quick_ref_button",
to_state = to_state,
})
end
end
--- @param Gui InfoGui
function actions.toggle_favorite(Gui, _, _)
local player_table = Gui.player_table
local favorites = player_table.favorites
local context = Gui:get_context()
local combined_name = context.class .. "." .. context.name
local to_state
if favorites[combined_name] then
to_state = false
favorites[combined_name] = nil
else
-- Copy the table instead of passing a reference
favorites[combined_name] = { class = context.class, name = context.name }
to_state = true
end
for _, InfoGui in pairs(INFO_GUI.find_open_context(Gui.player_table, context)) do
InfoGui:dispatch({ action = "update_header_button", button = "favorite_button", to_state = to_state })
end
local SearchGui = util.get_gui(Gui.player.index, "search")
if SearchGui and SearchGui.refs.window.visible then
SearchGui:dispatch("update_favorites")
end
end
--- @param Gui InfoGui
--- @param msg table
function actions.update_header_button(Gui, msg, _)
local button = Gui.refs.header[msg.button]
if msg.to_state then
button.style = "flib_selected_tool_button"
button.tooltip = constants.header_button_tooltips[msg.button].selected
else
button.style = "tool_button"
button.tooltip = constants.header_button_tooltips[msg.button].unselected
end
end
--- @param Gui InfoGui
--- @param msg table
function actions.open_list(Gui, msg, _)
local list_context = msg.context
local source = msg.source
local list = database[list_context.class][list_context.name][source]
if list and #list > 0 then
local first_obj = list[1]
OPEN_PAGE(Gui.player, Gui.player_table, {
class = first_obj.class,
name = first_obj.name,
list = {
context = list_context,
index = 1,
source = source,
},
})
end
end
--- @param Gui InfoGui
--- @param msg table
function actions.toggle_collapsed(Gui, msg, _)
local context = msg.context
local component_index = msg.component_index
local component_ident = constants.pages[context.class][component_index]
if component_ident then
local state = Gui.state.components[component_index]
if state then
state.collapsed = not state.collapsed
Gui:update_contents({ refresh = true })
end
end
end
--- @param Gui InfoGui
--- @param msg table
function actions.change_tech_level(Gui, msg, _)
local context = Gui:get_context()
local state = Gui.state
local context_data = database[context.class][context.name]
local min = context_data.min_level
local max = context_data.max_level
local new_level = math.clamp(state.selected_tech_level + msg.delta, min, max)
if new_level ~= state.selected_tech_level then
state.selected_tech_level = new_level
Gui:update_contents({ refresh = true })
end
end
--- @param Gui InfoGui
function actions.detach_window(Gui, _, _)
local state = Gui.state
-- Just in case
if not state.docked then
return
end
local context = Gui:get_context()
-- Close this GUI and create a detached one
Gui:destroy()
OPEN_PAGE(Gui.player, Gui.player_table, context)
end
--- @param Gui InfoGui
function actions.print_object(Gui, _, _)
local context = Gui:get_context()
local obj_data = database[context.class][context.name]
if obj_data then
if __DebugAdapter then
__DebugAdapter.print(obj_data)
Gui.player.print("Object data has been printed to the debug console.")
else
log(serpent.block(obj_data))
Gui.player.print("Object data has been printed to the log file.")
end
end
end
return actions

View File

@@ -0,0 +1,653 @@
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

View File

@@ -0,0 +1,190 @@
local gui = require("__flib__.gui")
local constants = require("constants")
local database = require("scripts.database")
local formatter = require("scripts.formatter")
local list_box = {}
function list_box.build(parent, index, component, variables)
return gui.build(parent, {
{
type = "flow",
direction = "vertical",
index = index,
ref = { "root" },
action = {
on_click = { gui = "info", id = variables.gui_id, action = "set_as_active" },
},
{
type = "flow",
style_mods = { vertical_align = "center" },
action = {
on_click = { gui = "info", id = variables.gui_id, action = "set_as_active" },
},
{ type = "label", style = "rb_list_box_label", ref = { "label" } },
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{
type = "sprite-button",
style = "mini_button_aligned_to_text_vertically_when_centered",
tooltip = { "gui.rb-open-list-in-new-window" },
sprite = "rb_export_black",
ref = { "open_list_button" },
-- NOTE: Actions are set in the update function
},
{
type = "sprite-button",
style = "mini_button_aligned_to_text_vertically_when_centered",
ref = { "expand_collapse_button" },
-- NOTE: Sprite, tooltip, and action are set in the update function
},
},
{
type = "frame",
style = "deep_frame_in_shallow_frame",
{
type = "scroll-pane",
style = "rb_list_box_scroll_pane",
ref = { "scroll_pane" },
},
},
},
})
end
function list_box.default_state(settings)
return { collapsed = settings.default_state == "collapsed" }
end
function list_box.update(component, refs, context_data, player_data, settings, variables)
-- Scroll pane
local scroll = refs.scroll_pane
local children = scroll.children
-- Settings and variables
local always_show = component.always_show
local context = variables.context
local query = variables.search_query
local search_type = player_data.settings.general.search.search_type
-- Add items
local i = 0 -- The "added" index
local iterator = component.use_pairs and pairs or ipairs
local objects = settings.default_state ~= "hidden" and context_data[component.source] or {}
for _, obj in iterator(objects) do
local translation = player_data.translations[obj.class][obj.name]
-- Match against search string
local matched
if search_type == "both" then
matched = string.find(string.lower(obj.name), query) or string.find(string.lower(translation), query)
elseif search_type == "internal" then
matched = string.find(string.lower(obj.name), query)
elseif search_type == "localised" then
matched = string.find(string.lower(translation), query)
end
if matched then
local obj_data = database[obj.class][obj.name]
local blueprint_result
if context.class == "recipe" and component.source == "made_in" and obj_data.blueprintable then
blueprint_result = { name = obj.name, recipe = context.name }
elseif context.class == "entity" and component.source == "can_craft" and context_data.blueprintable then
blueprint_result = { name = context.name, recipe = obj.name }
end
local info = formatter(obj_data, player_data, {
always_show = always_show,
amount_ident = obj.amount_ident,
blueprint_result = blueprint_result,
rocket_parts_required = obj_data.rocket_parts_required,
})
if info then
i = i + 1
local style = info.researched and "rb_list_box_item" or "rb_unresearched_list_box_item"
local item = children[i]
if item then
item.style = style
item.caption = info.caption
item.tooltip = info.tooltip
item.enabled = info.num_interactions > 0
gui.update_tags(
item,
{ blueprint_result = blueprint_result, context = { class = obj.class, name = obj.name } }
)
else
gui.add(scroll, {
type = "button",
style = style,
caption = info.caption,
tooltip = info.tooltip,
enabled = info.num_interactions > 0,
mouse_button_filter = { "left", "middle" },
tags = {
blueprint_result = blueprint_result,
context = { class = obj.class, name = obj.name },
},
actions = {
on_click = { gui = "info", id = variables.gui_id, action = "navigate_to" },
},
})
end
end
end
end
-- Destroy extraneous items
for j = i + 1, #children do
children[j].destroy()
end
-- Set listbox properties
if i > 0 then
refs.root.visible = true
local translations = player_data.translations.gui
-- Update label caption
refs.label.caption = formatter.expand_string(
translations.list_box_label,
translations[component.source] or component.source,
i
)
-- Update open list button
if i > 1 then
refs.open_list_button.visible = true
gui.set_action(refs.open_list_button, "on_click", {
gui = "info",
id = variables.gui_id,
action = "open_list",
context = variables.context,
source = component.source,
})
else
refs.open_list_button.visible = false
end
-- Update expand/collapse button and height
gui.set_action(refs.expand_collapse_button, "on_click", {
gui = "info",
id = variables.gui_id,
action = "toggle_collapsed",
context = variables.context,
component_index = variables.component_index,
})
if variables.component_state.collapsed then
refs.expand_collapse_button.sprite = "rb_collapsed"
scroll.style.maximal_height = 1
refs.expand_collapse_button.tooltip = { "gui.rb-expand" }
else
refs.expand_collapse_button.sprite = "rb_expanded"
scroll.style.maximal_height = (settings.max_rows or constants.default_max_rows) * 28
refs.expand_collapse_button.tooltip = { "gui.rb-collapse" }
end
else
refs.root.visible = false
end
return i > 0
end
return list_box

View File

@@ -0,0 +1,248 @@
local gui = require("__flib__.gui")
local table = require("__flib__.table")
local database = require("scripts.database")
local formatter = require("scripts.formatter")
local table_comp = {}
function table_comp.build(parent, index, component, variables)
local has_label = (component.label or component.source) and true or false
return gui.build(parent, {
{
type = "flow",
style_mods = not has_label and { top_margin = 4 } or nil,
direction = "vertical",
index = index,
ref = { "root" },
action = {
on_click = { gui = "info", id = variables.gui_id, action = "set_as_active" },
},
{
type = "flow",
style_mods = { vertical_align = "center" },
action = {
on_click = { gui = "info", id = variables.gui_id, action = "set_as_active" },
},
visible = has_label,
{ type = "label", style = "rb_list_box_label", ref = { "label" } },
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{
type = "sprite-button",
style = "mini_button_aligned_to_text_vertically_when_centered",
ref = { "expand_collapse_button" },
-- NOTE: Sprite, tooltip, and action are set in the update function
},
},
{
type = "frame",
style = "deep_frame_in_shallow_frame",
action = {
on_click = { gui = "info", id = variables.gui_id, action = "set_as_active" },
},
ref = { "deep_frame" },
{
type = "table",
style = "rb_info_table",
column_count = 2,
ref = { "table" },
-- Dummy elements so the first row doesn't get used
{ type = "empty-widget" },
{ type = "empty-widget" },
},
},
},
})
end
function table_comp.default_state(settings)
return { collapsed = settings.default_state == "collapsed" }
end
function table_comp.update(component, refs, object_data, player_data, settings, variables)
local tbl = refs.table
local children = tbl.children
local gui_translations = player_data.translations.gui
local search_query = variables.search_query
local i = 2
local is_shown = settings.default_state ~= "hidden"
local row_settings = settings.rows
local source_tbl = is_shown and (component.source and object_data[component.source] or component.rows) or {}
for _, row in ipairs(source_tbl) do
local row_name = row.label or row.source
local value = row.value or object_data[row.source]
if value and (not row_settings or row_settings[row_name]) then
local caption = gui_translations[row_name] or row_name
if string.find(string.lower(caption), search_query) then
-- Label
i = i + 1
local label_label = children[i]
if not label_label or not label_label.valid then
label_label = tbl.add({
type = "label",
style = "rb_table_label",
index = i,
})
end
local tooltip = row.label_tooltip
if tooltip then
caption = caption .. " [img=info]"
tooltip = gui_translations[row.label_tooltip]
else
tooltip = ""
end
label_label.caption = caption
label_label.tooltip = tooltip
-- Value
if row.type == "plain" then
local fmt = row.formatter
if fmt then
value = formatter[fmt](value, gui_translations)
end
i = i + 1
local value_label = children[i]
if not value_label or not value_label.valid or value_label.type ~= "label" then
if value_label then
value_label.destroy()
end
value_label = tbl.add({ type = "label", index = i })
end
value_label.caption = value
elseif row.type == "goto" then
i = i + 1
local button = children[i]
if not button or button.type ~= "button" then
if button then
button.destroy()
end
button = tbl.add({
type = "button",
style = "rb_table_button",
mouse_button_filter = { "left", "middle" },
index = i,
})
end
local source_data = database[value.class][value.name]
local options = table.shallow_copy(row.options or {})
options.label_only = true
options.amount_ident = value.amount_ident
options.blueprint_result = value.class == "entity" and source_data.blueprintable and { name = value.name }
or nil
local info = formatter(source_data, player_data, options)
if info then
button.caption = info.caption
button.tooltip = info.tooltip
gui.set_action(button, "on_click", { gui = "info", id = variables.gui_id, action = "navigate_to" })
gui.update_tags(
button,
{ context = { class = value.class, name = value.name }, blueprint_result = options.blueprint_result }
)
else
-- Don't actually show this row
-- This is an ugly way to do it, but whatever
button.destroy()
label_label.destroy()
i = i - 2
end
elseif row.type == "tech_level_selector" then
i = i + 1
local flow = children[i]
if not flow or flow.type ~= "flow" then
if flow then
flow.destroy()
end
flow = gui.build(tbl, {
{
type = "flow",
style_mods = { vertical_align = "center" },
index = i,
ref = { "flow" },
{
type = "sprite-button",
style = "mini_button_aligned_to_text_vertically_when_centered",
sprite = "rb_minus_black",
mouse_button_filter = { "left" },
actions = {
on_click = { gui = "info", id = variables.gui_id, action = "change_tech_level", delta = -1 },
},
},
{ type = "label", name = "tech_level_label" },
{
type = "sprite-button",
style = "mini_button_aligned_to_text_vertically_when_centered",
sprite = "rb_plus_black",
mouse_button_filter = { "left" },
actions = {
on_click = { gui = "info", id = variables.gui_id, action = "change_tech_level", delta = 1 },
},
},
},
}).flow
end
flow.tech_level_label.caption = formatter.number(variables.selected_tech_level)
elseif row.type == "tech_level_research_unit_count" then
i = i + 1
local value_label = children[i]
if not value_label or value_label.type ~= "label" then
if value_label then
value_label.destroy()
end
value_label = tbl.add({ type = "label", index = i })
end
local tech_level = variables.selected_tech_level
value_label.caption = formatter[row.formatter](
game.evaluate_expression(value, { L = tech_level, l = tech_level })
)
end
end
end
end
for j = i + 1, #children do
children[j].destroy()
end
if i > 3 then
refs.root.visible = true
local label_source = component.source or component.label
if label_source then
if component.hide_count then
refs.label.caption = gui_translations[label_source] or label_source
else
refs.label.caption = formatter.expand_string(
gui_translations.list_box_label,
gui_translations[label_source] or label_source,
i / 2 - 1
)
end
end
-- Update expand/collapse button and height
gui.set_action(refs.expand_collapse_button, "on_click", {
gui = "info",
id = variables.gui_id,
action = "toggle_collapsed",
context = variables.context,
component_index = variables.component_index,
})
if variables.component_state.collapsed then
refs.deep_frame.style.maximal_height = 1
refs.expand_collapse_button.sprite = "rb_collapsed"
refs.expand_collapse_button.tooltip = { "gui.rb-expand" }
else
refs.deep_frame.style.maximal_height = 0
refs.expand_collapse_button.sprite = "rb_expanded"
refs.expand_collapse_button.tooltip = { "gui.rb-collapse" }
end
else
refs.root.visible = false
end
return i > 3
end
return table_comp

View File

@@ -0,0 +1,50 @@
local gui = require("__flib__.gui")
local gui_util = require("scripts.gui.util")
local actions = {}
--- @param Gui QuickRefGui
function actions.close(Gui, _, _)
Gui:destroy()
end
--- @param Gui QuickRefGui
--- @param e on_gui_click
function actions.reset_location(Gui, _, e)
if e.button == defines.mouse_button_type.middle then
Gui.refs.window.location = { x = 0, y = 0 }
end
end
--- @param Gui QuickRefGui
function actions.bring_to_front(Gui, _, _)
Gui.refs.window.bring_to_front()
end
--- @param Gui QuickRefGui
--- @param e on_gui_click
function actions.handle_button_click(Gui, _, e)
if e.alt then
local button = e.element
local style = button.style.name
if style == "flib_slot_button_green" then
button.style = gui.get_tags(button).previous_style
else
gui.update_tags(button, { previous_style = style })
button.style = "flib_slot_button_green"
end
else
local context = gui_util.navigate_to(e)
if context then
OPEN_PAGE(Gui.player, Gui.player_table, context)
end
end
end
--- @param Gui QuickRefGui
function actions.view_details(Gui, _, _)
OPEN_PAGE(Gui.player, Gui.player_table, { class = "recipe", name = Gui.recipe_name })
end
return actions

View File

@@ -0,0 +1,231 @@
local gui = require("__flib__.gui")
local constants = require("constants")
local database = require("scripts.database")
local formatter = require("scripts.formatter")
local util = require("scripts.util")
local function quick_ref_panel(ref)
return {
type = "flow",
direction = "vertical",
ref = { ref, "flow" },
{ type = "label", style = "rb_list_box_label", ref = { ref, "label" } },
{
type = "frame",
style = "rb_slot_table_frame",
ref = { ref, "frame" },
{ type = "table", style = "slot_table", column_count = 5, ref = { ref, "table" } },
},
}
end
--- @class QuickRefGuiRefs
--- @field window LuaGuiElement
--- @field titlebar_flow LuaGuiElement
--- @field label LuaGuiElement
--- @class QuickRefGui
local Gui = {}
local actions = require("scripts.gui.quick-ref.actions")
function Gui:dispatch(msg, e)
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.quick_ref[self.recipe_name] = nil
local context = { class = "recipe", name = self.recipe_name }
for _, InfoGui in pairs(INFO_GUI.find_open_context(self.player_table, context)) do
InfoGui:dispatch({
action = "update_header_button",
button = "quick_ref_button",
to_state = false,
})
end
end
function Gui:update_contents()
local refs = self.refs
local show_made_in = self.player_table.settings.general.content.show_made_in_in_quick_ref
local recipe_data = database.recipe[self.recipe_name]
local player_data = formatter.build_player_data(self.player, self.player_table)
-- Label
local recipe_info = formatter(recipe_data, player_data, { always_show = true, is_label = true })
local label = refs.label
label.caption = recipe_info.caption
label.tooltip = recipe_info.tooltip
label.style = recipe_info.researched and "rb_toolbar_label" or "rb_unresearched_toolbar_label"
-- Slot boxes
for _, source in ipairs({ "ingredients", "products", "made_in" }) do
local box = refs[source]
if source == "made_in" and not show_made_in then
box.flow.visible = false
break
else
box.flow.visible = true
end
local table = box.table
local buttons = table.children
local i = 0
for _, object in pairs(recipe_data[source]) do
local object_data = database[object.class][object.name]
local blueprint_result = source == "made_in" and { name = object.name, self.recipe_name } or nil
local object_info = formatter(object_data, player_data, {
always_show = source ~= "made_in",
amount_ident = object.amount_ident,
amount_only = true,
blueprint_result = blueprint_result,
})
if object_info then
i = i + 1
local button_style = object_info.researched and "flib_slot_button_default" or "flib_slot_button_red"
local button = buttons[i]
if button and button.valid then
button.style = button_style
button.sprite = constants.class_to_type[object.class] .. "/" .. object_data.prototype_name
button.tooltip = object_info.tooltip
gui.update_tags(button, {
blueprint_result = blueprint_result,
context = object,
researched = object_data.researched,
})
else
local probability = object.amount_ident.probability
if probability == 1 then
probability = false
end
gui.build(table, {
{
type = "sprite-button",
style = button_style,
sprite = constants.class_to_type[object.class] .. "/" .. object_data.prototype_name,
tooltip = object_info.tooltip,
tags = {
blueprint_result = blueprint_result,
context = object,
researched = object_data.researched,
},
actions = {
on_click = {
gui = "quick_ref",
id = self.recipe_name,
action = "handle_button_click",
source = source,
},
},
{
type = "label",
style = "rb_slot_label",
caption = object_info.caption,
ignored_by_interaction = true,
},
{
type = "label",
style = "rb_slot_label_top",
caption = probability and "%" or "",
ignored_by_interaction = true,
},
},
})
end
end
for j = i + 1, #buttons do
buttons[j].destroy()
end
-- Label
box.label.caption = { "gui.rb-list-box-label", { "gui.rb-" .. string.gsub(source, "_", "-") }, i }
end
end
end
local index = {}
function index.build(player, player_table, recipe_name)
--- @type QuickRefGuiRefs
local refs = gui.build(player.gui.screen, {
{
type = "frame",
direction = "vertical",
ref = { "window" },
{
type = "flow",
style = "flib_titlebar_flow",
ref = { "titlebar_flow" },
actions = {
on_click = { gui = "quick_ref", id = recipe_name, action = "reset_location" },
},
{ type = "label", style = "frame_title", caption = { "gui.rb-recipe" }, ignored_by_interaction = true },
{ type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
util.frame_action_button(
"rb_expand",
{ "gui.rb-view-details" },
nil,
{ gui = "quick_ref", id = recipe_name, action = "view_details" }
),
util.frame_action_button(
"utility/close",
{ "gui.close" },
nil,
{ gui = "quick_ref", id = recipe_name, action = "close" }
),
},
{
type = "frame",
style = "rb_quick_ref_content_frame",
direction = "vertical",
{
type = "frame",
style = "subheader_frame",
{ type = "label", style = "rb_toolbar_label", ref = { "label" } },
{ type = "empty-widget", style = "flib_horizontal_pusher" },
},
{
type = "flow",
style = "rb_quick_ref_content_flow",
direction = "vertical",
quick_ref_panel("ingredients"),
quick_ref_panel("products"),
quick_ref_panel("made_in"),
},
},
},
})
refs.titlebar_flow.drag_target = refs.window
--- @class QuickRefGui
local self = {
player = player,
player_table = player_table,
recipe_name = recipe_name,
refs = refs,
}
index.load(self)
player_table.guis.quick_ref[recipe_name] = self
self:update_contents()
end
function index.load(self)
setmetatable(self, { __index = Gui })
end
return index

View File

@@ -0,0 +1,427 @@
local gui = require("__flib__.gui")
local on_tick_n = require("__flib__.on-tick-n")
local table = require("__flib__.table")
local constants = require("constants")
local database = require("scripts.database")
local formatter = require("scripts.formatter")
local gui_util = require("scripts.gui.util")
local util = require("scripts.util")
local actions = {}
--- @param Gui SearchGui
--- @param e on_gui_click
function actions.reset_location(Gui, _, e)
if e.button ~= defines.mouse_button_type.middle then
return
end
if Gui.player_table.settings.general.interface.search_gui_location == "top_left" then
local scale = Gui.player.display_scale
Gui.refs.window.location = table.map(constants.search_gui_top_left_location, function(pos)
return pos * scale
end)
Gui.refs.window.auto_center = false
else
Gui.refs.window.force_auto_center()
end
end
--- @param Gui SearchGui
function actions.close(Gui, _, _)
if not Gui.state.ignore_closed and not Gui.player_table.flags.technology_gui_open then
Gui:close()
end
end
--- @param Gui SearchGui
function actions.toggle_pinned(Gui, _, _)
local player = Gui.player
local refs = Gui.refs
local state = Gui.state
local pin_button = refs.titlebar.pin_button
state.pinned = not state.pinned
if state.pinned then
pin_button.style = "flib_selected_frame_action_button"
pin_button.sprite = "rb_pin_black"
if player.opened == refs.window then
state.ignore_closed = true
player.opened = nil
state.ignore_closed = false
end
else
pin_button.style = "frame_action_button"
pin_button.sprite = "rb_pin_white"
player.opened = refs.window
end
end
--- @param Gui SearchGui
function actions.toggle_settings(Gui, _, _)
local state = Gui.state
local player = Gui.player
state.ignore_closed = true
local SettingsGui = util.get_gui(Gui.player.index, "settings")
if SettingsGui then
SettingsGui:destroy()
else
SETTINGS_GUI.build(player, Gui.player_table)
end
state.ignore_closed = false
local settings_button = Gui.refs.titlebar.settings_button
if Gui.player_table.guis.settings then
settings_button.style = "flib_selected_frame_action_button"
settings_button.sprite = "rb_settings_black"
else
settings_button.style = "frame_action_button"
settings_button.sprite = "rb_settings_white"
if not state.pinned then
player.opened = Gui.refs.window
end
end
end
--- @param Gui SearchGui
function actions.deselect_settings_button(Gui, _, _)
local settings_button = Gui.refs.titlebar.settings_button
settings_button.style = "frame_action_button"
settings_button.sprite = "rb_settings_white"
if not Gui.state.pinned and Gui.refs.window.visible then
Gui.player.opened = Gui.refs.window
end
end
--- @param Gui SearchGui
--- @param e on_gui_text_changed
function actions.update_search_query(Gui, _, e)
local player_table = Gui.player_table
local state = Gui.state
local refs = Gui.refs
local class_filter
local query = string.lower(e.element.text)
if string.find(query, "/") then
-- The `_`s here are technically globals, but whatever
_, _, class_filter, query = string.find(query, "^/(.-)/(.-)$")
if class_filter then
class_filter = string.lower(class_filter)
end
-- Check translations of each class filter
local matched = false
if class_filter then
local gui_translations = player_table.translations.gui
for _, class in pairs(constants.classes) do
if class_filter == string.lower(gui_translations[class]) then
matched = true
class_filter = class
break
end
end
end
-- Invalidate textfield
if not class_filter or not query or not matched then
class_filter = false
query = nil
end
end
-- Remove results update action if there is one
if state.update_results_ident then
on_tick_n.remove(state.update_results_ident)
state.update_results_ident = nil
end
if query then
-- Fuzzy search
if player_table.settings.general.search.fuzzy_search then
query = string.gsub(query, ".", "%1.*")
end
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
-- Save query
state.search_query = query
state.class_filter = class_filter
-- Reset textfield style
refs.search_textfield.style = "rb_search_textfield"
if #query == 0 and not class_filter then
-- Update immediately
actions.update_search_results(Gui)
else
-- Update in a while
state.update_results_ident = on_tick_n.add(
game.tick + constants.search_timeout,
{ gui = "search", action = "update_search_results", player_index = e.player_index }
)
end
else
state.search_query = ""
refs.search_textfield.style = "rb_search_invalid_textfield"
end
end
--- @param Gui SearchGui
function actions.update_search_results(Gui, _, _)
local player = Gui.player
local player_table = Gui.player_table
local state = Gui.state
local refs = Gui.refs
-- Data
local player_data = formatter.build_player_data(player, player_table)
local show_fluid_temperatures = player_table.settings.general.search.show_fluid_temperatures
local search_type = player_table.settings.general.search.search_type
local class_filter = state.class_filter
local query = state.search_query
if state.search_type == "textual" then
-- Update results based on query
local i = 0
local pane = refs.textual_results_pane
local children = pane.children
local max = constants.search_results_limit
if class_filter ~= false and (class_filter or #query >= 2) then
for class in pairs(constants.pages) do
if not class_filter or class_filter == class then
for internal, translation in pairs(player_table.translations[class]) do
-- Match against search string
local matched
if search_type == "both" then
matched = string.find(string.lower(internal), query) or string.find(string.lower(translation), query)
elseif search_type == "internal" then
matched = string.find(string.lower(internal), query)
elseif search_type == "localised" then
matched = string.find(string.lower(translation), query)
end
if matched then
local obj_data = database[class][internal]
-- Check temperature settings
local passed = true
if obj_data.class == "fluid" then
local temperature_ident = obj_data.temperature_ident
if temperature_ident then
local is_range = temperature_ident.min ~= temperature_ident.max
if is_range then
if show_fluid_temperatures ~= "all" then
passed = false
end
else
if show_fluid_temperatures == "off" then
passed = false
end
end
end
end
if passed then
local blueprint_result = class == "entity" and obj_data.blueprintable and { name = internal } or nil
local info = formatter(obj_data, player_data, { blueprint_result = blueprint_result })
if info then
i = i + 1
local style = info.researched and "rb_list_box_item" or "rb_unresearched_list_box_item"
local item = children[i]
if item then
item.style = style
item.caption = info.caption
item.tooltip = info.tooltip
item.enabled = info.num_interactions > 0
gui.update_tags(
item,
{ blueprint_result = blueprint_result, context = { class = class, name = internal } }
)
else
gui.add(pane, {
type = "button",
style = style,
caption = info.caption,
tooltip = info.tooltip,
enabled = info.num_interactions > 0,
mouse_button_filter = { "left", "middle" },
tags = {
blueprint_result = blueprint_result,
context = { class = class, name = internal },
},
actions = {
on_click = { gui = "search", action = "open_object" },
},
})
if i >= max then
break
end
end
end
end
end
end
end
if i >= max then
break
end
end
end
-- Destroy extraneous items
for j = i + 1, #children do
children[j].destroy()
end
elseif state.search_type == "visual" then
refs.objects_frame.visible = true
refs.warning_frame.visible = false
--- @type LuaGuiElement
local group_table = refs.group_table
for _, group_scroll in pairs(refs.objects_frame.children) do
local group_has_results = false
for _, subgroup_table in pairs(group_scroll.children) do
local visible_count = 0
for _, obj_button in pairs(subgroup_table.children) do
local context = gui.get_tags(obj_button).context
local matched
-- Match against class filter
if not class_filter or class_filter == context.class then
local translation = player_data.translations[context.class][context.name]
-- Match against search string
if search_type == "both" then
matched = string.find(string.lower(context.name), query) or string.find(string.lower(translation), query)
elseif search_type == "internal" then
matched = string.find(string.lower(context.name), query)
elseif search_type == "localised" then
matched = string.find(string.lower(translation), query)
end
end
if matched then
obj_button.visible = true
visible_count = visible_count + 1
else
obj_button.visible = false
end
end
if visible_count > 0 then
group_has_results = true
subgroup_table.visible = true
else
subgroup_table.visible = false
end
end
local group_name = group_scroll.name
local group_button = group_table[group_name]
if group_has_results then
group_button.style = "rb_filter_group_button_tab"
group_button.enabled = state.active_group ~= group_scroll.name
if state.active_group == group_name then
group_scroll.visible = true
else
group_scroll.visible = false
end
else
group_scroll.visible = false
group_button.style = "rb_disabled_filter_group_button_tab"
group_button.enabled = false
if state.active_group == group_name then
local matched = false
for _, group_button in pairs(group_table.children) do
if group_button.enabled then
matched = true
actions.change_group(Gui, { group = group_button.name, ignore_last_button = true })
break
end
end
if not matched then
refs.objects_frame.visible = false
refs.warning_frame.visible = true
end
end
end
end
end
end
--- @param Gui SearchGui
--- @param e on_gui_click
function actions.open_object(Gui, _, e)
local context = gui_util.navigate_to(e)
if context then
local attach = Gui.player_table.settings.general.interface.attach_search_results
local sticky = attach and e.button == defines.mouse_button_type.left
local id = sticky and Gui.state.id and Gui.player_table.guis.info[Gui.state.id] and Gui.state.id or nil
local parent = sticky and Gui.refs.window or nil
OPEN_PAGE(Gui.player, Gui.player_table, context, { id = id, parent = parent })
if sticky and not id then
Gui.state.id = Gui.player_table.guis.info._active_id
end
if not sticky and Gui.player_table.settings.general.interface.close_search_gui_after_selection then
actions.close(Gui)
end
end
end
--- @param Gui SearchGui
function actions.change_search_type(Gui)
local state = Gui.state
local refs = Gui.refs
if state.search_type == "textual" then
state.search_type = "visual"
refs.textual_results_pane.visible = false
refs.visual_results_flow.visible = true
if state.needs_visual_update then
state.needs_visual_update = false
Gui:update_visual_contents()
end
elseif state.search_type == "visual" then
state.search_type = "textual"
refs.textual_results_pane.visible = true
refs.visual_results_flow.visible = false
end
actions.update_search_results(Gui)
end
--- @param Gui SearchGui
--- @param msg table
function actions.change_group(Gui, msg)
local last_group = Gui.state.active_group
if not msg.ignore_last_button then
Gui.refs.group_table[last_group].enabled = true
end
Gui.refs.objects_frame[last_group].visible = false
local new_group = msg.group
Gui.refs.group_table[new_group].enabled = false
Gui.refs.objects_frame[new_group].visible = true
Gui.state.active_group = msg.group
end
--- @param Gui SearchGui
function actions.update_favorites(Gui, _, _)
Gui:update_favorites()
end
--- @param Gui SearchGui
function actions.update_history(Gui, _, _)
Gui:update_history()
end
--- @param Gui SearchGui
function actions.delete_favorites(Gui, _, _)
Gui.player_table.favorites = {}
Gui:update_favorites()
end
--- @param Gui SearchGui
function actions.delete_history(Gui, _, _)
Gui.player_table.global_history = {}
Gui:update_history()
end
return actions

View File

@@ -0,0 +1,512 @@
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 gui_util = require("scripts.gui.util")
local util = require("scripts.util")
--- @class SearchGuiRefs
--- @field window LuaGuiElement
--- @field titlebar SearchGuiTitlebarRefs
--- @field tabbed_pane LuaGuiElement
--- @field search_textfield LuaGuiElement
--- @field textual_results_pane LuaGuiElement
--- @field visual_results_flow LuaGuiElement
--- @field group_table LuaGuiElement
--- @field objects_frame LuaGuiElement
--- @field warning_frame LuaGuiElement
--- @field delete_favorites_button LuaGuiElement
--- @field delete_history_button LuaGuiElement
--- @field favorites_pane LuaGuiElement
--- @field history_pane LuaGuiElement
--- @class SearchGuiTitlebarRefs
--- @field flow LuaGuiElement
--- @field drag_handle LuaGuiElement
--- @field pin_button LuaGuiElement
--- @field settings_button LuaGuiElement
--- @class SearchGui
local Gui = {}
local actions = require("scripts.gui.search.actions")
function Gui:dispatch(msg, e)
if type(msg) == "string" then
actions[msg](self, msg, e)
else
actions[msg.action](self, msg, e)
end
end
function Gui:destroy()
if self.refs.window.valid then
self.refs.window.destroy()
end
self.player_table.guis.search = nil
self.player.set_shortcut_toggled("rb-search", false)
end
function Gui:open()
local refs = self.refs
refs.window.visible = true
refs.window.bring_to_front()
refs.tabbed_pane.selected_tab_index = 1
refs.search_textfield.select_all()
refs.search_textfield.focus()
if not self.state.pinned then
self.player.opened = refs.window
end
-- Workaround to prevent the search GUI from centering itself if the player doesn't manually recenter
if self.player_table.settings.general.interface.search_gui_location ~= "center" then
refs.window.auto_center = false
end
self.player.set_shortcut_toggled("rb-search", true)
if self.state.search_type == "visual" and self.state.needs_visual_update then
self:update_visual_contents()
end
end
function Gui:close()
local window = self.player_table.guis.search.refs.window
window.visible = false
local player = self.player
player.set_shortcut_toggled("rb-search", false)
if player.opened == window then
player.opened = nil
end
end
function Gui:toggle()
if self.refs.window.visible then
self:close()
else
self:open()
end
end
function Gui:update_visual_contents()
self.state.needs_visual_update = false
local player_data = formatter.build_player_data(self.player, self.player_table)
local show_fluid_temperatures = player_data.settings.general.search.show_fluid_temperatures
local groups = {}
for _, objects in pairs(
{ database.item, database.fluid }
-- { database.recipe }
) do
for name, object in pairs(objects) do
-- Create / retrieve group and subgroup
local group = object.group
local group_table = groups[group.name]
if not group_table then
group_table = {
button = {
type = "sprite-button",
name = group.name,
style = "rb_filter_group_button_tab",
sprite = "item-group/" .. group.name,
tooltip = { "item-group-name." .. group.name },
actions = {
on_click = { gui = "search", action = "change_group", group = group.name },
},
},
members = 0,
scroll_pane = {
type = "scroll-pane",
name = group.name,
style = "rb_filter_scroll_pane",
vertical_scroll_policy = "always",
visible = false,
},
subgroups = {},
}
groups[group.name] = group_table
end
local subgroup = object.subgroup
local subgroup_table = group_table.subgroups[subgroup.name]
if not subgroup_table then
subgroup_table = { type = "table", style = "slot_table", column_count = 10 }
group_table.subgroups[subgroup.name] = subgroup_table
table.insert(group_table.scroll_pane, subgroup_table)
end
-- Check fluid temperature
local matched = true
local temperature_ident = object.temperature_ident
if temperature_ident then
local is_range = temperature_ident.min ~= temperature_ident.max
if is_range then
if show_fluid_temperatures ~= "all" then
matched = false
end
else
if show_fluid_temperatures == "off" then
matched = false
end
end
end
if matched then
local blueprint_result = object.place_result and { name = object.place_result.name } or nil
local formatted = formatter(object, player_data, { blueprint_result = blueprint_result })
if formatted then
group_table.members = group_table.members + 1
local style = "default"
if formatted.disabled or formatted.hidden then
style = "grey"
elseif not formatted.researched then
style = "red"
end
-- Create the button
table.insert(subgroup_table, {
type = "sprite-button",
style = "flib_slot_button_" .. style,
sprite = object.class .. "/" .. object.prototype_name,
tooltip = formatted.tooltip,
mouse_button_filter = { "left", "middle", "right" },
tags = {
blueprint_result = blueprint_result,
context = { class = object.class, name = name },
},
actions = {
on_click = { gui = "search", action = "open_object" },
},
temperature_ident and {
type = "label",
style = "rb_slot_label",
caption = temperature_ident.short_string,
ignored_by_interaction = true,
} or nil,
temperature_ident and temperature_ident.short_top_string and {
type = "label",
style = "rb_slot_label_top",
caption = temperature_ident.short_top_string,
ignored_by_interaction = true,
} or nil,
})
end
end
end
end
local group_buttons = {}
local group_scroll_panes = {}
local first_group
for group_name, group in pairs(groups) do
if group.members > 0 then
table.insert(group_buttons, group.button)
table.insert(group_scroll_panes, group.scroll_pane)
if not first_group then
first_group = group_name
end
end
end
if
#self.state.active_group == 0
or not table.for_each(group_buttons, function(button)
return button.name == self.state.active_group
end)
then
self.state.active_group = first_group
end
local refs = self.refs
refs.group_table.clear()
gui.build(refs.group_table, group_buttons)
refs.objects_frame.clear()
gui.build(refs.objects_frame, group_scroll_panes)
self:dispatch({ action = "change_group", group = self.state.active_group, ignore_last_button = true })
self:dispatch("update_search_results")
end
function Gui:update_favorites()
local favorites = self.player_table.favorites
local refs = self.refs
gui_util.update_list_box(
refs.favorites_pane,
favorites,
formatter.build_player_data(self.player, self.player_table),
pairs,
{ always_show = true }
)
refs.delete_favorites_button.enabled = next(favorites) and true or false
for id, InfoGui in pairs(self.player_table.guis.info) do
if not constants.ignored_info_ids[id] then
local context = InfoGui:get_context()
local to_state = favorites[context.class .. "." .. context.name]
InfoGui:dispatch({ action = "update_header_button", button = "favorite_button", to_state = to_state })
end
end
end
function Gui:update_history()
local refs = self.refs
gui_util.update_list_box(
refs.history_pane,
self.player_table.global_history,
formatter.build_player_data(self.player, self.player_table),
ipairs,
{ always_show = true }
)
refs.delete_history_button.enabled = next(self.player_table.global_history) and true or false
end
function Gui:bring_to_front()
self.refs.window.bring_to_front()
end
local index = {}
--- @param player LuaPlayer
--- @param player_table PlayerTable
function index.build(player, player_table)
--- @type SearchGuiRefs
local gui_type = player_table.settings.general.search.default_gui_type
local refs = gui.build(player.gui.screen, {
{
type = "frame",
name = "rb_search_window",
style = "invisible_frame",
visible = false,
ref = { "window" },
actions = {
on_closed = { gui = "search", action = "close" },
},
-- Search frame
{
type = "frame",
direction = "vertical",
{
type = "flow",
style = "flib_titlebar_flow",
ref = { "titlebar", "flow" },
actions = {
on_click = { gui = "search", action = "reset_location" },
},
{
type = "label",
style = "frame_title",
caption = { "gui.rb-search-title" },
ignored_by_interaction = true,
},
{ type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
util.frame_action_button(
"rb_pin",
{ "gui.rb-pin-instruction" },
{ "titlebar", "pin_button" },
{ gui = "search", action = "toggle_pinned" }
),
util.frame_action_button(
"rb_settings",
{ "gui.rb-settings-instruction" },
{ "titlebar", "settings_button" },
{ gui = "search", action = "toggle_settings" }
),
util.frame_action_button(
"utility/close",
{ "gui.close" },
{ "titlebar", "close_button" },
{ gui = "search", action = "close" }
),
},
{
type = "frame",
style = "inside_deep_frame_for_tabs",
direction = "vertical",
ref = { "tab_frame" },
{
type = "tabbed-pane",
style = "tabbed_pane_with_no_side_padding",
style_mods = { maximal_width = 426 },
ref = { "tabbed_pane" },
{
tab = { type = "tab", caption = { "gui.search" } },
content = {
type = "frame",
style = "rb_inside_deep_frame_under_tabs",
direction = "vertical",
{
type = "frame",
style = "rb_subheader_frame",
{
type = "textfield",
style = "flib_widthless_textfield",
style_mods = { horizontally_stretchable = true },
clear_and_focus_on_right_click = true,
ref = { "search_textfield" },
actions = {
on_text_changed = { gui = "search", action = "update_search_query" },
},
},
-- {
-- type = "sprite-button",
-- style = "tool_button",
-- tooltip = { "gui.rb-search-filters" },
-- sprite = "rb_filter",
-- actions = {
-- on_click = { gui = "search", action = "toggle_filters" },
-- },
-- },
{
type = "sprite-button",
style = "tool_button",
tooltip = { "gui.rb-change-search-type" },
sprite = "rb_swap",
actions = {
on_click = { gui = "search", action = "change_search_type" },
},
},
},
{
type = "scroll-pane",
style = "rb_search_results_scroll_pane",
ref = { "textual_results_pane" },
visible = gui_type == "textual",
},
{
type = "flow",
style_mods = { padding = 0, margin = 0, vertical_spacing = 0 },
direction = "vertical",
visible = gui_type == "visual",
ref = { "visual_results_flow" },
{
type = "table",
style = "filter_group_table",
style_mods = { width = 426 },
column_count = 6,
ref = { "group_table" },
},
{
type = "frame",
style = "rb_filter_frame",
{
type = "frame",
style = "deep_frame_in_shallow_frame",
style_mods = { natural_height = 40 * 15, natural_width = 40 * 10 },
ref = { "objects_frame" },
},
{
type = "frame",
style = "rb_warning_frame_in_shallow_frame",
style_mods = { height = 40 * 15, width = 40 * 10 },
ref = { "warning_frame" },
visible = false,
{
type = "flow",
style = "rb_warning_flow",
direction = "vertical",
{ type = "label", style = "bold_label", caption = { "gui.rb-no-results" } },
},
},
},
},
},
},
{
tab = { type = "tab", caption = { "gui.rb-favorites" } },
content = {
type = "frame",
style = "rb_inside_deep_frame_under_tabs",
direction = "vertical",
{
type = "frame",
style = "subheader_frame",
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.rb-delete-favorites" },
ref = { "delete_favorites_button" },
actions = {
on_click = { gui = "search", action = "delete_favorites" },
},
},
},
{ type = "scroll-pane", style = "rb_search_results_scroll_pane", ref = { "favorites_pane" } },
},
},
{
tab = { type = "tab", caption = { "gui.rb-history" } },
content = {
type = "frame",
style = "rb_inside_deep_frame_under_tabs",
direction = "vertical",
{
type = "frame",
style = "subheader_frame",
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.rb-delete-history" },
ref = { "delete_history_button" },
actions = {
on_click = { gui = "search", action = "delete_history" },
},
},
},
{ type = "scroll-pane", style = "rb_search_results_scroll_pane", ref = { "history_pane" } },
},
},
},
},
},
},
})
refs.titlebar.flow.drag_target = refs.window
if player_table.settings.general.interface.search_gui_location == "top_left" then
refs.window.location = table.map(constants.search_gui_top_left_location, function(pos)
return pos * player.display_scale
end)
else
refs.window.force_auto_center()
end
--- @class SearchGui
local self = {
player = player,
player_table = player_table,
state = {
active_group = "",
ignore_closed = false,
needs_visual_update = true,
search_query = "",
search_type = gui_type,
pinned = false,
},
refs = refs,
}
index.load(self)
player_table.guis.search = self
self:update_favorites()
self:update_history()
if gui_type == "visual" then
self:update_visual_contents()
end
end
function index.load(self)
setmetatable(self, { __index = Gui })
end
return index

View File

@@ -0,0 +1,200 @@
local on_tick_n = require("__flib__.on-tick-n")
local constants = require("constants")
local util = require("scripts.util")
local actions = {}
--- @param Gui SettingsGui
function actions.close(Gui, _, _)
Gui:destroy()
local SearchGui = util.get_gui(Gui.player.index, "search")
if SearchGui then
SearchGui:dispatch("deselect_settings_button")
end
end
--- @param Gui SettingsGui
--- @param e on_gui_click
function actions.reset_location(Gui, _, e)
if e.button == defines.mouse_button_type.middle then
Gui.refs.window.force_auto_center()
end
end
--- @param Gui SettingsGui
function actions.toggle_search(Gui, _, _)
local state = Gui.state
local refs = Gui.refs
local opened = state.search_opened
state.search_opened = not opened
local search_button = refs.titlebar.search_button
local search_textfield = refs.titlebar.search_textfield
if opened then
search_button.style = "frame_action_button"
search_button.sprite = "utility/search_white"
search_textfield.visible = false
if state.search_query ~= "" then
-- Reset query
search_textfield.text = ""
state.search_query = ""
-- Immediately refresh page
Gui:update_contents()
end
else
-- Show search textfield
search_button.style = "flib_selected_frame_action_button"
search_button.sprite = "utility/search_black"
search_textfield.visible = true
search_textfield.focus()
end
end
--- @param Gui SettingsGui
--- @param e on_gui_text_changed
function actions.update_search_query(Gui, _, e)
local player_table = Gui.player_table
local state = Gui.state
local query = string.lower(e.element.text)
-- Fuzzy search
if player_table.settings.general.search.fuzzy_search then
query = string.gsub(query, ".", "%1.*")
end
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
-- Save query
state.search_query = query
-- Remove scheduled update if one exists
if state.update_results_ident then
on_tick_n.remove(state.update_results_ident)
state.update_results_ident = nil
end
if query == "" then
-- Update now
actions.update_search_results(Gui)
else
-- Update in a while
state.update_results_ident = on_tick_n.add(
game.tick + constants.search_timeout,
{ gui = "settings", action = "update_search_results", player_index = e.player_index }
)
end
end
--- @param Gui SettingsGui
function actions.update_search_results(Gui, _, _)
Gui:update_contents()
end
--- @param Gui SettingsGui
--- @param msg table
--- @param e on_gui_checked_state_changed|on_gui_selection_state_changed
function actions.change_general_setting(Gui, msg, e)
local type = msg.type
local category = msg.category
local name = msg.name
local setting_ident = constants.general_settings[category][name]
local settings = Gui.player_table.settings.general[category]
local new_value
local element = e.element
-- NOTE: This shouldn't ever happen, but we will avoid a crash just in case!
if not element.valid then
return
end
if type == "bool" then
new_value = element.state
elseif type == "enum" then
local selected_index = element.selected_index
new_value = setting_ident.options[selected_index]
end
-- NOTE: This _also_ shouldn't ever happen, but you can't be too safe!
if new_value ~= nil then
settings[name] = new_value
REFRESH_CONTENTS(Gui.player, Gui.player_table)
-- Update enabled statuses
Gui:update_contents("general")
end
end
--- @param Gui SettingsGui
--- @param e on_gui_selection_state_changed
function actions.change_category(Gui, _, e)
Gui.state.selected_category = e.element.selected_index
Gui:update_contents("categories")
end
--- @param Gui SettingsGui
--- @param msg table
--- @param e on_gui_checked_state_changed
function actions.change_category_setting(Gui, msg, e)
local class = msg.class
local name = msg.name
local category_settings = Gui.player_table.settings.categories[class]
category_settings[name] = e.element.state
REFRESH_CONTENTS(Gui.player, Gui.player_table)
end
--- @param Gui SettingsGui
--- @param e on_gui_selected_tab_changed
function actions.change_page(Gui, _, e)
Gui.state.selected_page = e.element.selected_index
Gui:update_contents("pages")
end
--- @param Gui SettingsGui
--- @param msg table
--- @param e on_gui_selection_state_changed
function actions.change_default_state(Gui, msg, e)
local class = msg.class
local component = msg.component
local component_settings = Gui.player_table.settings.pages[class][component]
if component_settings then
component_settings.default_state = constants.component_states[e.element.selected_index]
end
REFRESH_CONTENTS(Gui.player, Gui.player_table)
end
--- @param Gui SettingsGui
--- @param msg table
--- @param e on_gui_text_changed
function actions.change_max_rows(Gui, msg, e)
local class = msg.class
local component = msg.component
local component_settings = Gui.player_table.settings.pages[class][component]
if component_settings then
component_settings.max_rows = tonumber(e.element.text)
end
REFRESH_CONTENTS(Gui.player, Gui.player_table)
end
--- @param Gui SettingsGui
--- @param msg table
--- @param e on_gui_checked_state_changed
function actions.change_row_visible(Gui, msg, e)
local class = msg.class
local component = msg.component
local row = msg.row
local component_settings = Gui.player_table.settings.pages[class][component]
if component_settings then
component_settings.rows[row] = e.element.state
end
REFRESH_CONTENTS(Gui.player, Gui.player_table)
end
return actions

View File

@@ -0,0 +1,414 @@
local gui = require("__flib__.gui")
local table = require("__flib__.table")
local constants = require("constants")
local database = require("scripts.database")
local util = require("scripts.util")
--- @class SettingsGui
local Gui = {}
local actions = require("scripts.gui.settings.actions")
function Gui:dispatch(msg, e)
if type(msg) == "string" then
actions[msg](self, msg, e)
else
actions[msg.action](self, msg, e)
end
end
function Gui:destroy()
self.player_table.guis.settings.refs.window.destroy()
self.player_table.guis.settings = nil
end
function Gui:update_contents(tab)
local refs = self.refs
local state = self.state
local query = state.search_query
local translations = self.player_table.translations
local gui_translations = translations.gui
local actual_settings = self.player_table.settings
-- For simplicity's sake, since there's not _that much_ going on here, we will just destroy and recreate things
-- instead of updating them.
-- GENERAL
if not tab or tab == "general" then
local general_pane = refs.general.pane
general_pane.clear()
for category, settings in pairs(constants.general_settings) do
local actual_category_settings = actual_settings.general[category]
local children = {}
for setting_name, setting_ident in pairs(settings) do
local caption = gui_translations[setting_name] or setting_name
if string.find(string.lower(caption), query) then
local converted_setting_name = string.gsub(setting_name, "_", "-")
local tooltip = ""
if setting_ident.has_tooltip then
tooltip = { "gui.rb-" .. converted_setting_name .. "-description" }
caption = caption .. " [img=info]"
end
local enabled = true
if setting_ident.dependencies then
for _, dependency in pairs(setting_ident.dependencies) do
if actual_settings.general[dependency.category][dependency.name] ~= dependency.value then
enabled = false
break
end
end
end
if setting_ident.type == "bool" then
children[#children + 1] = {
type = "checkbox",
caption = caption,
tooltip = tooltip,
state = actual_category_settings[setting_name],
enabled = enabled,
actions = enabled and {
on_click = {
gui = "settings",
action = "change_general_setting",
type = setting_ident.type,
category = category,
name = setting_name,
},
} or nil,
}
elseif setting_ident.type == "enum" then
children[#children + 1] = {
type = "flow",
style_mods = { vertical_align = "center" },
{ type = "label", caption = caption, tooltip = tooltip },
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{
type = "drop-down",
items = table.map(setting_ident.options, function(option_name)
return { "gui.rb-" .. converted_setting_name .. "-" .. string.gsub(option_name, "_", "-") }
end),
selected_index = table.find(setting_ident.options, actual_category_settings[setting_name]),
enabled = enabled,
actions = enabled and {
on_selection_state_changed = {
gui = "settings",
action = "change_general_setting",
type = setting_ident.type,
category = category,
name = setting_name,
},
} or nil,
},
}
end
end
end
if #children > 0 then
gui.build(general_pane, {
{
type = "frame",
style = "bordered_frame",
direction = "vertical",
caption = gui_translations[category] or category,
children = children,
},
})
end
end
end
-- CATEGORIES
if not tab or tab == "categories" then
local categories_frame = refs.categories.frame
categories_frame.clear()
local selected_class = constants.category_classes[state.selected_category]
local class_settings = actual_settings.categories[selected_class]
local class_translations = translations[selected_class]
local children = {}
for category_name in pairs(database[selected_class]) do
local category_translation = class_translations[category_name] or category_name
if string.find(string.lower(category_translation), query) then
local img_type = constants.class_to_type[selected_class]
if img_type then
category_translation = "[img=" .. img_type .. "/" .. category_name .. "] " .. category_translation
end
children[#children + 1] = {
type = "checkbox",
caption = category_translation,
state = class_settings[category_name],
actions = {
on_checked_state_changed = {
gui = "settings",
action = "change_category_setting",
class = selected_class,
name = category_name,
},
},
}
end
end
if #children > 0 then
gui.build(categories_frame, children)
end
end
-- PAGES
if not tab or tab == "pages" then
local pages_pane = refs.pages.pane
pages_pane.clear()
local selected_page = constants.pages_arr[state.selected_page]
local page_settings = actual_settings.pages[selected_page]
local children = {}
for component_name, component_settings in pairs(page_settings) do
local component_children = {}
component_children[1] = {
type = "flow",
style_mods = { vertical_align = "center" },
{ type = "label", caption = gui_translations.default_state },
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{
type = "drop-down",
items = table.map(constants.component_states, function(option_name)
return { "gui.rb-" .. string.gsub(option_name, "_", "-") }
end),
selected_index = table.find(constants.component_states, component_settings.default_state),
actions = {
on_selection_state_changed = {
gui = "settings",
action = "change_default_state",
class = selected_page,
component = component_name,
},
},
},
}
if component_settings.max_rows then
component_children[#component_children + 1] = {
type = "flow",
style_mods = { vertical_align = "center" },
{ type = "label", caption = gui_translations.max_rows },
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{
type = "textfield",
style_mods = { width = 50, horizontal_align = "center" },
numeric = true,
lose_focus_on_confirm = true,
clear_and_focus_on_right_click = true,
text = tostring(component_settings.max_rows),
actions = {
on_confirmed = {
gui = "settings",
action = "change_max_rows",
class = selected_page,
component = component_name,
},
},
},
}
end
if component_settings.rows then
for row_name, row_state in pairs(component_settings.rows) do
component_children[#component_children + 1] = {
type = "checkbox",
caption = gui_translations[row_name],
state = row_state,
actions = {
on_checked_state_changed = {
gui = "settings",
action = "change_row_visible",
class = selected_page,
component = component_name,
row = row_name,
},
},
}
end
end
children[#children + 1] = {
type = "frame",
style = "bordered_frame",
style_mods = { minimal_width = 300, horizontally_stretchable = true },
direction = "vertical",
caption = gui_translations[component_name] or component_name,
children = component_children,
}
end
gui.build(pages_pane, children)
end
end
local index = {}
local function subpage_set(name, action, include_tooltip, include_bordered_frame, initial_items)
return {
tab = {
type = "tab",
style_mods = { padding = { 7, 10, 8, 10 } },
caption = { "", { "gui.rb-" .. name }, include_tooltip and " [img=info]" or nil },
tooltip = include_tooltip and { "gui.rb-" .. name .. "-description" } or nil,
},
content = {
type = "flow",
style_mods = { horizontal_spacing = 12, padding = { 8, 0, 12, 12 } },
{
type = "list-box",
style = "list_box_in_shallow_frame",
style_mods = { height = 28 * constants.settings_gui_rows, width = 150 },
items = initial_items,
selected_index = 1,
actions = {
on_selection_state_changed = { gui = "settings", action = action },
},
},
{
type = "frame",
style = "flib_shallow_frame_in_shallow_frame",
style_mods = { height = 28 * constants.settings_gui_rows },
{
type = "scroll-pane",
style = "flib_naked_scroll_pane",
style_mods = { padding = 4, vertically_stretchable = true },
vertical_scroll_policy = "always",
ref = { name, "pane" },
include_bordered_frame and {
type = "frame",
style = "bordered_frame",
style_mods = { minimal_width = 300, horizontally_stretchable = true, vertically_stretchable = true },
direction = "vertical",
ref = { name, "frame" },
} or nil,
},
},
},
}
end
--- @param player LuaPlayer
--- @param player_table PlayerTable
function index.build(player, player_table)
local gui_translations = player_table.translations.gui
local refs = gui.build(player.gui.screen, {
{
type = "frame",
name = "rb_settings_window",
direction = "vertical",
ref = { "window" },
actions = {
on_closed = { gui = "settings", action = "close" },
},
{
type = "flow",
style = "flib_titlebar_flow",
ref = { "titlebar", "flow" },
actions = {
on_click = { gui = "settings", action = "reset_location" },
},
{ type = "label", style = "frame_title", caption = { "gui.rb-settings" }, ignored_by_interaction = true },
{ type = "empty-widget", style = "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 = "settings", action = "update_search_query" },
},
},
util.frame_action_button(
"utility/search",
{ "gui.rb-search-instruction" },
{ "titlebar", "search_button" },
{ gui = "settings", action = "toggle_search" }
),
util.frame_action_button(
"utility/close",
{ "gui.close" },
{ "titlebar", "close_button" },
{ gui = "settings", action = "close" }
),
},
{
type = "frame",
style = "inside_deep_frame_for_tabs",
direction = "vertical",
{
type = "tabbed-pane",
style = "flib_tabbed_pane_with_no_padding",
{
tab = { type = "tab", caption = { "gui.rb-general" } },
content = {
type = "flow",
style_mods = { padding = 4 },
direction = "vertical",
ref = { "general", "pane" },
},
},
subpage_set(
"categories",
"change_category",
true,
true,
table.map(constants.category_classes, function(class)
return gui_translations[class] or class
end)
),
subpage_set(
"pages",
"change_page",
false,
false,
table.map(constants.pages_arr, function(class)
return gui_translations[class] or class
end)
),
},
},
},
})
refs.window.force_auto_center()
refs.titlebar.flow.drag_target = refs.window
player.opened = refs.window
--- @type SettingsGui
local self = {
player = player,
player_table = player_table,
refs = refs,
state = {
search_opened = false,
search_query = "",
selected_category = 1,
selected_page = 1,
},
}
index.load(self)
player_table.guis.settings = self
self:update_contents()
end
function index.load(self)
setmetatable(self, { __index = Gui })
end
return index

View File

@@ -0,0 +1,125 @@
local bounding_box = require("__flib__.bounding-box")
local gui = require("__flib__.gui")
local math = require("__flib__.math")
local table = require("__flib__.table")
local constants = require("constants")
local database = require("scripts.database")
local formatter = require("scripts.formatter")
local gui_util = {}
-- The calling GUI will navigate to the context that is returned, if any
-- Actions that do not open a page will not return a context
function gui_util.navigate_to(e)
local tags = gui.get_tags(e.element)
local context = tags.context
local modifiers = {}
for name, modifier in pairs({ control = e.control, shift = e.shift, alt = e.alt }) do
if modifier then
modifiers[#modifiers + 1] = name
end
end
for _, interaction in pairs(constants.interactions[context.class]) do
if table.deep_compare(interaction.modifiers, modifiers) then
local action = interaction.action
local context_data = database[context.class][context.name]
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
if action == "view_details" then
return context
elseif action == "view_product_details" and #context_data.products == 1 then
return context_data.products[1]
elseif action == "get_blueprint" then
local blueprint_result = tags.blueprint_result
if blueprint_result then
local cursor_stack = player.cursor_stack
player.clear_cursor()
if cursor_stack and cursor_stack.valid then
local collision_box = game.entity_prototypes[blueprint_result.name].collision_box
local height = bounding_box.height(collision_box)
local width = bounding_box.width(collision_box)
cursor_stack.set_stack({ name = "blueprint", count = 1 })
cursor_stack.set_blueprint_entities({
{
entity_number = 1,
name = blueprint_result.name,
position = {
-- Entities with an even number of tiles to a side need to be set at -0.5 instead of 0
math.ceil(width) % 2 == 0 and -0.5 or 0,
math.ceil(height) % 2 == 0 and -0.5 or 0,
},
recipe = blueprint_result.recipe,
},
})
player.add_to_clipboard(cursor_stack)
player.activate_paste()
end
else
player.create_local_flying_text({
text = { "message.rb-cannot-create-blueprint" },
create_at_cursor = true,
})
player.play_sound({ path = "utility/cannot_build" })
end
elseif action == "open_in_technology_window" then
local player_table = global.players[e.player_index]
player_table.flags.technology_gui_open = true
player.open_technology_gui(context.name)
elseif action == "view_source" then
local source = context_data[interaction.source]
if source then
return source
end
end
end
end
end
function gui_util.update_list_box(pane, source_tbl, player_data, iterator, options)
local i = 0
local children = pane.children
local add = pane.add
for _, obj_ident in iterator(source_tbl) do
local obj_data = database[obj_ident.class][obj_ident.name]
local info = formatter(obj_data, player_data, options)
if info then
i = i + 1
local style = info.researched and "rb_list_box_item" or "rb_unresearched_list_box_item"
local item = children[i]
if item then
item.style = style
item.caption = info.caption
item.tooltip = info.tooltip
item.enabled = info.num_interactions > 0
gui.update_tags(item, { context = { class = obj_ident.class, name = obj_ident.name } })
else
add({
type = "button",
style = style,
caption = info.caption,
tooltip = info.tooltip,
enabled = info.num_interactions > 0,
mouse_button_filter = { "left", "middle" },
tags = {
[script.mod_name] = {
context = { class = obj_ident.class, name = obj_ident.name },
flib = {
on_click = { gui = "search", action = "open_object" },
},
},
},
})
end
end
end
-- Destroy extraneous items
for j = i + 1, #children do
children[j].destroy()
end
end
return gui_util