merged gui

This commit is contained in:
monica
2023-01-07 12:04:08 -05:00
committed by Monica Moniot
parent 2e021267e4
commit 1821dc1ccc
18 changed files with 2308 additions and 1 deletions

View File

@@ -0,0 +1,84 @@
[controls]
cybersyn-toggle-gui=Toggle LTN Manager
[cybersyn-gui]
alert-delivery-failed=Delivery failed
alert-delivery-failed-description=The train took too long to perform its task and was abandoned by LTN, or the train was otherwise invalidated. You can configure how long LTN will wait for a train in the mod settings.\n\nThe planned shipment is listed in green.
alert-provider-missing-cargo-description=Not all of the intended cargo was loaded before the train left the provider.\n\nGreen indicates the intended shipment, red indicates the actual shipment.
alert-provider-missing-cargo=Provider - missing cargo
alert-provider-unscheduled-cargo-description=The train left the provider with unscheduled / unintended cargo.\n\nGreen indicates the planned shipment, red indicates the unscheduled load.
alert-provider-unscheduled-cargo=Provider - unscheduled cargo
alert-requester-remaining-cargo-description=Not all materials were unloaded from the train before it left the station, and it has returned to its depot with the leftover cargo.\n\nThe leftover items are listed in red.
alert-requester-remaining-cargo=Requester - remaining cargo
alert-requester-unscheduled-cargo-description=The train arrived at the requester with unscheduled / unintended cargo.\n\nGreen indicates the planned shipment, red indicates the unscheduled load.
alert-requester-unscheduled-cargo=Requester - unscheduled cargo
alerts=Alerts
all-paren=(All)
clear-history=Clear history
composition=Composition
composition-description=The train's layout.\nL = Locomotive\nC = Cargo wagon\nF = Fluid wagon\nA = Artillery wagon\n< > = Direction
control-signals=Control signals
delete-alert=Delete alert
delete-all-alerts=Delete all alerts
delivering-to=Delivering to
depot=Depot
depots=Depots
dispatcher-disabled-description=The LTN dispatcher is disabled; no new deliveries will be created. Re-enable the dispatcher via the map settings or by using the hotkey.
dispatcher-disabled=LTN dispatcher is disabled!
fetching-from=Fetching from
finished=Finished
history=History
in-transit=In transit
inventory=Inventory
keep-open=Keep open
loading-at=Loading at
name=Name
network-id-label=Network ID:
network-id=Network ID
no-alerts=[img=warning-white] No alerts
no-depots=[img=warning-white] No depots
no-history=[img=warning-white] No history
no-stations=[img=warning-white] No stations
not-available=Not available
no-trains=[img=warning-white] No trains
open-ltn-combinator-gui=[font=default-semibold][color=128,206,240]Control:[/color][/font] Open LTN Combinator GUI
open-station-gui=Open station GUI\n[font=default-semibold][color=128,206,240]Shift:[/color][/font] Open station on map
open-train-gui=Open train GUI
parked-at-depot=Parked at depot
parked-at-depot-with-residue=Parked at depot with residue
provided=Provided
provided-requested-description=Green = provided\nRed = requested
provided-requested=Provided / requested
refresh-tooltip=Refresh\n[font=default-semibold][color=128,206,240]Shift:[/color][/font] Toggle auto-refresh
requested=Requested
returning-to-depot=Returning to depot
route=Route
runtime=Runtime
search-label=Search:
shipments-description=Green = Inbound\nBlue = Outbound
shipment=Shipment
shipments=Shipments
stations=Stations
status-description=[img=flib_indicator_green]1 = normal status\n[img=flib_indicator_blue]n = LTN Controlled Train parked at stop, n = number of trains\n[img=flib_indicator_yellow]n = Stop is part of a scheduled delivery, n = number of trains\n[img=flib_indicator_white]1 = Error - not initialized\n[img=flib_indicator_red]1 = Error - short circuit\n[img=flib_indicator_red]2 = Error - deactivated stop
status=Status
surface-label=Surface:
time=Time
train-id=Train ID
trains=Trains
type=Type
unloading-at=Unloading at
[cybersyn-message]
error-ltn-combinator-not-found=Could not find an LTN Combinator for this station.
error-station-is-invalid=Station is invalid, please refresh the GUI.
error-train-is-invalid=Train is invalid, please refresh the GUI.
[cybersyn-mod-setting-description]
iterations-per-tick=Decrease this number if you're having performance issues.
[cybersyn-mod-setting-name]
history-length=History length
iterations-per-tick=Iterations per tick [img=info]
[shortcut-name]
cybersyn-toggle-gui=Toggle LTN Manager

View File

