Aetheria/Assets/Scripts/UI/ScenarioPicker.cs
soul 3bfac25027 New Game is a two-pane scenario picker: names by group, selected brief/seed/ambient, one Launch, arrows and Enter
MainMenu.AddScenarios and the per-scenario launching buttons are deleted; the seven Clear+Title pairs collapse into Next. The main menu EventSystem no longer deselects on background click so the arrows keep working.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:42:54 +02:00

130 lines
5.4 KiB
C#

using System;
using System.Collections.Generic;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
// The New Game screen: scenario names on the left pane by group, the selected scenario's brief, seed and ambient on a
// docked right pane, one Launch. This class owns which scenario is selected (one index into the rows it built);
// everything the screen shows about a scenario, and what Launch and Enter launch, reads that index. Keys arrive only as
// Input System UI events on the focused Launch button, so Enter and a mouse click are the same Button.onClick.
public sealed class ScenarioPicker
{
private readonly List<Scenario> _scenarios = new List<Scenario>();
private readonly List<string> _groupLabels = new List<string>();
private readonly List<Button> _rows = new List<Button>();
private readonly List<ColorBlock> _rowColors = new List<ColorBlock>();
private readonly PropertiesPanel _detail;
private readonly Action<Scenario> _launch;
private readonly Action _back;
private Button _launchButton;
private int _selected;
private ScenarioPicker(PropertiesPanel detail, Action<Scenario> launch, Action back)
{
_detail = detail;
_launch = launch;
_back = back;
}
// The scenario the detail pane describes and Launch will launch.
public Scenario Selected => _scenarios[_selected];
// Parks the detail pane beside the list pane: a child of it, outside its layout, hidden until Show.
public static void Dock(PropertiesPanel detail, PropertiesPanel list, float width = 480, float gap = 16)
{
var rect = (RectTransform) detail.transform;
rect.SetParent(list.transform, false);
detail.gameObject.AddComponent<LayoutElement>().ignoreLayout = true;
rect.anchorMin = new Vector2(1, 0);
rect.anchorMax = new Vector2(1, 1);
rect.pivot = new Vector2(0, .5f);
rect.anchoredPosition = new Vector2(gap, 0);
rect.sizeDelta = new Vector2(width, 0);
detail.gameObject.SetActive(false);
}
// Builds both panes for the groups, shows the detail pane, selects the first scenario and focuses Launch.
public static ScenarioPicker Show(PropertiesPanel list, PropertiesPanel detail,
IReadOnlyList<(string label, Scenario[] scenarios)> groups, Action<Scenario> launch, Action back)
{
var picker = new ScenarioPicker(detail, launch, back);
foreach (var group in groups)
{
if (groups.Count > 1) list.AddSection(group.label);
foreach (var scenario in group.scenarios)
{
var index = picker._scenarios.Count;
picker._scenarios.Add(scenario);
picker._groupLabels.Add(group.label);
var row = list.AddButton(scenario.Name, () => picker.Select(index));
picker.Track(row.Button);
picker._rows.Add(row.Button);
picker._rowColors.Add(row.Button.colors);
}
}
picker.Track(list.AddButton("Back", picker.Back).Button);
detail.Clear();
detail.gameObject.SetActive(true);
detail.AddProperty(() => picker.Selected.Brief);
detail.AddProperty("Seed", () => picker.Selected.Seed == 0 ? "new each launch" : picker.Selected.Seed.ToString());
detail.AddProperty("Ambient", () => picker.Selected.Ambient ? "on" : "off");
picker._launchButton = detail.AddButton("Launch", picker.Activate).Button;
picker.Track(picker._launchButton);
picker._launchButton.OnMoveAsObservable().Subscribe(move =>
{
if (move.moveDir == MoveDirection.Up) picker.Step(-1);
else if (move.moveDir == MoveDirection.Down) picker.Step(1);
});
picker._launchButton.OnCancelAsObservable().Subscribe(_ => picker.Back());
picker.Select(0);
return picker;
}
// Takes a button out of Unity's navigation so a click never moves focus and Move never walks automatic navigation.
private void Track(Button button) => button.navigation = new Navigation { mode = Navigation.Mode.None };
// Moves the selection one row, stopping at either end.
private void Step(int delta) => Select(_selected + delta);
// Makes one row the selection: clamps the index, redraws the highlight and title, and returns focus to Launch.
private void Select(int index)
{
_selected = Mathf.Clamp(index, 0, _scenarios.Count - 1);
for (var i = 0; i < _rows.Count; i++)
{
var colors = _rowColors[i];
if (i == _selected)
{
colors.normalColor = colors.highlightedColor;
colors.selectedColor = colors.highlightedColor;
}
_rows[i].colors = colors;
}
_detail.Title.text = MainMenu.TitleSubtitle(Selected.Name, _groupLabels[_selected]);
Focus(_launchButton.gameObject);
}
// Launches the selection; focus is cleared first so a repeated Enter or a second click cannot launch twice.
private void Activate()
{
Focus(null);
_launch(Selected);
}
// Leaves the screen; the same path for the Back button and Cancel.
private void Back()
{
Focus(null);
_back();
}
private static void Focus(GameObject target)
{
if (EventSystem.current != null) EventSystem.current.SetSelectedGameObject(target);
}
}