513 lines
16 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 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