Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion control.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Search = require "scripts.search"
SearchResults = require "scripts.search-results"
ResultLocation = require "scripts.result-location"
SearchGui = require "scripts.search-gui"
require "scripts.remote"
Interop = require "scripts.remote"

---@alias ItemName string
---@alias EntityName string
Expand Down Expand Up @@ -36,6 +36,7 @@ require "scripts.remote"
---@field searching_label LuaGuiElement label
---@field search_progressbar LuaGuiElement progressbar
---@field result_flow LuaGuiElement flow
---@field recent_items_flow LuaGuiElement flow
---@field highlighted_button? LuaGuiElement sprite-button

---@class (exact) PlayerData
Expand Down Expand Up @@ -237,15 +238,37 @@ local function generate_item_to_entity_table()
storage.item_to_entities = item_to_entities
end

local function setup_interop_callback()
Interop.set_on_external_item_callback(function(player_index, item_name, source)
Interop.add_recent_item(player_index, item_name, source)
local player = game.get_player(player_index)
if player then
local player_data = storage.players[player_index]
if player_data and player_data.refs.frame.valid and player_data.refs.frame.visible then
SearchGui.update_recent_panel(player_data)
end
end
end)
end

local function on_init()
---@type table<PlayerIndex, PlayerData>
storage.players = {}
---@type table<PlayerIndex, SearchData>
storage.current_searches = {}
---@type boolean
storage.multiple_surfaces = false
---@type table<PlayerIndex, {item_name: string, source: string}[]>
storage.recent_external_items = {}
update_surface_count()
generate_item_to_entity_table()
Interop.subscribe_to_events()
setup_interop_callback()
end

local function on_load()
Interop.subscribe_to_events()
setup_interop_callback()
end

local function on_configuration_changed()
Expand All @@ -263,11 +286,17 @@ local function on_configuration_changed()
storage.current_searches = {}

storage.multiple_surfaces = false
if not storage.recent_external_items then
storage.recent_external_items = {}
end
update_surface_count()
generate_item_to_entity_table()
Interop.subscribe_to_events()
setup_interop_callback()
end