@@ -0,0 +1,329 @@
local constants = require("constants")
local util = require("prototypes.util")
local styles = data.raw["gui-style"]["default"]
-- local depot_button_height = 89
-- BUTTON STYLES
-- smaller flib slot buttons
for _, color in ipairs({ "default", "red", "green", "blue" }) do
styles["ltnm_small_slot_button_" .. color] = {
type = "button_style",
parent = "flib_slot_button_" .. color,
size = 36,
}
styles["ltnm_selected_small_slot_button_" .. color] = {
type = "button_style",
parent = "flib_selected_slot_button_" .. color,
size = 36,
}
end
styles.ltnm_train_minimap_button = {
type = "button_style",
parent = "button",
size = 90,
default_graphical_set = {},
hovered_graphical_set = {
base = { position = { 81, 80 }, size = 1, opacity = 0.7 },
},
clicked_graphical_set = { position = { 70, 146 }, size = 1, opacity = 0.7 },
}
-- CHECKBOX STYLES
-- inactive is grey until hovered
-- checked = ascending, unchecked = descending
styles.ltnm_sort_checkbox = {
type = "checkbox_style",
font = "default-bold",
-- font_color = bold_font_color,
padding = 0,
default_graphical_set = {
filename = "__core__/graphics/arrows/table-header-sort-arrow-down-white.png",
size = { 16, 16 },
scale = 0.5,
},
hovered_graphical_set = {
filename = "__core__/graphics/arrows/table-header-sort-arrow-down-hover.png",
size = { 16, 16 },
scale = 0.5,
},
clicked_graphical_set = {
filename = "__core__/graphics/arrows/table-header-sort-arrow-down-white.png",
size = { 16, 16 },
scale = 0.5,
},
disabled_graphical_set = {
filename = "__core__/graphics/arrows/table-header-sort-arrow-down-white.png",
size = { 16, 16 },
scale = 0.5,
},
selected_graphical_set = {
filename = "__core__/graphics/arrows/table-header-sort-arrow-up-white.png",
size = { 16, 16 },
scale = 0.5,
},
selected_hovered_graphical_set = {
filename = "__core__/graphics/arrows/table-header-sort-arrow-up-hover.png",
size = { 16, 16 },
scale = 0.5,
},
selected_clicked_graphical_set = {
filename = "__core__/graphics/arrows/table-header-sort-arrow-up-white.png",
size = { 16, 16 },
scale = 0.5,
},
selected_disabled_graphical_set = {
filename = "__core__/graphics/arrows/table-header-sort-arrow-up-white.png",
size = { 16, 16 },
scale = 0.5,
},
checkmark = util.empty_checkmark,
disabled_checkmark = util.empty_checkmark,
text_padding = 5,
}
-- selected is orange by default
styles.ltnm_selected_sort_checkbox = {
type = "checkbox_style",
parent = "ltnm_sort_checkbox",
-- font_color = bold_font_color,
default_graphical_set = {
filename = "__core__/graphics/arrows/table-header-sort-arrow-down-active.png",
size = { 16, 16 },
scale = 0.5,
},
selected_graphical_set = {
filename = "__core__/graphics/arrows/table-header-sort-arrow-up-active.png",
size = { 16, 16 },
scale = 0.5,
},
}
-- FLOW STYLES
styles.ltnm_warning_flow = {
type = "horizontal_flow_style",
padding = 12,
horizontal_align = "center",
vertical_align = "center",
vertical_spacing = 8,
horizontally_stretchable = "on",
vertically_stretchable = "on",
}
-- FRAME STYLES
styles.ltnm_main_content_frame = {
type = "frame_style",
parent = "deep_frame_in_shallow_frame",
height = constants.gui_content_frame_height,
}
styles.ltnm_main_toolbar_frame = {
type = "frame_style",
parent = "subheader_frame",
bottom_margin = 12,
horizontal_flow_style = {
type = "horizontal_flow_style",
horizontal_spacing = 12,
vertical_align = "center",
},
}
styles.ltnm_small_slot_table_frame_light = {
type = "frame_style",
parent = "ltnm_table_inset_frame_light",
minimal_height = 36,
background_graphical_set = {
base = {
position = { 282, 17 },
corner_size = 8,
overall_tiling_horizontal_padding = 4,
overall_tiling_horizontal_size = 28,
overall_tiling_horizontal_spacing = 8,
overall_tiling_vertical_padding = 4,
overall_tiling_vertical_size = 28,
overall_tiling_vertical_spacing = 8,
},
},
}
styles.ltnm_small_slot_table_frame_dark = {
type = "frame_style",
parent = "ltnm_table_inset_frame_dark",
minimal_height = 36,
background_graphical_set = {
base = {
position = { 282, 17 },
corner_size = 8,
overall_tiling_horizontal_padding = 4,
overall_tiling_horizontal_size = 28,
overall_tiling_horizontal_spacing = 8,
overall_tiling_vertical_padding = 4,
overall_tiling_vertical_size = 28,
overall_tiling_vertical_spacing = 8,
},
},
}
styles.ltnm_table_inset_frame_light = {
type = "frame_style",
parent = "deep_frame_in_shallow_frame",
}
styles.ltnm_table_inset_frame_dark = {
type = "frame_style",
parent = "deep_frame_in_shallow_frame",
graphical_set = {
base = {
position = { 51, 0 },
corner_size = 8,
center = { position = { 42, 8 }, size = { 1, 1 } },
draw_type = "outer",
},
shadow = default_inner_shadow,
},
}
styles.ltnm_table_row_frame_light = {
type = "frame_style",
parent = "statistics_table_item_frame",
top_padding = 8,
bottom_padding = 8,
left_padding = 8,
right_padding = 8,
minimal_height = 52,
horizontal_flow_style = {
type = "horizontal_flow_style",
vertical_align = "center",
horizontal_spacing = 10,
horizontally_stretchable = "on",
},
graphical_set = {
base = {
center = { position = { 76, 8 }, size = { 1, 1 } },
-- bottom = {position = {8, 40}, size = {1, 8}},
},
},
}
styles.ltnm_table_row_frame_dark = {
type = "frame_style",
parent = "ltnm_table_row_frame_light",
-- graphical_set = {
-- base = {bottom = {position = {8, 40}, size = {1, 8}}},
-- },
graphical_set = {},
}
styles.ltnm_table_toolbar_frame = {
type = "frame_style",
parent = "subheader_frame",
left_padding = 9,
right_padding = 7 + 12, -- For scrollbar
horizontally_stretchable = "on", -- FIXME: This causes the GUI to jump when the scrollbar appears
horizontal_flow_style = {
type = "horizontal_flow_style",
horizontal_spacing = 10,
vertical_align = "center",
},
}
styles.ltnm_main_warning_frame = {
type = "frame_style",
parent = "deep_frame_in_shallow_frame",
height = constants.gui_content_frame_height,
graphical_set = {
base = {
position = { 85, 0 },
corner_size = 8,
center = { position = { 411, 25 }, size = { 1, 1 } },
draw_type = "outer",
},
shadow = default_inner_shadow,
},
}
-- LABEL STYLES
local hovered_label_color = {
r = 0.5 * (1 + default_orange_color.r),
g = 0.5 * (1 + default_orange_color.g),
b = 0.5 * (1 + default_orange_color.b),
}
styles.ltnm_clickable_semibold_label = {
type = "label_style",
parent = "ltnm_semibold_label",
hovered_font_color = hovered_label_color,
disabled_font_color = hovered_label_color,
}
styles.ltnm_minimap_label = {
type = "label_style",
font = "default-game",
font_color = default_font_color,
size = 90,
vertical_align = "bottom",
horizontal_align = "right",
right_padding = 4,
}
styles.ltnm_semibold_label = {
type = "label_style",
font = "default-semibold",
}
-- MINIMAP STYLES
styles.ltnm_train_minimap = {
type = "minimap_style",
size = 90,
}
-- SCROLL PANE STYLES
styles.ltnm_table_scroll_pane = {
type = "scroll_pane_style",
parent = "flib_naked_scroll_pane_no_padding",
vertical_flow_style = {
type = "vertical_flow_style",
vertical_spacing = 0,
},
}
styles.ltnm_slot_table_scroll_pane = {
type = "scroll_pane_style",
parent = "flib_naked_scroll_pane_no_padding",
horizontally_squashable = "off",
background_graphical_set = {
base = {
position = { 282, 17 },
corner_size = 8,
overall_tiling_horizontal_padding = 4,
overall_tiling_horizontal_size = 32,
overall_tiling_horizontal_spacing = 8,
overall_tiling_vertical_padding = 4,
overall_tiling_vertical_size = 32,
overall_tiling_vertical_spacing = 8,
},
},
}
-- TABBED PANE STYLES
styles.ltnm_tabbed_pane = {
type = "tabbed_pane_style",
tab_content_frame = {
type = "frame_style",
parent = "tabbed_pane_frame",
left_padding = 12,
right_padding = 12,
bottom_padding = 8,
},
}

View File

@@ -0,0 +1,23 @@
local util = {}
local data_util = require("__flib__.data-util")
for key, value in pairs(require("__core__.lualib.util")) do
util[key] = value
end
util.paths = {
nav_icons = "__LtnManager__/graphics/gui/frame-action-icons.png",
shortcut_icons = "__LtnManager__/graphics/shortcut/ltn-manager-shortcut.png",
}
util.empty_checkmark = {
filename = data_util.empty_image,
priority = "very-low",
width = 1,
height = 1,
frame_count = 1,
scale = 8,
}
return util

View File

