428 lines
14 KiB
Lua

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