From 7c1dd5a17d69cf03faf286c34a6880355a8ace5a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 8 Aug 2026 16:26:00 +0100 Subject: [PATCH] Lower typed Eve select controls in Unity --- .../Runtime/EveUiToolkitSurfaceLowerer.cs | 47 +++++++++++++++++++ .../Editor/EveUiToolkitSurfaceLowererTests.cs | 29 ++++++++++++ 2 files changed, 76 insertions(+) diff --git a/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs b/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs index 52b2c47..f6e4fcd 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs +++ b/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs @@ -49,6 +49,11 @@ namespace GameCult.Eve.UnityUIToolkit if (IsHidden(component) || IsExternalProjectionRoot(component.Kind)) return element; + // Select options are semantic children consumed by the control itself. + // They are not separately lowered visual elements. + if (NormalizeKind(component.Kind) == "control.select") + return element; + foreach (var child in component.Children) { var loweredChild = LowerComponent(child, document, commandSink); @@ -114,6 +119,8 @@ namespace GameCult.Eve.UnityUIToolkit return InventoryItem(component, document, commandSink); case EveInventoryInteraction.DragSessionKind: return InventoryDragSession(component); + case "control.select": + return Select(component, document, commandSink); case "partition": { var element = new VisualElement(); @@ -458,6 +465,46 @@ namespace GameCult.Eve.UnityUIToolkit idempotencyKey: $"unity-uitoolkit-{Guid.NewGuid():N}"); } + private static VisualElement Select( + EveSurfaceComponent component, + EveSurfaceDocument document, + Action? commandSink) + { + var options = component.Children + .Where(child => NormalizeKind(child.Kind) == "control.option") + .Select(child => new + { + Label = child.GetProp("label", child.GetProp("value")), + Value = child.GetProp("value") + }) + .Where(option => !string.IsNullOrWhiteSpace(option.Value)) + .ToArray(); + var labels = options.Select(option => option.Label).ToList(); + var selectedValue = component.GetProp("value"); + var selectedIndex = Array.FindIndex(options, option => + string.Equals(option.Value, selectedValue, StringComparison.Ordinal)); + var field = new DropdownField(component.GetProp("label"), labels, Math.Max(0, selectedIndex)); + field.SetEnabled(!ParseBool(component.GetProp("disabled"))); + field.RegisterValueChangedCallback(change => + { + var option = options.FirstOrDefault(candidate => + string.Equals(candidate.Label, change.newValue, StringComparison.Ordinal)); + if (option == null) + return; + var payload = new Dictionary(component.Props, StringComparer.Ordinal) + { + ["value"] = option.Value + }; + EmitCommand( + document, + component, + component.GetProp("command", component.GetProp("operationId")), + GameCult.Mesh.CultMesh.OperationPayload(payload), + commandSink); + }); + return field; + } + private static Label TitleLabel(string text) { var label = new Label(text); diff --git a/packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs b/packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs index 38b2fcb..c870112 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs @@ -11,6 +11,35 @@ namespace GameCult.Eve.UnityUIToolkit.Tests { public sealed class EveUiToolkitSurfaceLowererTests { + [Test] + public void SelectLowersProviderOptionsAndEmitsSelectedTypedValue() + { + EveSurfaceCommandRequest? emitted = null; + var select = Component( + "verse", + "control.select", + new Dictionary + { + ["label"] = "VERSE", + ["value"] = "local", + ["command"] = "eve.client.verse.select" + }, + new[] + { + Component("local", "control.option", new Dictionary { ["label"] = "Local", ["value"] = "local" }), + Component("hosted", "control.option", new Dictionary { ["label"] = "Hosted", ["value"] = "hosted" }) + }); + var root = new EveUiToolkitSurfaceLowerer().Lower(Document(select), request => emitted = request); + var field = root.Q(); + + Assert.That(field, Is.Not.Null); + Assert.That(field.choices, Is.EqualTo(new[] { "Local", "Hosted" })); + field.value = "Hosted"; + Assert.That(emitted, Is.Not.Null); + Assert.That(emitted!.Command, Is.EqualTo("eve.client.verse.select")); + Assert.That(emitted.PayloadFields["value"], Is.EqualTo("hosted")); + } + [Test] public void DefaultOptionsExposeSaiNornAndTeXProjectionAdapters() {