@@ -190,9 +190,9 @@ function register_gui_actions()
["setting"] = handle_setting, ["setting"] = handle_setting,
["setting_flip"] = handle_setting_flip, ["setting_flip"] = handle_setting_flip,
}) })
flib_gui.handle_events()
script.on_event(defines.events.on_gui_opened, on_gui_opened) script.on_event(defines.events.on_gui_opened, on_gui_opened)
script.on_event(defines.events.on_gui_closed, on_gui_closed) script.on_event(defines.events.on_gui_closed, on_gui_closed)
flib_gui.handle_events()
end end
---@param comb LuaEntity ---@param comb LuaEntity

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 raiguard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,195 @@
local train_util = require("__flib__.train")
local constants = require("scripts.gui.constants")
local util = require("scripts.gui.util")
local actions = {}
local function toggle_fab(elem, sprite, state)
if state then
elem.style = "flib_selected_frame_action_button"
elem.sprite = sprite .. "_black"
else
elem.style = "frame_action_button"
elem.sprite = sprite .. "_white"
end
end
function actions.close(Gui)
Gui:close()
end
function actions.recenter(Gui)
Gui.refs.window.force_auto_center()
end
function actions.toggle_auto_refresh(Gui)
Gui.state.auto_refresh = not Gui.state.auto_refresh
toggle_fab(Gui.refs.titlebar.refresh_button, "ltnm_refresh", Gui.state.auto_refresh)
end
function actions.toggle_pinned(Gui)
Gui.state.pinned = not Gui.state.pinned
toggle_fab(Gui.refs.titlebar.pin_button, "ltnm_pin", Gui.state.pinned)
if Gui.state.pinned then
Gui.state.pinning = true
Gui.player.opened = nil
Gui.state.pinning = false
else
Gui.player.opened = Gui.refs.window
Gui.refs.window.force_auto_center()
end
end
function actions.update_text_search_query(Gui, _, e)
local query = e.text
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
Gui.state.search_query = query
if Gui.state.search_job then
on_tick_n.remove(Gui.state.search_job)
end
if #query == 0 then
Gui:schedule_update()
else
Gui.state.search_job = on_tick_n.add(
game.tick + 30,
{ gui = "main", action = "update", player_index = Gui.player.index }
)
end
end
function actions.update_network_id_query(Gui)
Gui.state.network_id = tonumber(Gui.refs.toolbar.network_id_field.text) or -1
Gui:schedule_update()
end
function actions.open_train_gui(Gui, msg)
local train_id = msg.train_id
local train_data = Gui.state.ltn_data.trains[train_id]
if not train_data or not train_data.train.valid then
util.error_flying_text(Gui.player, { "message.ltnm-error-train-is-invalid" })
return
end
train_util.open_gui(Gui.player.index, train_data.train)
end
function actions.open_station_gui(Gui, msg, e)
local station_id = msg.station_id
local station_data = Gui.state.ltn_data.stations[station_id]
if not station_data or not station_data.entity.valid then
util.error_flying_text(Gui.player, { "message.ltnm-error-station-is-invalid" })
return
end
--- @type LuaPlayer
local player = Gui.player
if e.shift then
player.zoom_to_world(station_data.entity.position, 1)
rendering.draw_circle({
color = constants.colors.red.tbl,
target = station_data.entity.position,
surface = station_data.entity.surface,
radius = 0.5,
filled = false,
width = 5,
time_to_live = 60 * 3,
players = { player },
})
if not Gui.state.pinned then
Gui:close()
end
elseif e.control and remote.interfaces["ltn-combinator"] then
if not remote.call("ltn-combinator", "open_ltn_combinator", e.player_index, station_data.lamp_control, true) then
util.error_flying_text(player, { "message.ltnm-error-ltn-combinator-not-found" })
end
else
player.opened = station_data.entity
end
end
function actions.toggle_sort(Gui, msg, e)
local tab = msg.tab
local column = msg.column
local sorts = Gui.state.sorts[tab]
local active_column = sorts._active
if active_column == column then
sorts[column] = e.element.state
else
sorts._active = column
e.element.state = sorts[column]
local widths = Gui.widths[tab]
local old_checkbox = Gui.refs[tab].toolbar[active_column .. "_checkbox"]
old_checkbox.style = "ltnm_sort_checkbox"
if widths[active_column .. "_checkbox_stretchy"] then
old_checkbox.style.horizontally_stretchable = true
else
old_checkbox.style.width = widths[active_column]
end
e.element.style = "ltnm_selected_sort_checkbox"
if widths[column .. "_checkbox_stretchy"] then
e.element.style.horizontally_stretchable = true
else
e.element.style.width = widths[column]
end
end
Gui:schedule_update()
end
function actions.update(Gui)
Gui:schedule_update()
end
function actions.change_tab(Gui, msg)
Gui.state.active_tab = msg.tab
Gui:schedule_update()
end
function actions.change_surface(Gui, _, e)
local selected_index = e.element.selected_index
local selected_surface_index = Gui.state.ltn_data.surfaces.selected_to_index[selected_index]
if selected_surface_index then
Gui.state.surface = selected_surface_index
Gui:schedule_update()
end
end
function actions.clear_history(Gui)
global.flags.deleted_history = true
Gui:schedule_update()
end
function actions.delete_alert(Gui, msg)
global.active_data.alerts_to_delete[msg.alert_id] = true
Gui:schedule_update()
end
function actions.delete_all_alerts(Gui)
global.flags.deleted_all_alerts = true
Gui:schedule_update()
end
function actions.focus_search(Gui)
if not Gui.pinned then
Gui.refs.toolbar.text_search_field.select_all()
Gui.refs.toolbar.text_search_field.focus()
end
end
return actions

View File

