From f04a2a36612639b1d18640c0142d96c5b83341d0 Mon Sep 17 00:00:00 2001 From: iisaev Date: Wed, 21 Oct 2026 20:15:32 +0200 Subject: [PATCH] Game picker and save browser: menu window shell, background warm-up The main menu warms every ready game's mods up into the assembly cache in the background (last played first), so picking a game only loads cached assemblies. Game picker and save browser are scenes on the shared MenuWindowShell (also used by the settings): caps title, close on the right, footer hint; the picker footer shows the warm-up state, cards get framed covers with a letter placeholder and Continue as the main action; the save browser shows 3x2 large cards with the game selector in the header. new_game waits for the warm-up; menu_flow takes --shots. --- locale/en.properties | 5 + locale/ru.properties | 5 + scenes/menu/game_picker.tscn | 111 +++++++++++++++ scenes/menu/save_load_menu.tscn | 119 +++++++++++----- scenes/menu/save_slot_item.tscn | 100 +++++++------ src/GamePickerPanel.cs | 132 ++++++++++-------- src/MainMenu.cs | 27 +++- src/MenuWindowShell.cs | 66 +++++++++ src/SaveLoadMenu.cs | 23 +-- src/SaveSlotItem.cs | 60 +++++--- src/SettingsMenu.cs | 31 +--- .../menu_flow/src/MenuFlowTestDriverNode.cs | 21 ++- .../new_game/src/NewGameTestDriverNode.cs | 10 +- 13 files changed, 500 insertions(+), 210 deletions(-) create mode 100644 scenes/menu/game_picker.tscn create mode 100644 src/MenuWindowShell.cs diff --git a/locale/en.properties b/locale/en.properties index d83907a..a99ff8b 100644 --- a/locale/en.properties +++ b/locale/en.properties @@ -26,6 +26,11 @@ menu/play/status_mismatch = Incompatible versions: {0} menu/play/status_problems = Problems in the game composition: {0} menu/play/status_install = To install: {0}, to enable: {1} menu/load/empty = No saves yet +menu/save/new_slot = + New save +menu/load/hint = Click a save to load it +menu/save/hint = Click a slot to save the game; a used slot is overwritten after a confirmation +menu/play/hint_warming = The games' mods are being prepared in the background — starting will be quick afterwards +menu/play/hint_ready = The games' mods are ready, starting takes a few seconds menu/load/legacy_group = Saves without a game menu/load/legacy_title = Save without a game menu/load/legacy_text = This save was made before game collections existed. Choose the game it belongs to — the file moves into that game's saves. diff --git a/locale/ru.properties b/locale/ru.properties index be52afa..f2a3128 100644 --- a/locale/ru.properties +++ b/locale/ru.properties @@ -26,6 +26,11 @@ menu/play/status_mismatch = Несовместимые версии: {0} menu/play/status_problems = Ошибки в составе игры: {0} menu/play/status_install = Нужно установить: {0}, включить: {1} menu/load/empty = Сохранений пока нет +menu/save/new_slot = + Новое сохранение +menu/load/hint = Нажмите на сохранение, чтобы загрузить его +menu/save/hint = Нажмите на слот, чтобы сохранить игру; занятый слот перезапишется после подтверждения +menu/play/hint_warming = Моды игр готовятся в фоне — запуск после этого будет быстрым +menu/play/hint_ready = Моды игр готовы, запуск займёт несколько секунд menu/load/legacy_group = Сохранения без игры menu/load/legacy_title = Сохранение без игры menu/load/legacy_text = Это сохранение сделано до появления игр-коллекций. Выберите игру, к которой оно относится, — файл переедет в её сохранения. diff --git a/scenes/menu/game_picker.tscn b/scenes/menu/game_picker.tscn new file mode 100644 index 0000000..3a19a94 --- /dev/null +++ b/scenes/menu/game_picker.tscn @@ -0,0 +1,111 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="src/GamePickerPanel.cs" id="GamePickerScript"] + +[node name="GamePickerPanel" type="Control" unique_id=352232428] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("GamePickerScript") + +[node name="Backdrop" type="ColorRect" parent="." unique_id=1612952176] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0.9) + +[node name="Window" type="PanelContainer" parent="." unique_id=788680784] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -520.0 +offset_top = -360.0 +offset_right = 520.0 +offset_bottom = 360.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Column" type="VBoxContainer" parent="Window" unique_id=1513362382] +layout_mode = 2 +theme_override_constants/separation = 0 + +[node name="Header" type="MarginContainer" parent="Window/Column" unique_id=1901680926] +layout_mode = 2 +theme_override_constants/margin_left = 24 +theme_override_constants/margin_top = 16 +theme_override_constants/margin_right = 16 +theme_override_constants/margin_bottom = 16 + +[node name="Row" type="HBoxContainer" parent="Window/Column/Header" unique_id=897579862] +layout_mode = 2 +theme_override_constants/separation = 24 + +[node name="Title" type="Label" parent="Window/Column/Header/Row" unique_id=1589586692] +layout_mode = 2 +size_flags_vertical = 4 +text = "Choose a game" +uppercase = true + +[node name="Spacer" type="Control" parent="Window/Column/Header/Row" unique_id=1565293582] +layout_mode = 2 +size_flags_horizontal = 3 +mouse_filter = 2 + +[node name="CloseButton" type="Button" parent="Window/Column/Header/Row" unique_id=392870380] +layout_mode = 2 +custom_minimum_size = Vector2(38, 38) +size_flags_vertical = 4 +focus_mode = 0 +text = "✕" + +[node name="RuleHeader" type="ColorRect" parent="Window/Column" unique_id=1522041891] +custom_minimum_size = Vector2(0, 1) +layout_mode = 2 + +[node name="Body" type="MarginContainer" parent="Window/Column" unique_id=897575893] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 24 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 24 +theme_override_constants/margin_bottom = 20 + +[node name="Scroll" type="ScrollContainer" parent="Window/Column/Body" unique_id=1648576427] +layout_mode = 2 +size_flags_vertical = 3 +horizontal_scroll_mode = 0 + +[node name="List" type="VBoxContainer" parent="Window/Column/Body/Scroll" unique_id=355317885] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 10 + +[node name="RuleFooter" type="ColorRect" parent="Window/Column" unique_id=866559016] +custom_minimum_size = Vector2(0, 1) +layout_mode = 2 + +[node name="Footer" type="MarginContainer" parent="Window/Column" unique_id=508767107] +layout_mode = 2 +theme_override_constants/margin_left = 24 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 24 +theme_override_constants/margin_bottom = 12 + +[node name="Row" type="HBoxContainer" parent="Window/Column/Footer" unique_id=1496801409] +custom_minimum_size = Vector2(0, 38) +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Hint" type="Label" parent="Window/Column/Footer/Row" unique_id=1150953277] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 diff --git a/scenes/menu/save_load_menu.tscn b/scenes/menu/save_load_menu.tscn index dcb2601..90a6eea 100644 --- a/scenes/menu/save_load_menu.tscn +++ b/scenes/menu/save_load_menu.tscn @@ -1,84 +1,131 @@ -[gd_scene load_steps=3 format=3] +[gd_scene format=3] [ext_resource type="Script" path="src/SaveLoadMenu.cs" id="SaveLoadMenuScript"] [ext_resource type="Script" path="src/SaveSlotList.cs" id="SaveSlotListScript"] -[node name="SaveLoadMenu" type="Control"] +[node name="SaveLoadMenu" type="Control" unique_id=1300922993] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -mouse_filter = 1 script = ExtResource("SaveLoadMenuScript") -[node name="Bg" type="ColorRect" parent="."] +[node name="Backdrop" type="ColorRect" parent="." unique_id=1957446002] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -color = Color(0, 0, 0, 0.65) +color = Color(0, 0, 0, 0.9) -[node name="Panel" type="Panel" parent="."] +[node name="Window" type="PanelContainer" parent="." unique_id=1214442353] layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -offset_left = -420.0 -offset_top = -280.0 -offset_right = 420.0 -offset_bottom = 280.0 +offset_left = -500.0 +offset_top = -350.0 +offset_right = 500.0 +offset_bottom = 350.0 grow_horizontal = 2 grow_vertical = 2 -[node name="Margin" type="MarginContainer" parent="Panel"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -theme_override_constants/margin_left = 28 -theme_override_constants/margin_top = 20 -theme_override_constants/margin_right = 28 -theme_override_constants/margin_bottom = 20 - -[node name="VBox" type="VBoxContainer" parent="Panel/Margin"] +[node name="Column" type="VBoxContainer" parent="Window" unique_id=154632647] layout_mode = 2 -theme_override_constants/separation = 14 +theme_override_constants/separation = 0 -[node name="Title" type="Label" parent="Panel/Margin/VBox"] +[node name="Header" type="MarginContainer" parent="Window/Column" unique_id=255743212] layout_mode = 2 -text = "Save/Load" -horizontal_alignment = 1 -theme_override_font_sizes/font_size = 24 +theme_override_constants/margin_left = 24 +theme_override_constants/margin_top = 16 +theme_override_constants/margin_right = 16 +theme_override_constants/margin_bottom = 16 -[node name="GameSelect" type="OptionButton" parent="Panel/Margin/VBox"] +[node name="Row" type="HBoxContainer" parent="Window/Column/Header" unique_id=1566510433] layout_mode = 2 +theme_override_constants/separation = 24 -[node name="Scroll" type="ScrollContainer" parent="Panel/Margin/VBox"] +[node name="Title" type="Label" parent="Window/Column/Header/Row" unique_id=1345868865] +layout_mode = 2 +size_flags_vertical = 4 +text = "Load" +uppercase = true + +[node name="GameSelect" type="OptionButton" parent="Window/Column/Header/Row" unique_id=1531297566] +custom_minimum_size = Vector2(280, 0) +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Spacer" type="Control" parent="Window/Column/Header/Row" unique_id=739525391] layout_mode = 2 size_flags_horizontal = 3 +mouse_filter = 2 + +[node name="CloseButton" type="Button" parent="Window/Column/Header/Row" unique_id=1063180782] +layout_mode = 2 +custom_minimum_size = Vector2(38, 38) +size_flags_vertical = 4 +focus_mode = 0 +text = "✕" + +[node name="RuleHeader" type="ColorRect" parent="Window/Column" unique_id=1844886327] +custom_minimum_size = Vector2(0, 1) +layout_mode = 2 + +[node name="Body" type="MarginContainer" parent="Window/Column" unique_id=280231313] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 24 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 24 +theme_override_constants/margin_bottom = 20 + +[node name="Content" type="VBoxContainer" parent="Window/Column/Body" unique_id=1998936353] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Scroll" type="ScrollContainer" parent="Window/Column/Body/Content" unique_id=131125275] +layout_mode = 2 size_flags_vertical = 3 -custom_minimum_size = Vector2(0, 380) horizontal_scroll_mode = 0 -[node name="SlotGrid" type="Control" parent="Panel/Margin/VBox/Scroll"] +[node name="SlotGrid" type="Control" parent="Window/Column/Body/Content/Scroll" unique_id=702369365] layout_mode = 2 size_flags_horizontal = 3 script = ExtResource("SaveSlotListScript") +ColumnCount = 3 +RowCount = 2 +SlotPadding = 14.0 -[node name="EmptyLabel" type="Label" parent="Panel/Margin/VBox"] +[node name="EmptyLabel" type="Label" parent="Window/Column/Body/Content" unique_id=1909778806] layout_mode = 2 +size_flags_vertical = 3 horizontal_alignment = 1 +vertical_alignment = 1 visible = false -[node name="BackBtn" type="Button" parent="Panel/Margin/VBox"] +[node name="RuleFooter" type="ColorRect" parent="Window/Column" unique_id=225368463] +custom_minimum_size = Vector2(0, 1) layout_mode = 2 -text = "Back" -size_flags_horizontal = 8 + +[node name="Footer" type="MarginContainer" parent="Window/Column" unique_id=775810662] +layout_mode = 2 +theme_override_constants/margin_left = 24 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 24 +theme_override_constants/margin_bottom = 12 + +[node name="Row" type="HBoxContainer" parent="Window/Column/Footer" unique_id=1592632905] +custom_minimum_size = Vector2(0, 38) +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Hint" type="Label" parent="Window/Column/Footer/Row" unique_id=1935428591] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 diff --git a/scenes/menu/save_slot_item.tscn b/scenes/menu/save_slot_item.tscn index 9fd4abf..ce17cb1 100644 --- a/scenes/menu/save_slot_item.tscn +++ b/scenes/menu/save_slot_item.tscn @@ -2,88 +2,86 @@ [ext_resource type="Script" path="src/SaveSlotItem.cs" id="SaveSlotItemScript"] -[node name="SaveSlotItem" type="Control" unique_id=2135443384] -custom_minimum_size = Vector2(120, 120) +[node name="SaveSlotItem" type="Control" unique_id=1515573353] +custom_minimum_size = Vector2(160, 140) layout_mode = 3 anchors_preset = 0 script = ExtResource("SaveSlotItemScript") -[node name="Background" type="ColorRect" parent="." unique_id=255218239] +[node name="Card" type="PanelContainer" parent="." unique_id=368114590] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -color = Color(0.12, 0.12, 0.12, 0.9) +mouse_filter = 2 -[node name="ScreenshotRect" type="TextureRect" parent="." unique_id=1290008068] -layout_mode = 1 -anchors_preset = -1 -anchor_right = 1.0 -anchor_bottom = 0.55 -grow_horizontal = 2 -grow_vertical = 2 +[node name="Column" type="VBoxContainer" parent="Card" unique_id=1588140578] +layout_mode = 2 +mouse_filter = 2 +theme_override_constants/separation = 0 + +[node name="Shot" type="PanelContainer" parent="Card/Column" unique_id=1733920571] +layout_mode = 2 +size_flags_vertical = 3 +mouse_filter = 2 + +[node name="ScreenshotRect" type="TextureRect" parent="Card/Column/Shot" unique_id=1172880873] +layout_mode = 2 +mouse_filter = 2 expand_mode = 1 -stretch_mode = 5 +stretch_mode = 6 -[node name="DateLabel" type="Label" parent="." unique_id=408317888] -layout_mode = 1 -anchors_preset = -1 -anchor_left = 0.02 -anchor_top = 0.57 -anchor_right = 0.98 -anchor_bottom = 0.68 -grow_horizontal = 2 -theme_override_font_sizes/font_size = 12 +[node name="Info" type="MarginContainer" parent="Card/Column" unique_id=288868314] +layout_mode = 2 +mouse_filter = 2 +theme_override_constants/margin_left = 12 +theme_override_constants/margin_top = 8 +theme_override_constants/margin_right = 12 +theme_override_constants/margin_bottom = 10 -[node name="SceneLabel" type="Label" parent="." unique_id=1968956898] -layout_mode = 1 -anchors_preset = -1 -anchor_left = 0.02 -anchor_top = 0.70 -anchor_right = 0.98 -anchor_bottom = 0.80 -grow_horizontal = 2 -theme_override_font_sizes/font_size = 11 +[node name="Column" type="VBoxContainer" parent="Card/Column/Info" unique_id=700616276] +layout_mode = 2 +mouse_filter = 2 +theme_override_constants/separation = 2 -[node name="TimeLabel" type="Label" parent="." unique_id=698436659] -layout_mode = 1 -anchors_preset = -1 -anchor_left = 0.02 -anchor_top = 0.83 -anchor_right = 0.56 -anchor_bottom = 0.95 -grow_horizontal = 2 -theme_override_font_sizes/font_size = 10 +[node name="DateLabel" type="Label" parent="Card/Column/Info/Column" unique_id=931970274] +layout_mode = 2 +clip_text = true -[node name="StatsLabel" type="Label" parent="." unique_id=400000001] -layout_mode = 1 -anchors_preset = -1 -anchor_left = 0.58 -anchor_top = 0.83 -anchor_right = 0.98 -anchor_bottom = 0.95 -grow_horizontal = 2 -theme_override_font_sizes/font_size = 10 +[node name="SceneLabel" type="Label" parent="Card/Column/Info/Column" unique_id=1597878703] +layout_mode = 2 +clip_text = true + +[node name="Meta" type="HBoxContainer" parent="Card/Column/Info/Column" unique_id=1948773627] +layout_mode = 2 +mouse_filter = 2 + +[node name="TimeLabel" type="Label" parent="Card/Column/Info/Column/Meta" unique_id=729436693] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="StatsLabel" type="Label" parent="Card/Column/Info/Column/Meta" unique_id=381078225] +layout_mode = 2 horizontal_alignment = 2 -[node name="EmptyLabel" type="Label" parent="." unique_id=1262712091] +[node name="EmptyLabel" type="Label" parent="." unique_id=1904510000] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -theme_override_font_sizes/font_size = 14 horizontal_alignment = 1 vertical_alignment = 1 -[node name="SlotButton" type="Button" parent="." unique_id=1877010914] +[node name="SlotButton" type="Button" parent="." unique_id=1369022409] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +focus_mode = 0 flat = true diff --git a/src/GamePickerPanel.cs b/src/GamePickerPanel.cs index 3b765b2..ac7f2c6 100644 --- a/src/GamePickerPanel.cs +++ b/src/GamePickerPanel.cs @@ -17,11 +17,17 @@ namespace Cthangover.BaseMenu /// readiness (what is missing and what can be installed), saves and the /// last play date — with New game / Continue / Saves / Install / Configure. /// Data comes from against all installed mods, so - /// it works from the front-end where only the menu is loaded. + /// it works from the front-end where only the menu is loaded. The window is + /// the menu shell (, scene game_picker.tscn); + /// its footer says whether the games' mods are still compiled in the + /// background () or a start is quick. /// public partial class GamePickerPanel : Control { + private const float HintRefreshSeconds = 0.5f; + private VBoxContainer _list; + private Label _hint; private readonly HashSet _sessionInstalled = new(StringComparer.OrdinalIgnoreCase); /// Raised when the player opens a game's saves; the menu shows its save browser. @@ -31,58 +37,29 @@ namespace Cthangover.BaseMenu public override void _Ready() { - SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); - MouseFilter = MouseFilterEnum.Stop; + MenuWindowShell.Style(this, Close); + GetNode