Control.on_init = on_init
Control.on_load = on_load
Control.on_configuration_changed = on_configuration_changed
Control.events = {
[defines.events.on_surface_created] = update_surface_count,
Expand Down
102 changes: 92 additions & 10 deletions scripts/remote.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,92 @@
remote.add_interface("factory-search", {
---@param player LuaPlayer
---@param search_value SignalID
search = function(player, search_value)
SearchGui.open(player, storage.players[player.index])
local player_data = storage.players[player.index]
player_data.refs.item_select.elem_value = search_value
SearchGui.start_search(player, player_data)
end
})
local remote_interface = {}

local on_item_selected = script.generate_event_name()

local on_external_item_callback = nil

--- Open the search GUI for a player with the given item pre-selected.
---@param player LuaPlayer
---@param search_value SignalID
function remote_interface.search(player, search_value)
SearchGui.open(player, storage.players[player.index])
local player_data = storage.players[player.index]
player_data.refs.item_select.elem_value = search_value
SearchGui.start_search(player, player_data)
end

function remote_interface.get_on_item_selected()
return on_item_selected
end

function remote_interface.interop_version()
return 1
end

remote.add_interface("factory-search", remote_interface)

local function raise_item_selected(player_index, item_name)
script.raise_event(on_item_selected, {
player_index = player_index,
item_name = item_name,
})
end

local function subscribe_to_events()
for iface, functions in pairs(remote.interfaces) do
if iface == "factory-search" then goto continue end

if functions["get_on_item_selected"] then
local event_id = remote.call(iface, "get_on_item_selected")
if event_id then
local source_iface = iface
script.on_event(event_id, function(e)
local player = game.get_player(e.player_index)
if not player then return end
local item_name = e.item_name
if item_name and (prototypes.item[item_name] or prototypes.fluid[item_name]) then
if on_external_item_callback then
on_external_item_callback(e.player_index, item_name, source_iface)
end
end
end)
log("FactorySearch: subscribed to " .. iface .. ".get_on_item_selected")
end
end

::continue::
end
end

local function add_recent_item(player_index, item_name, source)
if not storage.recent_external_items then
storage.recent_external_items = {}
end
local list = storage.recent_external_items[player_index]
if not list then
list = {}
storage.recent_external_items[player_index] = list
end

-- Deduplicate: remove existing entry for this item
for i = #list, 1, -1 do
if list[i].item_name == item_name then
table.remove(list, i)
end
end

table.insert(list, 1, { item_name = item_name, source = source })
if #list > 5 then
list[6] = nil
end
end

local function set_on_external_item_callback(cb)
on_external_item_callback = cb
end

return {
raise_item_selected = raise_item_selected,
subscribe_to_events = subscribe_to_events,
add_recent_item = add_recent_item,
set_on_external_item_callback = set_on_external_item_callback,
}
68 changes: 68 additions & 0 deletions scripts/search-gui.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
local interop = require("scripts.remote")

local SearchGui = {}

---@param signal SignalID
Expand Down Expand Up @@ -390,6 +392,12 @@ function SearchGui.build(player)
ignored_by_interaction = true,
},
{type = "empty-widget", style = "fs_flib_titlebar_drag_handle", ignored_by_interaction = true},
{
type = "flow",
direction = "horizontal",
ref = {"recent_items_flow"},
style_mods = {horizontal_spacing = 2},
},
{
type = "sprite-button",
style = "frame_action_button",
Expand Down Expand Up @@ -668,6 +676,7 @@ function SearchGui.open(player, player_data)
refs.frame.visible = true
refs.frame.bring_to_front()
player.set_shortcut_toggled("search-factory", true)
SearchGui.update_recent_panel(player_data)
end

---@param player LuaPlayer
Expand Down Expand Up @@ -791,6 +800,10 @@ function SearchGui.start_search(player, player_data, _, _, immediate)
local elem_button = refs.item_select
local item = elem_button.elem_value --[[@as SignalID]]
if item then
-- Broadcast item/fluid selection to other mods (interop spec v1)
if item.name and (item.type == "item" or item.type == "fluid") then
interop.raise_item_selected(player.index, item.name)
end
local force = player.force --[[@as LuaForce]]
local state = generate_state(refs)
local state_valid = is_valid_state(state)
Expand Down Expand Up @@ -836,6 +849,61 @@ function SearchGui.sort_results_dropdown_changed(player, player_data)
player_data.sort_results_by = sort_results_by_options[dropdown.selected_index]
end

---@param player_data PlayerData
function SearchGui.update_recent_panel(player_data)
local flow = player_data.refs.recent_items_flow
if not flow or not flow.valid then return end

flow.clear()

local items = storage.recent_external_items and storage.recent_external_items[player_data.refs.frame.player_index]
if not items or #items == 0 then return end

for _, entry in ipairs(items) do
local sprite_path
if prototypes.item[entry.item_name] then
sprite_path = "item/" .. entry.item_name
elseif prototypes.fluid[entry.item_name] then
sprite_path = "fluid/" .. entry.item_name
end

if sprite_path then
local proto = prototypes.item[entry.item_name] or prototypes.fluid[entry.item_name]
local tooltip = proto and proto.localised_name or entry.item_name
gui.add(flow, {
{
type = "sprite-button",
sprite = sprite_path,
tooltip = tooltip,
style = "frame_action_button",
tags = { fs_recent_item_name = entry.item_name },
handler = {[defines.events.on_gui_click] = SearchGui.on_recent_item_clicked},
}
})
end
end
end

---@param player LuaPlayer
---@param player_data PlayerData
---@param element LuaGuiElement
function SearchGui.on_recent_item_clicked(player, player_data, element)
local item_name = element.tags.fs_recent_item_name
if not item_name then return end

local sig_type
if prototypes.item[item_name] then
sig_type = "item"
elseif prototypes.fluid[item_name] then
sig_type = "fluid"
else
return
end

player_data.refs.item_select.elem_value = { type = sig_type, name = item_name }
SearchGui.start_search(player, player_data)
end

gui.add_handlers(SearchGui,
function(event, handler)
local player = game.get_player(event.player_index) ---@cast player -?
Expand Down