@@ -0,0 +1,227 @@
local gui = require("__flib__.gui")
local misc = require("__flib__.misc")
local constants = require("constants")
local util = require("scripts.util")
local templates = require("templates")
local alerts_tab = {}
function alerts_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-alerts" },
ref = { "alerts", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "alerts" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "alerts", "content_frame" },
{
type = "frame",
style = "ltnm_table_toolbar_frame",
style_mods = { right_padding = 4 },
templates.sort_checkbox(widths, "alerts", "time", true, nil, true),
templates.sort_checkbox(widths, "alerts", "train_id", false),
templates.sort_checkbox(widths, "alerts", "route", false),
templates.sort_checkbox(widths, "alerts", "network_id", false),
templates.sort_checkbox(nil, "alerts", "type", false),
{
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.ltnm-delete-all-alerts" },
ref = { "alerts", "delete_all_button" },
actions = {
on_click = { gui = "main", action = "delete_all_alerts" },
},
},
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "alerts", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "alerts", "warning_flow" },
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-alerts" },
ref = { "alerts", "warning_label" },
},
},
},
}
end
function alerts_tab.update(self)
local dictionaries = self.player_table.dictionaries
local state = self.state
local refs = self.refs.alerts
local widths = self.widths
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local ltn_alerts = state.ltn_data.alerts
local alerts_to_delete = global.active_data.alerts_to_delete
local scroll_pane = refs.scroll_pane
local children = scroll_pane.children
local sorts = state.sorts[state.active_tab]
local active_sort = sorts._active
local sorted_alerts = state.ltn_data.sorted_alerts[active_sort]
local table_index = 0
-- False = ascending (arrow down), True = descending (arrow up)
local start, finish, step
if sorts[active_sort] then
start = #sorted_alerts
finish = 1
step = -1
else
start = 1
finish = #sorted_alerts
step = 1
end
if not global.flags.deleted_all_alerts then
for sorted_index = start, finish, step do
local alert_id = sorted_alerts[sorted_index]
local alerts_entry = ltn_alerts[alert_id]
if
(search_surface == -1 or (alerts_entry.train.surface_index == search_surface))
and bit32.btest(alerts_entry.train.network_id, search_network_id)
and (#search_query == 0 or string.find(
alerts_entry.search_strings[self.player.index] or "",
string.lower(search_query)
))
and not alerts_to_delete[alert_id]
then
table_index = table_index + 1
local row = children[table_index]
local color = table_index % 2 == 0 and "dark" or "light"
if not row then
row = gui.add(scroll_pane, {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
{ type = "label", style_mods = { width = widths.alerts.time } },
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.alerts.train_id, horizontal_align = "center" },
tooltip = { "gui.ltnm-open-train-gui" },
},
{
type = "flow",
style_mods = { vertical_spacing = 0 },
direction = "vertical",
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.alerts.route },
tooltip = constants.open_station_gui_tooltip,
},
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.alerts.route },
tooltip = constants.open_station_gui_tooltip,
},
},
{ type = "label", style_mods = { width = widths.alerts.network_id, horizontal_align = "center" } },
{ type = "label", style_mods = { width = widths.alerts.type } },
{
type = "frame",
name = "contents_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths.alerts.contents },
{ type = "table", name = "contents_table", style = "slot_table", column_count = 4 },
},
{
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.ltnm-delete-alert" },
},
})
end
gui.update(row, {
{ elem_mods = { caption = misc.ticks_to_timestring(alerts_entry.time) } },
{
elem_mods = { caption = alerts_entry.train_id },
actions = {
on_click = { gui = "main", action = "open_train_gui", train_id = alerts_entry.train_id },
},
},
{
{
elem_mods = { caption = alerts_entry.train.from },
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = alerts_entry.train.from_id },
},
},
{
elem_mods = {
caption = "[color=" .. constants.colors.caption.str .. "]->[/color] " .. alerts_entry.train.to,
},
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = alerts_entry.train.to_id },
},
},
},
{ elem_mods = { caption = util.signed_int32(alerts_entry.train.network_id) } },
{
elem_mods = {
caption = { "gui.ltnm-alert-" .. string.gsub(alerts_entry.type, "_", "-") },
tooltip = { "gui.ltnm-alert-" .. string.gsub(alerts_entry.type, "_", "-") .. "-description" },
},
},
{},
{
actions = {
on_click = { gui = "main", action = "delete_alert", alert_id = alert_id },
},
},
})
util.slot_table_update(row.contents_frame.contents_table, {
{ color = "green", entries = alerts_entry.planned_shipment or {}, translations = dictionaries.materials },
{ color = "red", entries = alerts_entry.actual_shipment or {}, translations = dictionaries.materials },
{ color = "red", entries = alerts_entry.unscheduled_load or {}, translations = dictionaries.materials },
{ color = "red", entries = alerts_entry.remaining_load or {}, translations = dictionaries.materials },
})
end
end
end
for child_index = table_index + 1, #children do
children[child_index].destroy()
end
if table_index == 0 then
refs.warning_flow.visible = true
scroll_pane.visible = false
refs.content_frame.style = "ltnm_main_warning_frame"
refs.delete_all_button.enabled = false
else
refs.warning_flow.visible = false
scroll_pane.visible = true
refs.content_frame.style = "ltnm_main_content_frame"
refs.delete_all_button.enabled = true
end
end
return alerts_tab

View File

@@ -0,0 +1,152 @@
local constants = {}
constants.colors = {
caption = {
str = "255, 230, 192",
tbl = { 255, 230, 192 },
},
green = {
str = "69, 255, 69",
tbl = { 69, 255, 69 },
},
info = {
str = "128, 206, 240",
tbl = { 128, 206, 240 },
},
red = {
str = "255, 69, 69",
tbl = { 255, 69, 69 },
},
station_circle = {
str = "255, 50, 50, 190",
tbl = { 255, 50, 50, 190 },
},
yellow = {
str = "255, 240, 69",
tbl = { 255, 240, 69 },
},
white = {
str = "255, 255, 255",
tbl = { 255, 255, 255 },
},
}
-- dictionary locale identifier -> dictionary of hardcoded GUI sizes
constants.gui = {
en = {
trains = {
train_id = 90,
status = 378,
composition = 200,
depot = 149,
shipment = 36 * 6,
shipment_columns = 6,
},
stations = {
name = 238,
status = 53,
network_id = 84,
provided_requested = 36 * 6,
provided_requested_columns = 6,
shipments = 36 * 5,
shipments_columns = 5,
control_signals = 36 * 7,
control_signals_columns = 7,
},
depots = {
name = 200,
network_id = 84,
status = 200,
trains = 200,
},
history = {
train_id = 60,
route = 357,
depot = 160,
network_id = 84,
runtime = 68,
finished = 68,
shipment = (36 * 6),
shipment_checkbox_stretchy = true,
},
alerts = {
time = 68,
train_id = 60,
route = 326,
network_id = 84,
type = 230,
type_checkbox_stretchy = true,
contents = 36 * 6,
},
},
}
constants.gui_content_frame_height = 744
constants.gui_inventory_table_height = 40 * 18
constants.gui_translations = {
delivering_to = { "gui.ltnm-delivering-to" },
fetching_from = { "gui.ltnm-fetching-from" },
loading_at = { "gui.ltnm-loading-at" },
not_available = { "gui.ltnm-not-available" },
parked_at_depot_with_residue = { "gui.ltnm-parked-at-depot-with-residue" },
parked_at_depot = { "gui.ltnm-parked-at-depot" },
returning_to_depot = { "gui.ltnm-returning-to-depot" },
unloading_at = { "gui.ltnm-unloading-at" },
}
constants.input_sanitizers = {
["%%"] = "%%%%",
["%("] = "%%(",
["%)"] = "%%)",
["%.^[%*]"] = "%%.",
["%+"] = "%%+",
["%-"] = "%%-",
["^[%.]%*"] = "%%*",
["%?"] = "%%?",
["%["] = "%%[",
["%]"] = "%%]",
["%^"] = "%%^",
["%$"] = "%%$",
}
constants.ltn_control_signals = {
["ltn-depot"] = true,
["ltn-depot-priority"] = true,
-- excluded because it's shown as a separate column
-- ["ltn-network-id"] = true,
["ltn-min-train-length"] = true,
["ltn-max-train-length"] = true,
["ltn-max-trains"] = true,
["ltn-provider-threshold"] = true,
["ltn-provider-stack-threshold"] = true,
["ltn-provider-priority"] = true,
["ltn-locked-slots"] = true,
["ltn-requester-threshold"] = true,
["ltn-requester-stack-threshold"] = true,
["ltn-requester-priority"] = true,
["ltn-disable-warnings"] = true,
}
constants.ltn_event_names = {
on_stops_updated = true,
on_dispatcher_updated = true,
-- on_delivery_pickup_complete = true,
on_delivery_completed = true,
on_delivery_failed = true,
-- on_dispatcher_no_train_found = true,
on_provider_missing_cargo = true,
on_provider_unscheduled_cargo = true,
on_requester_remaining_cargo = true,
on_requester_unscheduled_cargo = true,
}
if script then
constants.open_station_gui_tooltip = {
"",
{ "gui.ltnm-open-station-gui" },
remote.interfaces["ltn-combinator"] and { "", "\n", { "gui.ltnm-open-ltn-combinator-gui" } } or nil,
}
end
return constants

View File

