ScenarioPicker: selected row in the menu accent, title is group over name, panes docked 128 apart; verify and capture run green in Unity; metas
The prefab row tint is nearly invisible on the row's graphic, the long scenario name overflowed a 480-wide title, and the list scrolls to keep the selection in view. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
79a3a62fc1
commit
09de7e5eb2
6 changed files with 55 additions and 22 deletions
|
|
@ -8,9 +8,9 @@ using UnityEngine.UI;
|
|||
|
||||
// A render of the New Game screen as the game builds it: the real Main Menu scene's panel prototype, docked and filled
|
||||
// by ScenarioPicker.Dock and ScenarioPicker.Show with every scenario group, on a plain dark background (the cloud
|
||||
// backdrop is a play-mode renderer). Writes <project>/Temp/scenario-picker.png at 1920x1080.
|
||||
// backdrop is a play-mode renderer). Writes <project>/Temp/scenario-picker.png at 1920x1080, or the path after -pngOut (Unity empties Temp on exit).
|
||||
//
|
||||
// Unity -batchmode -projectPath <project> -executeMethod ScenarioPickerCapture.Run -logFile <log>
|
||||
// Unity -batchmode -projectPath <project> -executeMethod ScenarioPickerCapture.Run [-pngOut <file>] -logFile <log>
|
||||
//
|
||||
// Needs a GPU device; do not pass -nographics or -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
|
||||
public static class ScenarioPickerCapture
|
||||
|
|
@ -54,6 +54,8 @@ public static class ScenarioPickerCapture
|
|||
private static void Capture()
|
||||
{
|
||||
var (menu, list, detail) = Build();
|
||||
menu.Dialog.gameObject.SetActive(false);
|
||||
list.Title.text = MainMenu.TitleSubtitle("new game", "scenarios");
|
||||
ScenarioPicker.Show(list, detail, AllGroups(), _ => { }, () => { });
|
||||
list.RefreshValues();
|
||||
detail.RefreshValues();
|
||||
|
|
@ -75,6 +77,8 @@ public static class ScenarioPickerCapture
|
|||
Canvas.ForceUpdateCanvases();
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) list.transform);
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) detail.transform);
|
||||
foreach (var text in list.transform.GetComponentsInChildren<TMPro.TMP_Text>())
|
||||
text.ForceMeshUpdate();
|
||||
Canvas.ForceUpdateCanvases();
|
||||
camera.Render();
|
||||
|
||||
|
|
@ -93,6 +97,9 @@ public static class ScenarioPickerCapture
|
|||
throw new InvalidOperationException($"The capture is uniform: only {differing} pixels differ from the background.");
|
||||
|
||||
var path = Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Temp", "scenario-picker.png"));
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
var outIndex = Array.IndexOf(args, "-pngOut");
|
||||
if (outIndex >= 0) path = Path.GetFullPath(args[outIndex + 1]);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
File.WriteAllBytes(path, read.EncodeToPNG());
|
||||
Debug.Log($"[ScenarioPickerCapture] {path} ({differing} of {Width * Height} pixels differ from the background)");
|
||||
|
|
|
|||
2
Assets/Scripts/Editor/ScenarioPickerCapture.cs.meta
Normal file
2
Assets/Scripts/Editor/ScenarioPickerCapture.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2e79f280179150e4080250c45e07cd69
|
||||
|
|
@ -44,7 +44,6 @@ public static class ScenarioPickerVerify
|
|||
{
|
||||
try
|
||||
{
|
||||
EventSystem.current = new GameObject("verify EventSystem").AddComponent<EventSystem>();
|
||||
SelectionFollowsClicksAndArrows();
|
||||
ArrowsStopAtTheEnds();
|
||||
LeftAndRightAreIgnored();
|
||||
|
|
@ -70,6 +69,10 @@ public static class ScenarioPickerVerify
|
|||
private static Screen Build(bool oneGroup = false)
|
||||
{
|
||||
var (_, list, detail) = ScenarioPickerCapture.Build();
|
||||
// Build opens the scene afresh, which destroys the previous EventSystem.
|
||||
var events = new GameObject("verify EventSystem").AddComponent<EventSystem>();
|
||||
// Edit mode never calls OnEnable, which is what registers an EventSystem as current.
|
||||
typeof(EventSystem).GetMethod("OnEnable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(events, null);
|
||||
var screen = new Screen
|
||||
{
|
||||
List = list, Detail = detail,
|
||||
|
|
@ -81,10 +84,10 @@ public static class ScenarioPickerVerify
|
|||
? new List<(string label, Scenario[] scenarios)> { ("modes", screen.A) }
|
||||
: new List<(string label, Scenario[] scenarios)> { ("modes", screen.A), ("development", screen.B), ("test arenas", screen.C) };
|
||||
screen.Picker = ScenarioPicker.Show(list, detail, groups, screen.Launched.Add, () => screen.Backs++);
|
||||
var buttons = list.GetComponentsInChildren<PropertyButton>(true).Select(button => button.Button).ToArray();
|
||||
var buttons = list.GetComponentsInChildren<PropertyButton>(false).Where(button => !button.transform.IsChildOf(detail.transform)).Select(button => button.Button).ToArray();
|
||||
screen.Rows = buttons.Take(buttons.Length - 1).ToArray();
|
||||
screen.Back = buttons.Last();
|
||||
screen.Launch = detail.GetComponentsInChildren<PropertyButton>(true).Single().Button;
|
||||
screen.Launch = detail.GetComponentsInChildren<PropertyButton>(false).Single().Button;
|
||||
return screen;
|
||||
}
|
||||
|
||||
|
|
@ -114,10 +117,10 @@ public static class ScenarioPickerVerify
|
|||
Require(EventSystem.current.currentSelectedGameObject == screen.Launch.gameObject, "A click on a name took focus off Launch.");
|
||||
Require(screen.Rows.All(row => row.navigation.mode == Navigation.Mode.None) && screen.Launch.navigation.mode == Navigation.Mode.None
|
||||
&& screen.Back.navigation.mode == Navigation.Mode.None, "A picker button still takes Unity navigation.");
|
||||
var selected = screen.Rows[3].colors;
|
||||
var other = screen.Rows[0].colors;
|
||||
Require(selected.normalColor == selected.highlightedColor && other.normalColor != other.highlightedColor,
|
||||
"Only the selected row is drawn highlighted.");
|
||||
var accent = screen.Rows[3].targetGraphic.color;
|
||||
Require(screen.Rows.Where((row, i) => i != 3).All(row => row.targetGraphic.color != accent), "Only the selected row is drawn in the accent.");
|
||||
Move(screen, MoveDirection.Up);
|
||||
Require(screen.Rows[2].targetGraphic.color == accent && screen.Rows[3].targetGraphic.color != accent, "The accent did not follow the selection.");
|
||||
}
|
||||
|
||||
private static void ArrowsStopAtTheEnds()
|
||||
|
|
@ -163,7 +166,7 @@ public static class ScenarioPickerVerify
|
|||
private static string DetailText(Screen screen)
|
||||
{
|
||||
screen.Detail.RefreshValues();
|
||||
return string.Join("|", screen.Detail.GetComponentsInChildren<TextMeshProUGUI>(true).Select(text => text.text));
|
||||
return string.Join("|", screen.Detail.GetComponentsInChildren<TextMeshProUGUI>(false).Select(text => text.text));
|
||||
}
|
||||
|
||||
private static void DetailPaneFollowsSelection()
|
||||
|
|
@ -171,13 +174,13 @@ public static class ScenarioPickerVerify
|
|||
var screen = Build();
|
||||
var first = DetailText(screen);
|
||||
Require(first.Contains("brief of Alpha") && first.Contains("7") && first.Contains("on"), $"Detail does not describe Alpha: {first}");
|
||||
Require(screen.Detail.Title.text == MainMenu.TitleSubtitle("Alpha", "modes"), "Detail title is not the name and group.");
|
||||
Require(screen.Detail.Title.text == MainMenu.TitleSubtitle("modes", "Alpha"), "Detail title is not the name and group.");
|
||||
Move(screen, MoveDirection.Down);
|
||||
Move(screen, MoveDirection.Down);
|
||||
var third = DetailText(screen);
|
||||
Require(third.Contains("brief of Gamma") && third.Contains("new each launch") && third.Contains("off"), $"Detail does not describe Gamma: {third}");
|
||||
Require(screen.Detail.Title.text == MainMenu.TitleSubtitle("Gamma", "development"), "Detail title did not follow the selection.");
|
||||
Require(screen.Detail.GetComponentsInChildren<PropertyButton>(true).Single().Button == screen.Launch, "Selecting rebuilt the Launch button.");
|
||||
Require(screen.Detail.Title.text == MainMenu.TitleSubtitle("development", "Gamma"), "Detail title did not follow the selection.");
|
||||
Require(screen.Detail.GetComponentsInChildren<PropertyButton>(false).Single().Button == screen.Launch, "Selecting rebuilt the Launch button.");
|
||||
}
|
||||
|
||||
private static void OneGroupHasNoSectionLabel()
|
||||
|
|
|
|||
2
Assets/Scripts/Editor/ScenarioPickerVerify.cs.meta
Normal file
2
Assets/Scripts/Editor/ScenarioPickerVerify.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7ca0b5db32f745a43a8fdc67dfac2996
|
||||
|
|
@ -16,14 +16,18 @@ public sealed class ScenarioPicker
|
|||
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 List<Color> _rowFills = new List<Color>();
|
||||
private readonly PropertiesPanel _list;
|
||||
private readonly PropertiesPanel _detail;
|
||||
private readonly Action<Scenario> _launch;
|
||||
private readonly Action _back;
|
||||
private Button _launchButton;
|
||||
private static readonly Color Accent = new Color(1, .4458f, .05f);
|
||||
private int _selected;
|
||||
|
||||
private ScenarioPicker(PropertiesPanel detail, Action<Scenario> launch, Action back)
|
||||
private ScenarioPicker(PropertiesPanel list, PropertiesPanel detail, Action<Scenario> launch, Action back)
|
||||
{
|
||||
_list = list;
|
||||
_detail = detail;
|
||||
_launch = launch;
|
||||
_back = back;
|
||||
|
|
@ -33,7 +37,7 @@ public sealed class ScenarioPicker
|
|||
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)
|
||||
public static void Dock(PropertiesPanel detail, PropertiesPanel list, float width = 480, float gap = 128)
|
||||
{
|
||||
var rect = (RectTransform) detail.transform;
|
||||
rect.SetParent(list.transform, false);
|
||||
|
|
@ -50,7 +54,7 @@ public sealed class ScenarioPicker
|
|||
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);
|
||||
var picker = new ScenarioPicker(list, detail, launch, back);
|
||||
foreach (var group in groups)
|
||||
{
|
||||
if (groups.Count > 1) list.AddSection(group.label);
|
||||
|
|
@ -63,6 +67,7 @@ public sealed class ScenarioPicker
|
|||
picker.Track(row.Button);
|
||||
picker._rows.Add(row.Button);
|
||||
picker._rowColors.Add(row.Button.colors);
|
||||
picker._rowFills.Add(row.Button.targetGraphic.color);
|
||||
}
|
||||
}
|
||||
picker.Track(list.AddButton("Back", picker.Back).Button);
|
||||
|
|
@ -97,16 +102,28 @@ public sealed class ScenarioPicker
|
|||
_selected = Mathf.Clamp(index, 0, _scenarios.Count - 1);
|
||||
for (var i = 0; i < _rows.Count; i++)
|
||||
{
|
||||
// The prefab tint barely moves the row colour, so the selected row is drawn in the menu accent.
|
||||
var colors = _rowColors[i];
|
||||
if (i == _selected)
|
||||
{
|
||||
colors.normalColor = colors.highlightedColor;
|
||||
colors.selectedColor = colors.highlightedColor;
|
||||
}
|
||||
if (i == _selected) colors.normalColor = Color.white;
|
||||
_rows[i].colors = colors;
|
||||
_rows[i].targetGraphic.color = i == _selected ? Accent : _rowFills[i];
|
||||
}
|
||||
_detail.Title.text = MainMenu.TitleSubtitle(Selected.Name, _groupLabels[_selected]);
|
||||
_detail.Title.text = MainMenu.TitleSubtitle(_groupLabels[_selected], Selected.Name);
|
||||
Focus(_launchButton.gameObject);
|
||||
Reveal((RectTransform) _rows[_selected].transform);
|
||||
}
|
||||
|
||||
// Scrolls the list pane just far enough that the selected row is inside its viewport.
|
||||
private void Reveal(RectTransform row)
|
||||
{
|
||||
var scroll = (_list.Content ?? _list.transform).GetComponentInParent<ScrollRect>();
|
||||
if (scroll == null || scroll.content == null) return;
|
||||
var view = scroll.viewport != null ? scroll.viewport : (RectTransform) scroll.transform;
|
||||
var rowBounds = RectTransformUtility.CalculateRelativeRectTransformBounds(scroll.content, row);
|
||||
var viewBounds = RectTransformUtility.CalculateRelativeRectTransformBounds(scroll.content, view);
|
||||
var shift = rowBounds.max.y > viewBounds.max.y ? viewBounds.max.y - rowBounds.max.y
|
||||
: rowBounds.min.y < viewBounds.min.y ? viewBounds.min.y - rowBounds.min.y : 0;
|
||||
scroll.content.anchoredPosition -= new Vector2(0, shift);
|
||||
}
|
||||
|
||||
// Launches the selection; focus is cleared first so a repeated Enter or a second click cannot launch twice.
|
||||
|
|
|
|||
2
Assets/Scripts/UI/ScenarioPicker.cs.meta
Normal file
2
Assets/Scripts/UI/ScenarioPicker.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 252d53300699b374db95fe7d7fcfd9b8
|
||||
Loading…
Reference in a new issue