From 1821dc1ccc22e444f3af57ede5ae627b2a689542 Mon Sep 17 00:00:00 2001 From: monica Date: Sat, 7 Jan 2023 12:04:08 -0500 Subject: [PATCH] merged gui --- cybersyn/locale/en/manager.cfg | 84 ++++++++ cybersyn/prototypes/gui-style.lua | 329 +++++++++++++++++++++++++++++ cybersyn/prototypes/util.lua | 23 ++ cybersyn/scripts/gui.lua | 2 +- cybersyn/scripts/gui/LICENSE | 21 ++ cybersyn/scripts/gui/actions.lua | 195 +++++++++++++++++ cybersyn/scripts/gui/alerts.lua | 227 ++++++++++++++++++++ cybersyn/scripts/gui/constants.lua | 152 +++++++++++++ cybersyn/scripts/gui/depots.lua | 144 +++++++++++++ cybersyn/scripts/gui/history.lua | 208 ++++++++++++++++++ cybersyn/scripts/gui/inventory.lua | 87 ++++++++ cybersyn/scripts/gui/manager.lua | 270 +++++++++++++++++++++++ cybersyn/scripts/gui/stations.lua | 174 +++++++++++++++ cybersyn/scripts/gui/templates.lua | 100 +++++++++ cybersyn/scripts/gui/trains.lua | 151 +++++++++++++ cybersyn/scripts/gui/util.lua | 90 ++++++++ cybersyn/scripts/lib.lua | 43 ++++ cybersyn/settings.lua | 9 + 18 files changed, 2308 insertions(+), 1 deletion(-) create mode 100644 cybersyn/locale/en/manager.cfg create mode 100644 cybersyn/prototypes/gui-style.lua create mode 100644 cybersyn/prototypes/util.lua create mode 100644 cybersyn/scripts/gui/LICENSE create mode 100644 cybersyn/scripts/gui/actions.lua create mode 100644 cybersyn/scripts/gui/alerts.lua create mode 100644 cybersyn/scripts/gui/constants.lua create mode 100644 cybersyn/scripts/gui/depots.lua create mode 100644 cybersyn/scripts/gui/history.lua create mode 100644 cybersyn/scripts/gui/inventory.lua create mode 100644 cybersyn/scripts/gui/manager.lua create mode 100644 cybersyn/scripts/gui/stations.lua create mode 100644 cybersyn/scripts/gui/templates.lua create mode 100644 cybersyn/scripts/gui/trains.lua create mode 100644 cybersyn/scripts/gui/util.lua diff --git a/cybersyn/locale/en/manager.cfg b/cybersyn/locale/en/manager.cfg new file mode 100644 index 0000000..fadaa5a --- /dev/null +++ b/cybersyn/locale/en/manager.cfg @@ -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 diff --git a/cybersyn/prototypes/gui-style.lua b/cybersyn/prototypes/gui-style.lua new file mode 100644 index 0000000..91eb2d0 --- /dev/null +++ b/cybersyn/prototypes/gui-style.lua @@ -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, + }, +} diff --git a/cybersyn/prototypes/util.lua b/cybersyn/prototypes/util.lua new file mode 100644 index 0000000..1523199 --- /dev/null +++ b/cybersyn/prototypes/util.lua @@ -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 diff --git a/cybersyn/scripts/gui.lua b/cybersyn/scripts/gui.lua index da54079..c68aa46 100644 --- a/cybersyn/scripts/gui.lua +++ b/cybersyn/scripts/gui.lua @@ -190,9 +190,9 @@ function register_gui_actions() ["setting"] = handle_setting, ["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_closed, on_gui_closed) - flib_gui.handle_events() end ---@param comb LuaEntity diff --git a/cybersyn/scripts/gui/LICENSE b/cybersyn/scripts/gui/LICENSE new file mode 100644 index 0000000..ba4cee1 --- /dev/null +++ b/cybersyn/scripts/gui/LICENSE @@ -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. diff --git a/cybersyn/scripts/gui/actions.lua b/cybersyn/scripts/gui/actions.lua new file mode 100644 index 0000000..2683cbc --- /dev/null +++ b/cybersyn/scripts/gui/actions.lua @@ -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 diff --git a/cybersyn/scripts/gui/alerts.lua b/cybersyn/scripts/gui/alerts.lua new file mode 100644 index 0000000..1e915a5 --- /dev/null +++ b/cybersyn/scripts/gui/alerts.lua @@ -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 diff --git a/cybersyn/scripts/gui/constants.lua b/cybersyn/scripts/gui/constants.lua new file mode 100644 index 0000000..98f9d75 --- /dev/null +++ b/cybersyn/scripts/gui/constants.lua @@ -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 diff --git a/cybersyn/scripts/gui/depots.lua b/cybersyn/scripts/gui/depots.lua new file mode 100644 index 0000000..5dfa81a --- /dev/null +++ b/cybersyn/scripts/gui/depots.lua @@ -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 diff --git a/cybersyn/scripts/gui/history.lua b/cybersyn/scripts/gui/history.lua new file mode 100644 index 0000000..fa57171 --- /dev/null +++ b/cybersyn/scripts/gui/history.lua @@ -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 diff --git a/cybersyn/scripts/gui/inventory.lua b/cybersyn/scripts/gui/inventory.lua new file mode 100644 index 0000000..f882a03 --- /dev/null +++ b/cybersyn/scripts/gui/inventory.lua @@ -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 diff --git a/cybersyn/scripts/gui/manager.lua b/cybersyn/scripts/gui/manager.lua new file mode 100644 index 0000000..24d3fb8 --- /dev/null +++ b/cybersyn/scripts/gui/manager.lua @@ -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 + 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 +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 +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 +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 +function manager.handle.recenter(player, player_data, refs) + refs.window.force_auto_center() +end + +--- @param player LuaPlayer +--- @param player_data PlayerData +--- @param refs table +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 +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 +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 +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 diff --git a/cybersyn/scripts/gui/stations.lua b/cybersyn/scripts/gui/stations.lua new file mode 100644 index 0000000..dacc567 --- /dev/null +++ b/cybersyn/scripts/gui/stations.lua @@ -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 diff --git a/cybersyn/scripts/gui/templates.lua b/cybersyn/scripts/gui/templates.lua new file mode 100644 index 0000000..e2553d3 --- /dev/null +++ b/cybersyn/scripts/gui/templates.lua @@ -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 diff --git a/cybersyn/scripts/gui/trains.lua b/cybersyn/scripts/gui/trains.lua new file mode 100644 index 0000000..bdbd628 --- /dev/null +++ b/cybersyn/scripts/gui/trains.lua @@ -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 diff --git a/cybersyn/scripts/gui/util.lua b/cybersyn/scripts/gui/util.lua new file mode 100644 index 0000000..c1b01bf --- /dev/null +++ b/cybersyn/scripts/gui/util.lua @@ -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 diff --git a/cybersyn/scripts/lib.lua b/cybersyn/scripts/lib.lua index c851284..95afb2f 100644 --- a/cybersyn/scripts/lib.lua +++ b/cybersyn/scripts/lib.lua @@ -49,3 +49,46 @@ end function irpairs(a) return irnext, a, 0 end + +---@generic V +---@param arr Array +---@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 diff --git a/cybersyn/settings.lua b/cybersyn/settings.lua index 91b6ac7..57c2dcf 100644 --- a/cybersyn/settings.lua +++ b/cybersyn/settings.lua @@ -102,4 +102,13 @@ data:extend({ setting_type = "runtime-global", 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", + }, })