@@ -0,0 +1,144 @@
local gui = require("__flib__.gui")
local templates = require("templates")
local depots_tab = {}
function depots_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-depots" },
ref = { "depots", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "depots" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "depots", "content_frame" },
{
type = "frame",
style = "ltnm_table_toolbar_frame",
style_mods = { right_padding = 4 },
templates.sort_checkbox(widths, "depots", "name", true, nil, true),
templates.sort_checkbox(widths, "depots", "network_id", false),
templates.sort_checkbox(widths, "depots", "status", false, { "gui.ltnm-status-description" }),
templates.sort_checkbox(widths, "depots", "trains", false),
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "depots", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "depots", "warning_flow" },
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-depots" },
ref = { "depots", "warning_label" },
},
},
},
}
end
function depots_tab.update(self)
local state = self.state
local refs = self.refs.depots
local widths = self.widths.depots
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local ltn_depots = state.ltn_data.depots
local scroll_pane = refs.scroll_pane
local children = scroll_pane.children
local sorts = state.sorts.depots
local active_sort = sorts._active
local sorted_depots = state.ltn_data.sorted_depots[active_sort]
local table_index = 0
-- False = ascending (arrow down), True = descending (arrow up)
local start, finish, step
if sorts[active_sort] then
start = #sorted_depots
finish = 1
step = -1
else
start = 1
finish = #sorted_depots
step = 1
end
for sorted_index = start, finish, step do
local depot_name = sorted_depots[sorted_index]
local depot_data = ltn_depots[depot_name]
if
(search_surface == -1 or depot_data.surfaces[search_surface])
and bit32.btest(depot_data.network_id, search_network_id)
and (#search_query == 0 or string.find(depot_data.search_string, string.lower(search_query)))
then
table_index = table_index + 1
local row = children[table_index]
local color = table_index % 2 == 0 and "dark" or "light"
if not row then
row = gui.add(scroll_pane, {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
{ type = "label", style_mods = { width = widths.name } },
{ type = "label", style_mods = { width = widths.network_id, horizontal_align = "center" } },
{ type = "flow", name = "statuses_flow", style_mods = { width = widths.status } },
{ type = "label", style_mods = { width = widths.trains } },
})
end
gui.update(row, {
{ elem_mods = { caption = depot_name } },
{ elem_mods = { caption = depot_data.network_id } },
{},
{ elem_mods = { caption = depot_data.trains_string } },
})
local statuses_flow = row.statuses_flow
local statuses_children = statuses_flow.children
local status_index = 0
for color, count in pairs(depot_data.statuses) do
status_index = status_index + 1
local status_flow = statuses_children[status_index]
if not status_flow then
status_flow = gui.add(statuses_flow, templates.status_indicator())
end
gui.update(status_flow, {
{ elem_mods = { sprite = "flib_indicator_" .. color } },
{ elem_mods = { caption = count } },
})
end
for child_index = status_index + 1, #statuses_children do
statuses_children[child_index].destroy()
end
end
end
for child_index = table_index + 1, #children do
children[child_index].destroy()
end
if table_index == 0 then
refs.warning_flow.visible = true
scroll_pane.visible = false
refs.content_frame.style = "ltnm_main_warning_frame"
else
refs.warning_flow.visible = false
scroll_pane.visible = true
refs.content_frame.style = "ltnm_main_content_frame"
end
end
return depots_tab

View File

@@ -0,0 +1,208 @@
local gui = require("__flib__.gui")
local misc = require("__flib__.misc")
local constants = require("constants")
local util = require("scripts.util")
local templates = require("templates")
local history_tab = {}
function history_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-history" },
ref = { "history", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "history" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "history", "content_frame" },
{
type = "frame",
style = "ltnm_table_toolbar_frame",
style_mods = { right_padding = 4 },
templates.sort_checkbox(widths, "history", "train_id", false),
templates.sort_checkbox(widths, "history", "route", false),
templates.sort_checkbox(widths, "history", "depot", false),
templates.sort_checkbox(widths, "history", "network_id", false),
templates.sort_checkbox(widths, "history", "runtime", false),
templates.sort_checkbox(widths, "history", "finished", true, nil, true),
templates.sort_checkbox(nil, "history", "shipment", false),
{
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.ltnm-clear-history" },
ref = { "history", "clear_button" },
actions = {
on_click = { gui = "main", action = "clear_history" },
},
},
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "history", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "history", "warning_flow" },
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-history" },
ref = { "history", "warning_label" },
},
},
},
}
end
function history_tab.update(self)
local dictionaries = self.player_table.dictionaries
local state = self.state
local refs = self.refs.history
local widths = self.widths
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local ltn_history = state.ltn_data.history
local scroll_pane = refs.scroll_pane
local children = scroll_pane.children
local sorts = state.sorts[state.active_tab]
local active_sort = sorts._active
local sorted_history = state.ltn_data.sorted_history[active_sort]
local table_index = 0
-- False = ascending (arrow down), True = descending (arrow up)
local start, finish, step
if sorts[active_sort] then
start = #sorted_history
finish = 1
step = -1
else
start = 1
finish = #sorted_history
step = 1
end
if not global.flags.deleted_history then
for sorted_index = start, finish, step do
local history_id = sorted_history[sorted_index]
local history_entry = ltn_history[history_id]
if
(search_surface == -1 or (history_entry.surface_index == search_surface))
and bit32.btest(history_entry.network_id, search_network_id)
and (
#search_query == 0 or string.find(history_entry.search_strings[self.player.index], string.lower(search_query))
)
then
table_index = table_index + 1
local row = children[table_index]
local color = table_index % 2 == 0 and "dark" or "light"
if not row then
row = gui.add(scroll_pane, {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.history.train_id, horizontal_align = "center" },
tooltip = constants.open_station_gui_tooltip,
},
{
type = "flow",
style_mods = { vertical_spacing = 0 },
direction = "vertical",
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.history.route },
tooltip = constants.open_station_gui_tooltip,
},
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.history.route },
tooltip = constants.open_station_gui_tooltip,
},
},
{ type = "label", style_mods = { width = widths.history.depot } },
{ type = "label", style_mods = { width = widths.history.network_id, horizontal_align = "center" } },
{ type = "label", style_mods = { width = widths.history.finished, horizontal_align = "center" } },
{ type = "label", style_mods = { width = widths.history.runtime, horizontal_align = "center" } },
{
type = "frame",
name = "shipment_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths.history.shipment },
{ type = "table", name = "shipment_table", style = "slot_table", column_count = 4 },
},
})
end
gui.update(row, {
{
elem_mods = { caption = history_entry.train_id },
actions = {
on_click = { gui = "main", action = "open_train_gui", train_id = history_entry.train_id },
},
},
{
{
elem_mods = { caption = history_entry.from },
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = history_entry.from_id },
},
},
{
elem_mods = {
caption = "[color=" .. constants.colors.caption.str .. "]->[/color] " .. history_entry.to,
},
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = history_entry.to_id },
},
},
},
{ elem_mods = { caption = history_entry.depot } },
{ elem_mods = { caption = util.signed_int32(history_entry.network_id) } },
{ elem_mods = { caption = misc.ticks_to_timestring(history_entry.runtime) } },
{ elem_mods = { caption = misc.ticks_to_timestring(history_entry.finished) } },
})
util.slot_table_update(
row.shipment_frame.shipment_table,
{ { color = "default", entries = history_entry.shipment, translations = dictionaries.materials } }
)
end
end
end
for child_index = table_index + 1, #children do
children[child_index].destroy()
end
if table_index == 0 then
refs.warning_flow.visible = true
scroll_pane.visible = false
refs.content_frame.style = "ltnm_main_warning_frame"
refs.clear_button.enabled = false
else
refs.warning_flow.visible = false
scroll_pane.visible = true
refs.content_frame.style = "ltnm_main_content_frame"
refs.clear_button.enabled = true
end
end
return history_tab

View File

@@ -0,0 +1,87 @@
local misc = require("__flib__.misc")
local templates = require("templates")
local inventory_tab = {}
function inventory_tab.build()
return {
tab = {
type = "tab",
caption = { "gui.ltnm-inventory" },
ref = { "inventory", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "inventory" },
},
},
content = {
type = "flow",
style_mods = { horizontal_spacing = 12 },
direction = "horizontal",
ref = { "inventory", "content_frame" },
templates.inventory_slot_table("provided", 12),
templates.inventory_slot_table("in_transit", 8),
templates.inventory_slot_table("requested", 6),
},
}
end
local function update_table(self, name, color)
local translations = self.player_table.dictionaries.materials
local state = self.state
local refs = self.refs.inventory
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local ltn_inventory = state.ltn_data.inventory[name][search_surface]
local i = 0
local table = refs[name].table
local children = table.children
for name, count_by_network_id in pairs(ltn_inventory or {}) do
if
bit32.btest(count_by_network_id.combined_id, search_network_id)
and string.find(string.lower(translations[name]), string.lower(search_query))
then
local running_count = 0
for network_id, count in pairs(count_by_network_id) do
if network_id ~= "combined_id" and bit32.btest(network_id, search_network_id) then
running_count = running_count + count
end
end
if running_count > 0 then
i = i + 1
local button = children[i]
if not button then
button = table.add({ type = "sprite-button", style = "flib_slot_button_" .. color, enabled = false })
end
button.sprite = string.gsub(name, ",", "/")
button.number = running_count
button.tooltip = "[img="
.. string.gsub(name, ",", "/")
.. "] [font=default-semibold]"
.. translations[name]
.. "[/font]\n"
.. misc.delineate_number(running_count)
end
end
end
for j = i + 1, #children do
children[j].destroy()
end
end
function inventory_tab.update(self)
update_table(self, "provided", "green")
update_table(self, "in_transit", "blue")
update_table(self, "requested", "red")
end
return inventory_tab

View File

@@ -0,0 +1,270 @@
local gui = require("__flib__.gui-lite")
local constants = require("scripts.gui.constants")
--local actions = require("scripts.gui.actions")
local templates = require("scripts.gui.templates")
local trains_tab = require("scripts.gui.trains")
--local depots_tab = require("scripts.gui.depots")
--local stations_tab = require("scripts.gui.stations")
--local inventory_tab = require("scripts.gui.inventory")
--local history_tab = require("scripts.gui.history")
--local alerts_tab = require("scripts.gui.alerts")
function Index:dispatch(msg, e)
-- "Transform" the action based on criteria
if msg.transform == "handle_refresh_click" then
if e.shift then
msg.action = "toggle_auto_refresh"
else
self.state.ltn_data = global.data
self.do_update = true
end
elseif msg.transform == "handle_titlebar_click" then
if e.button == defines.mouse_button_type.middle then
msg.action = "recenter"
end
end
-- Dispatch the associated action
if msg.action then
local func = self.actions[msg.action]
if func then
func(self, msg, e)
else
log("Attempted to call action `" .. msg.action .. "` for which there is no handler yet.")
end
end
-- Update if necessary
if self.do_update then
self:update()
self.do_update = false
end
end
function Index:schedule_update()
self.do_update = true
end
local manager = {}
function manager.build(player, player_data)
local widths = constants.gui["en"]
---@type table<string, LuaGuiElement>
local refs = {}
local _, window = gui.add(player.gui.screen, {
{
name = "manager_window",
type = "frame",
direction = "vertical",
visible = false,
handler = manager_handle.close,
children = {
{
name = "manager_titlebar",
type = "flow",
style = "flib_titlebar_flow",
handler = manager_handle.titlebar_click,
children = {
{ type = "label", style = "frame_title", caption = { "mod-name.LtnManager" }, ignored_by_interaction = true },
{ type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
{
name = "manager_dispatcher_status_label",
type = "label",
style = "bold_label",
style_mods = { font_color = constants.colors.red.tbl, left_margin = -4, top_margin = 1 },
caption = { "gui.ltnm-dispatcher-disabled" },
tooltip = { "gui.ltnm-dispatcher-disabled-description" },
visible = not settings.global["cybersyn-enable-planner"].value,
},
templates.frame_action_button("manager_pin_button", "ltnm_pin", { "gui.ltnm-keep-open" }, manager_handle.pin),--on_gui_clicked
templates.frame_action_button("manager_refresh_button", "ltnm_refresh", { "gui.ltnm-refresh-tooltip" }, manager_handle.refresh_click),--on_gui_clicked
templates.frame_action_button(nil, "utility/close", { "gui.close-instruction" }, manager_handle.close),--on_gui_clicked
},
},
{
type = "frame",
style = "inside_deep_frame",
direction = "vertical",
children = {
{
type = "frame",
style = "ltnm_main_toolbar_frame",
children = {
{ type = "label", style = "subheader_caption_label", caption = { "gui.ltnm-search-label" } },
{
name = "manager_text_search_field",
type = "textfield",
clear_and_focus_on_right_click = true,
handler = manager_handle.update_text_search_query, --on_gui_text_changed
},
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{ type = "label", style = "caption_label", caption = { "gui.ltnm-network-id-label" } },
{
name = "manager_network_id_field",
type = "textfield",
style_mods = { width = 120 },
numeric = true,
allow_negative = true,
clear_and_focus_on_right_click = true,
text = "-1",
handler = manager_handle.update_network_id_query, --on_gui_text_changed
},
{ type = "label", style = "caption_label", caption = { "gui.ltnm-surface-label" } },
{
name = "manager_surface_dropdown",
type = "drop-down",
handler = manager_handle.change_surface, --on_gui_selection_state_changed
},
},
},
{
name = "manager_tabbed_pane",
type = "tabbed-pane",
style = "ltnm_tabbed_pane",
children = {
trains_tab.build(widths, refs),
},
},
},
},
},
},
}, refs)
refs.manager_titlebar.drag_target = window
window.force_auto_center()
end
--- @param player LuaPlayer
--- @param refs table<string, LuaGuiElement>
function manager.destroy(player, refs)
refs.manager_window.destroy()
player.set_shortcut_toggled("ltnm-toggle-gui", false)
player.set_shortcut_available("ltnm-toggle-gui", false)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.open(player, player_data, refs)
refs.manager_window.bring_to_front()
refs.manager_window.visible = true
player_data.visible = true
if not player_data.pinning then
player.opened = refs.manager_window
end
player.set_shortcut_toggled("ltnm-toggle-gui", true)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.close(player, player_data, refs)
if player_data.pinning then
return
end
refs.manager_window.visible = false
player_data.visible = false
if player.opened == refs.manager_window then
player.opened = nil
end
player.set_shortcut_toggled("ltnm-toggle-gui", false)
end
manager.handle = {}
--- @param e GuiEventData
function manager.wrapper(e, handler)
local player = game.get_player(e.player_index)
if not player then return end
local player_data = global.manager.players[e.player_index]
handler(player, player_data, refs)
end
local function toggle_fab(elem, sprite, state)
if state then
elem.style = "flib_selected_frame_action_button"
elem.sprite = sprite .. "_black"
else
elem.style = "frame_action_button"
elem.sprite = sprite .. "_white"
end
end
manager.handle.close = manager.close
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.recenter(player, player_data, refs)
refs.window.force_auto_center()
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.toggle_auto_refresh(player, player_data, refs)
player_data.auto_refresh = not player_data.auto_refresh
toggle_fab(refs.manager_refresh_button, "ltnm_refresh", player_data.auto_refresh)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.toggle_pinned(player, player_data, refs)
player_data.pinned = not player_data.pinned
toggle_fab(refs.manager_pin_button, "ltnm_pin", player_data.pinned)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.update_text_search_query(player, player_data, refs)
local query = e.text
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
Gui.state.search_query = query
if Gui.state.search_job then
on_tick_n.remove(Gui.state.search_job)
end
if #query == 0 then
Gui:schedule_update()
else
Gui.state.search_job = on_tick_n.add(
game.tick + 30,
{ gui = "main", action = "update", player_index = Gui.player.index }
)
end
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.update_network_id_query(player, player_data, refs)
Gui.state.network_id = tonumber(Gui.refs.toolbar.network_id_field.text) or -1
Gui:schedule_update()
end
return manager

View File

@@ -0,0 +1,174 @@
local gui = require("__flib__.gui")
local constants = require("constants")
local util = require("scripts.util")
local templates = require("templates")
local stations_tab = {}
function stations_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-stations" },
ref = { "stations", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "stations" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "stations", "content_frame" },
{
type = "frame",
style = "ltnm_table_toolbar_frame",
templates.sort_checkbox(widths, "stations", "name", true),
templates.sort_checkbox(widths, "stations", "status", false, { "gui.ltnm-status-description" }),
templates.sort_checkbox(widths, "stations", "network_id", false),
templates.sort_checkbox(
widths,
"stations",
"provided_requested",
false,
{ "gui.ltnm-provided-requested-description" }
),
templates.sort_checkbox(widths, "stations", "shipments", false, { "gui.ltnm-shipments-description" }),
templates.sort_checkbox(widths, "stations", "control_signals", false),
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "stations", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "stations", "warning_flow" },
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-stations" },
ref = { "stations", "warning_label" },
},
},
},
}
end
function stations_tab.update(self)
local dictionaries = self.player_table.dictionaries
local state = self.state
local refs = self.refs.stations
local widths = self.widths.stations
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local ltn_stations = state.ltn_data.stations
local scroll_pane = refs.scroll_pane
local children = scroll_pane.children
local sorts = state.sorts.stations
local active_sort = sorts._active
local sorted_stations = state.ltn_data.sorted_stations[active_sort]
local table_index = 0
-- False = ascending (arrow down), True = descending (arrow up)
local start, finish, step
if sorts[active_sort] then
start = #sorted_stations
finish = 1
step = -1
else
start = 1
finish = #sorted_stations
step = 1
end
for sorted_index = start, finish, step do
local station_id = sorted_stations[sorted_index]
local station_data = ltn_stations[station_id]
if station_data.entity.valid then
if
(search_surface == -1 or station_data.entity.surface.index == search_surface)
and bit32.btest(station_data.network_id, search_network_id)
and (
#search_query == 0 or string.find(station_data.search_strings[self.player.index], string.lower(search_query))
)
then
table_index = table_index + 1
local row = children[table_index]
local color = table_index % 2 == 0 and "dark" or "light"
if not row then
row = gui.add(scroll_pane, {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.name },
tooltip = constants.open_station_gui_tooltip,
},
templates.status_indicator(widths.status, true),
{ type = "label", style_mods = { width = widths.network_id, horizontal_align = "center" } },
templates.small_slot_table(widths, color, "provided_requested"),
templates.small_slot_table(widths, color, "shipments"),
templates.small_slot_table(widths, color, "control_signals"),
})
end
gui.update(row, {
{
elem_mods = { caption = station_data.name },
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = station_id },
},
},
{
{ elem_mods = { sprite = "flib_indicator_" .. station_data.status.color } },
{ elem_mods = { caption = station_data.status.count } },
},
{ elem_mods = { caption = station_data.network_id } },
})
util.slot_table_update(row.provided_requested_frame.provided_requested_table, {
{ color = "green", entries = station_data.provided, translations = dictionaries.materials },
{ color = "red", entries = station_data.requested, translations = dictionaries.materials },
})
util.slot_table_update(row.shipments_frame.shipments_table, {
{ color = "green", entries = station_data.inbound, translations = dictionaries.materials },
{ color = "blue", entries = station_data.outbound, translations = dictionaries.materials },
})
util.slot_table_update(row.control_signals_frame.control_signals_table, {
{
color = "default",
entries = station_data.control_signals,
translations = dictionaries.virtual_signals,
type = "virtual-signal",
},
})
end
end
end
for child_index = table_index + 1, #children do
children[child_index].destroy()
end
if table_index == 0 then
refs.warning_flow.visible = true
scroll_pane.visible = false
refs.content_frame.style = "ltnm_main_warning_frame"
else
refs.warning_flow.visible = false
scroll_pane.visible = true
refs.content_frame.style = "ltnm_main_content_frame"
end
end
return stations_tab

View File

@@ -0,0 +1,100 @@
local constants = require("constants")
local templates = {}
--- Creates a frame action button, automatically accounting for inverted sprites.
--- @param name string?
--- @param sprite string?
--- @param tooltip LocalisedString?
--- @param handler GuiElemHandler?
--- @param tags Tags?
function templates.frame_action_button(name, sprite, tooltip, handler, tags)
return {
type = "sprite-button",
name = name,
style = "frame_action_button",
sprite = sprite .. "_white",
hovered_sprite = sprite .. "_black",
clicked_sprite = sprite .. "_black",
mouse_button_filter = { "left" },
tooltip = tooltip,
handler = handler,
tags = tags,
}
end
--- Creates a full-sized scrollable slot table for the inventory tab.
--- @param name string
--- @param columns uint
function templates.inventory_slot_table(name, columns)
return {
type = "flow",
direction = "vertical",
{ type = "label", style = "bold_label", caption = { "gui.ltnm-" .. string.gsub(name, "_", "-") } },
{
type = "frame",
style = "deep_frame_in_shallow_frame",
style_mods = { height = constants.gui_inventory_table_height },
ref = { "inventory", name, "frame" },
{
type = "scroll-pane",
style = "ltnm_slot_table_scroll_pane",
style_mods = { width = 40 * columns + 12, minimal_height = constants.gui_inventory_table_height },
vertical_scroll_policy = "auto-and-reserve-space",
-- vertical_scroll_policy = "always",
ref = { "inventory", name, "scroll_pane" },
{ type = "table", style = "slot_table", column_count = columns, ref = { "inventory", name, "table" } },
},
},
}
end
--- Creates a small non-scrollable slot table.
--- @param widths table
--- @param color string
--- @param name string
function templates.small_slot_table(widths, color, name)
return {
type = "frame",
name = name .. "_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths[name] },
{ type = "table", name = name .. "_table", style = "slot_table", column_count = widths[name .. "_columns"] },
}
end
--- Creates a column header with a sort toggle.
--- @param widths table
--- @param tab string
--- @param column string
--- @param selected boolean
--- @param tooltip LocalisedString
function templates.sort_checkbox(widths, tab, column, selected, tooltip, state)
if state == nil then
state = false
end
return {
type = "checkbox",
style = selected and "ltnm_selected_sort_checkbox" or "ltnm_sort_checkbox",
style_mods = { width = widths and widths[tab][column] or nil, horizontally_stretchable = not widths },
caption = { "gui.ltnm-" .. string.gsub(column, "_", "-") },
tooltip = tooltip,
state = state,
ref = { tab, "toolbar", column .. "_checkbox" },
actions = {
on_checked_state_changed = { gui = "main", tab = tab, action = "toggle_sort", column = column },
},
}
end
function templates.status_indicator(width, center)
return {
type = "flow",
style = "flib_indicator_flow",
style_mods = { horizontal_align = center and "center" or nil, width = width },
{ type = "sprite", style = "flib_indicator" },
{ type = "label" },
}
end
return templates

View File

@@ -0,0 +1,151 @@
local gui = require("__flib__.gui-lite")
local constants = require("constants")
local util = require("scripts.gui.util")
local templates = require("templates")
local trains_tab = {}
function trains_tab.build(map_data, player_id, player_data)
local widths = constants.gui["en"]
local search_query = player_data.search_query
local search_network_flag = player_data.network_flag
local search_network = player_data.network
local trains_sorted = player_data.trains_sorted
---@type GuiElemDef
local train_list = {}
--if not sorted_trains then
-- sorted_trains = {}
-- ids = {}
-- for id, train in pairs(map_data) do
-- local i = #ids + 1
-- ids[i] = id
-- sorted_trains[i] = train
-- end
-- dual_sort(ids, sorted_trains)
--end
if #trains_sorted == 0 then
train_list[1] = {
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-trains" },
ref = { "trains", "warning_label" },
}
else
local start, finish, step
if player_data.trains_ascending then
start = #trains_sorted
finish = 1
step = -1
else
start = 1
finish = #trains_sorted
step = 1
end
local gui_idx = 1
for idx = start, finish, step do
local train_id = trains_sorted[idx]
local train = map_data.trains[train_id]
if
true
then
local color = gui_idx % 2 == 0 and "dark" or "light"
train_list[gui_idx] = {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
children = {
{
type = "frame",
style = "ltnm_table_inset_frame_" .. color,
children = {
type = "minimap",
style = "ltnm_train_minimap",
{ type = "label", style = "ltnm_minimap_label" },
{
type = "button",
style = "ltnm_train_minimap_button",
tooltip = { "gui.ltnm-open-train-gui" },
elem_mods = { entity = get_any_train_entity(train.entity) },
actions = {
on_click = { gui = "main", action = "open_train_gui", train_id = train_id },
},
},
},
},
{
type = "label",
style_mods = { width = widths.trains.composition },
elem_mods = { caption = train.composition },
},
{
type = "label", style_mods = { width = widths.trains.depot },
elem_mods = { caption = train.depot },
},
{
type = "frame",
name = "shipment_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths.trains.shipment },
children = {
{
type = "table",
name = "shipment_table",
style = "slot_table",
column_count = widths.trains.shipment_columns,
children = util.slot_table_build(train.manifest, "default"),
},
},
},
},
}
gui_idx = gui_idx + 1
end
end
end
return {
tab = {
type = "tab",
caption = #trains_sorted == 0 and { "gui.ltnm-trains" } or { "gui.ltnm-trains", #train_list },
badge_text = misc.delineate_number(#ltn_data.sorted_trains.composition),
ref = { "trains", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "trains" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "trains", "content_frame" },
children = {
{
type = "frame",
style = "ltnm_table_toolbar_frame",
templates.sort_checkbox(widths, "trains", "train_id", true),
templates.sort_checkbox(widths, "trains", "status", false),
templates.sort_checkbox(widths, "trains", "composition", false, { "gui.ltnm-composition-description" }),
templates.sort_checkbox(widths, "trains", "depot", false),
templates.sort_checkbox(widths, "trains", "shipment", false),
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "trains", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "trains", "warning_flow" },
children = train_list,
},
},
},
}
end
return trains_tab

View File

@@ -0,0 +1,90 @@
local gui = require("__flib__.gui-lite")
local format = require("__flib__.format")
local util = {}
--- Create a flying text at the player's cursor with an error sound.
--- @param player LuaPlayer
--- @param message LocalisedString
function util.error_flying_text(player, message)
player.create_local_flying_text({ create_at_cursor = true, text = message })
player.play_sound({ path = "utility/cannot_build" })
end
function util.gui_list(parent, iterator, test, build, update, ...)
local children = parent.children
local i = 0
for k, v in table.unpack(iterator) do
local passed = test(v, k, i, ...)
if passed then
i = i + 1
local child = children[i]
if not child then
gui.build(parent, { build(...) })
child = parent.children[i]
end
gui.update(child, update(v, k, i, ...))
end
end
for j = i + 1, #children do
children[j].destroy()
end
end
--- Updates a slot table based on the passed criteria.
--- @param manifest Manifest
--- @param color string
--- @return GuiElemDef[]
function util.slot_table_build(manifest, color)
local children = {}
local i = 1
for _, item in pairs(manifest) do
local name = item.name
local sprite
if item.type then
sprite = item.type .. "/" .. name
else
sprite = string.gsub(name, ",", "/")
end
if game.is_valid_sprite_path(sprite) then
children[i] = {
type = "sprite-button",
enabled = false,
style = "ltnm_small_slot_button_" .. color,
sprite = sprite,
tooltip = {
"",
"[img=" .. sprite .. "]",
{ "item-name." .. name },
"\n"..format.number(count),
},
}
i = i + 1
end
end
return children
end
function util.sorted_iterator(arr, src_tbl, sort_state)
local step = sort_state and 1 or -1
local i = sort_state and 1 or #arr
return function()
local j = i + step
if arr[j] then
i = j
local arr_value = arr[j]
return arr_value, src_tbl[arr_value]
end
end,
arr
end
local MAX_INT = 2147483648 -- math.pow(2, 31)
function util.signed_int32(val)
return (val >= MAX_INT and val - (2 * MAX_INT)) or val
end
return util

View File

@@ -49,3 +49,46 @@ end
function irpairs(a) function irpairs(a)
return irnext, a, 0 return irnext, a, 0
end end
---@generic V
---@param arr Array<V>
---@param comp fun(a: V, b: V) A comparison function for sorting. Must return truthy if `a < b`.
function stable_sort(arr, comp)
local size = #arr
for i = 2, size do
local a = arr[i]
local j = i
while j > 1 do
local b = arr[j - 1]
if comp(a, b) then
arr[j] = b
j = j - 1
else
break
end
end
arr[j] = a
end
end
---@param values number[]
---@param keys any[]
function dual_sort(values, keys)
local size = #values
for i = 2, size do
local a = values[i]
local j = i
while j > 1 do
local b = values[j - 1]
if a < b then
values[j] = b
keys[j] = keys[j - 1]
j = j - 1
else
break
end
end
values[j] = a
keys[j] = keys[i]
end
end

View File

@@ -102,4 +102,13 @@ data:extend({
setting_type = "runtime-global", setting_type = "runtime-global",
default_value = false, default_value = false,
}, },
{
type = "int-setting",
name = "cybersyn-history-length",
setting_type = "runtime-global",
minimum_value = 10,
maximum_value = 1000,
default_value = 50,
order = "ea",
},
}) })