From c5c7498a1475542683fdb2f359c60a257018e37b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 29 Jun 2026 10:52:47 +0100 Subject: [PATCH] Collapse menu tabs into MenuPanel --- Aetheria.State.Verify/Program.cs | 77 ++------ Assets/Scripts/UI/Menu/MenuPanel.cs | 139 ++++++++++++- GameCult.Aetheria.State.Unity.csproj | 1 - .../AetheriaRuntimeMenuTabsSurfaceBuilder.cs | 184 ------------------ ...heriaRuntimeMenuTabsSurfaceBuilder.cs.meta | 2 - 5 files changed, 144 insertions(+), 259 deletions(-) delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs.meta diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 3f88cfe2b..789bdcabc 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -586,7 +586,7 @@ Console.WriteLine("Main-menu input shell: the Eve input page delegates to the li Console.WriteLine("Runtime input screen shell: input rebinding lowers through an Eve UI Toolkit surface instead of the old drag/drop uGUI screen"); Console.WriteLine("Runtime input-screen authority: hotkey and menu handoff share the same fullscreen-menu primitive"); Console.WriteLine("Sector-map zone details shell: shared zone detail surface lowers selected zone state through Eve UI Toolkit"); -Console.WriteLine("Runtime menu tab shell: shared runtime tab surface lowers MenuPanel tab metadata through Eve UI Toolkit"); +Console.WriteLine("Runtime menu tab shell: MenuPanel owns its client-local tab surface and lowers it through Eve UI Toolkit"); Console.WriteLine("Inventory ship-settings shell: shared ship settings surface lowers selected ship state through Eve UI Toolkit"); Console.WriteLine("Inventory cargo-item shell: shared cargo detail surface lowers selected item state through Eve UI Toolkit"); Console.WriteLine("Inventory equipped-item shell: shared equipped item surface lowers selected equipment state and controls through Eve UI Toolkit"); @@ -4748,7 +4748,7 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) var menuPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuPanel.cs"); var localMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "LocalMenu.cs"); var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var menuTabsSurfaceBuilderPath = Path.Combine( + var legacyMenuTabsSurfaceBuilderPath = Path.Combine( root, "Packages", "org.gamecult.aetheria.state", @@ -4772,10 +4772,6 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) { throw new InvalidOperationException("Cannot verify runtime menu tab shell; ActionGameManager.cs is missing."); } - if (!File.Exists(menuTabsSurfaceBuilderPath)) - { - throw new InvalidOperationException("Cannot verify runtime menu tab shell; AetheriaRuntimeMenuTabsSurfaceBuilder.cs is missing."); - } if (!File.Exists(localStorySurfaceBuilderPath)) { throw new InvalidOperationException("Cannot verify runtime local story shell; AetheriaRuntimeLocalStorySurfaceBuilder.cs is missing."); @@ -4796,7 +4792,6 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) throw new InvalidOperationException("Cannot verify runtime menu tab shell; AetheriaEveUnitySurfaceHost.cs is missing."); } - var menuTabsSurfaceBuilder = File.ReadAllText(menuTabsSurfaceBuilderPath); var localStorySurfaceBuilder = File.ReadAllText(localStorySurfaceBuilderPath); var surfaceHost = File.ReadAllText(surfaceHostPath); var requiredSymbols = new[] @@ -4807,8 +4802,10 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) "HandleTabSurfaceCommand(", "AetheriaEveUnitySurfaceHost.RenderRuntime(", "AetheriaEveUnitySurfaceHost.Hide(_tabSurfaceDocument)", - "AetheriaRuntimeMenuTabsSurfaceBuilder.Build(", - "new AetheriaRuntimeMenuTabModelOption(", + "BuildTabSurfaceDocument(", + "new AetheriaRuntimeSurfaceDocument(", + "new AetheriaRuntimeSurfaceTree(", + "new AetheriaRuntimeSurfaceCommandTemplate(", "ResolveVisibleTabs(", "SetObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex)", "private AetheriaUnityObservedEntityIndex _observedEntityIndex;", @@ -4819,10 +4816,9 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) "new AetheriaUnityObservedDockingIndex(_observedEntityIndex)", "GetTabLabel(", "ToRuntimeTabKey(", - "AetheriaRuntimeMenuTabsSurfaceBuilder.NormalizeTabKey(tab.ToString())", - "AetheriaRuntimeMenuTabsSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeMenuTabCommandKind.SelectTab", - "string.Equals(command.TabKey, ToRuntimeTabKey(tab), StringComparison.Ordinal)", + "NormalizeTabKey(tab.ToString())", + "TryReadTabSurfaceCommand(request, out var tabKey)", + "string.Equals(tabKey, ToRuntimeTabKey(tab), StringComparison.Ordinal)", "TabButtons.gameObject.SetActive(false)" }; @@ -4869,6 +4865,10 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) "ResolveTabSurfaceDocument(", "new EveUiToolkitSurfaceLowerer()", "string.Equals(request.Command, AetheriaRuntimeMenuTabsSurfaceBuilder.CommandFor(", + "AetheriaRuntimeMenuTabsSurfaceBuilder", + "AetheriaRuntimeMenuTabsSurfaceCommands", + "AetheriaRuntimeMenuTabModelOption", + "AetheriaRuntimeMenuTabCommandKind", "ResolveClient().State.CurrentDocking()", "CultMeshReactiveDocument _currentDocking", ".ReactiveCurrentDocking(\"unity-runtime-menu-tabs\")", @@ -4903,36 +4903,10 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) "MenuPanel tab visibility must read managed typed current-docking state instead of direct docking caches or ActionGameManager observed adapters."); } - var requiredDocumentBuilderSymbols = new[] - { - "public sealed class AetheriaRuntimeMenuTabModelOption", - "public static string NormalizeTabKey(string tabKey)", - "public static AetheriaRuntimeSurfaceDocument Build(", - "public int Order { get; }", - ".OrderBy(tab => tab.Order)", - "var normalizedCurrent = NormalizeTabKey(currentTabKey)", - "var tabs = (visibleTabs ?? Array.Empty())", - "var selected = string.Equals(key, normalizedCurrent, StringComparison.Ordinal)", - "string.Equals(key, normalizedCurrent, StringComparison.Ordinal)" - }; - var missingDocumentBuilderSymbols = requiredDocumentBuilderSymbols - .Where(symbol => !menuTabsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDocumentBuilderSymbols.Length > 0) + if (File.Exists(legacyMenuTabsSurfaceBuilderPath)) { throw new InvalidOperationException( - "Shared runtime menu tab surface builder no longer owns tab document composition semantics: " + - string.Join(", ", missingDocumentBuilderSymbols)); - } - - if (menuTabsSurfaceBuilder.Contains("public static AetheriaRuntimeMenuTabsSurfaceState Compose(", StringComparison.Ordinal) || - menuTabsSurfaceBuilder.Contains("public static AetheriaRuntimeMenuTabsSurfaceState Project(", StringComparison.Ordinal) || - menuTabsSurfaceBuilder.Contains("AetheriaRuntimeMenuTabsSurfaceState", StringComparison.Ordinal) || - menuTabsSurfaceBuilder.Contains("AetheriaRuntimeMenuTabSurfaceEntry", StringComparison.Ordinal) || - menuTabsSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime menu tab composition must emit the typed surface document directly; do not rebuild a shadow SurfaceState/Entry projection layer."); + "Runtime menu tabs are client-local UI; do not keep a shared MenuTabsSurfaceBuilder or projection shell for them."); } var requiredDockedStoryObserverSymbols = new[] @@ -5010,27 +4984,6 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) "Runtime local story composition must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer."); } - var requiredBuilderSymbols = new[] - { - "public static class AetheriaRuntimeMenuTabsSurfaceBuilder", - "public const string SurfaceId = \"aetheria.runtime_menu.tabs\"", - "CommandFor(string tabKey)", - "public enum AetheriaRuntimeMenuTabCommandKind", - "public readonly struct AetheriaRuntimeMenuTabCommand", - "public static class AetheriaRuntimeMenuTabsSurfaceCommands", - "public static bool TryRead(", - "public static AetheriaRuntimeSurfaceDocument Build(", - "var tabs = (visibleTabs ?? Array.Empty())" - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !menuTabsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime menu tab surface builder no longer owns the tab shell contract: " + - string.Join(", ", missingBuilderSymbols)); - } } static void RequireInventoryShipSettingsUseEveSurface(string root) diff --git a/Assets/Scripts/UI/Menu/MenuPanel.cs b/Assets/Scripts/UI/Menu/MenuPanel.cs index ab73779ab..cc362cd60 100644 --- a/Assets/Scripts/UI/Menu/MenuPanel.cs +++ b/Assets/Scripts/UI/Menu/MenuPanel.cs @@ -13,6 +13,9 @@ using UnityEngine.UIElements; public class MenuPanel : MonoBehaviour { + private const string TabSurfaceId = "aetheria.runtime_menu.tabs"; + private const string TabCommandPrefix = "aetheria.runtime_menu.tab."; + [Serializable] private sealed class MenuTabBinding { @@ -121,13 +124,9 @@ public class MenuPanel : MonoBehaviour transform, _tabSurfaceDocument, "Aetheria Runtime Menu Tabs Surface", - AetheriaRuntimeMenuTabsSurfaceBuilder.Build( + BuildTabSurfaceDocument( ToRuntimeTabKey(CurrentTab), - ResolveVisibleTabs() - .Select(tabBinding => new AetheriaRuntimeMenuTabModelOption( - ToRuntimeTabKey(tabBinding.Tab), - GetTabLabel(tabBinding), - (int)tabBinding.Tab)), + ResolveVisibleTabs(), DateTime.UtcNow.ToString("O")), HandleTabSurfaceCommand, _tabSurfaceChrome); @@ -135,7 +134,7 @@ public class MenuPanel : MonoBehaviour private void HandleTabSurfaceCommand(EveSurfaceCommandRequest request) { - if (!AetheriaRuntimeMenuTabsSurfaceCommands.TryRead(request, out var command)) + if (!TryReadTabSurfaceCommand(request, out var tabKey)) { Debug.LogWarning($"Unknown runtime menu tab command: {request?.Command}"); return; @@ -143,8 +142,7 @@ public class MenuPanel : MonoBehaviour foreach (var tab in _tabs.Keys) { - if (command.Kind == AetheriaRuntimeMenuTabCommandKind.SelectTab && - string.Equals(command.TabKey, ToRuntimeTabKey(tab), StringComparison.Ordinal)) + if (string.Equals(tabKey, ToRuntimeTabKey(tab), StringComparison.Ordinal)) { ShowTab(tab); return; @@ -193,7 +191,128 @@ public class MenuPanel : MonoBehaviour private static string ToRuntimeTabKey(MenuTab tab) { - return AetheriaRuntimeMenuTabsSurfaceBuilder.NormalizeTabKey(tab.ToString()); + return NormalizeTabKey(tab.ToString()); + } + + private static string NormalizeTabKey(string tabKey) + { + return string.IsNullOrWhiteSpace(tabKey) + ? "unknown" + : tabKey.Trim().ToLowerInvariant(); + } + + private static string TabCommandFor(string tabKey) + { + return $"{TabCommandPrefix}{NormalizeTabKey(tabKey)}"; + } + + private static bool TryReadTabSurfaceCommand(EveSurfaceCommandRequest request, out string tabKey) + { + tabKey = ""; + if (request == null || + !string.Equals(request.SurfaceId, TabSurfaceId, StringComparison.Ordinal)) + return false; + + var commandText = request.Operation?.OperationId ?? ""; + if (!commandText.StartsWith(TabCommandPrefix, StringComparison.Ordinal)) + return false; + + tabKey = commandText.Substring(TabCommandPrefix.Length); + return !string.IsNullOrWhiteSpace(tabKey); + } + + private static AetheriaRuntimeSurfaceDocument BuildTabSurfaceDocument( + string currentTabKey, + IEnumerable visibleTabs, + string updatedAtUtc) + { + var normalizedCurrent = NormalizeTabKey(currentTabKey); + var tabs = (visibleTabs ?? Array.Empty()) + .Where(tab => tab != null) + .OrderBy(tab => (int)tab.Tab) + .Select(tab => + { + var key = ToRuntimeTabKey(tab.Tab); + var label = GetTabLabel(tab); + return new + { + Key = key, + Label = label, + Selected = string.Equals(key, normalizedCurrent, StringComparison.Ordinal) + }; + }) + .ToArray(); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "runtime.menu", + title: "Runtime Menu Tabs", + version: 1, + updatedAtUtc: updatedAtUtc ?? "", + surface: new AetheriaRuntimeSurfaceTree( + TabSurfaceId, + TabSurfaceNode( + $"{TabSurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + TabSurfaceText( + $"{TabSurfaceId}.current", + $"Current: {normalizedCurrent}"), + TabSurfaceButtonRow( + $"{TabSurfaceId}.tabs", + tabs + .Select(tab => TabSurfaceButton( + $"{TabSurfaceId}.{SafeSurfaceId(tab.Key)}", + tab.Selected ? $"{tab.Label} *" : tab.Label, + TabCommandFor(tab.Key))) + .ToArray())), + Array.Empty()), + commands: tabs + .Select(tab => new AetheriaRuntimeSurfaceCommandTemplate( + TabCommandFor(tab.Key), + tab.Label, + AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport)) + .ToArray()); + } + + private static string SafeSurfaceId(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return "empty"; + + return new string(value + .Select(character => char.IsLetterOrDigit(character) ? char.ToLowerInvariant(character) : '-') + .ToArray()).Trim('-'); + } + + private static AetheriaRuntimeSurfaceComponent TabSurfaceText(string id, string value) + { + return TabSurfaceNode(id, "text", new[] { ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent TabSurfaceButton(string id, string label, string command) + { + return TabSurfaceNode(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent TabSurfaceButtonRow( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return TabSurfaceNode(id, "row", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent TabSurfaceNode( + string id, + string kind, + IEnumerable<(string Key, string Value)> props, + params AetheriaRuntimeSurfaceComponent[] children) + { + return new AetheriaRuntimeSurfaceComponent( + id, + kind, + props.ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), + children ?? Array.Empty()); } // void Start() diff --git a/GameCult.Aetheria.State.Unity.csproj b/GameCult.Aetheria.State.Unity.csproj index 436af6ebf..3752c7865 100644 --- a/GameCult.Aetheria.State.Unity.csproj +++ b/GameCult.Aetheria.State.Unity.csproj @@ -109,7 +109,6 @@ - diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs deleted file mode 100644 index ec49e97a5..000000000 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using GameCult.Eve.Surface; - -namespace GameCult.Aetheria.State.Verse -{ - public sealed class AetheriaRuntimeMenuTabModelOption - { - public AetheriaRuntimeMenuTabModelOption(string key, string label, int order) - { - Key = key ?? ""; - Label = label ?? ""; - Order = order; - } - - public string Key { get; } - public string Label { get; } - public int Order { get; } - } - - public static class AetheriaRuntimeMenuTabsSurfaceBuilder - { - public const string SurfaceId = "aetheria.runtime_menu.tabs"; - - public static string NormalizeTabKey(string tabKey) - { - return string.IsNullOrWhiteSpace(tabKey) - ? "unknown" - : tabKey.Trim().ToLowerInvariant(); - } - - public static string CommandFor(string tabKey) - { - return $"aetheria.runtime_menu.tab.{NormalizeTabKey(tabKey)}"; - } - - public static AetheriaRuntimeSurfaceDocument Build( - string currentTabKey, - IEnumerable visibleTabs, - string updatedAtUtc, - long version = 1) - { - var normalizedCurrent = NormalizeTabKey(currentTabKey); - var tabs = (visibleTabs ?? Array.Empty()) - .Where(tab => tab != null) - .OrderBy(tab => tab.Order) - .ThenBy(tab => NormalizeTabKey(tab.Key), StringComparer.Ordinal) - .Select(tab => - { - var key = NormalizeTabKey(tab.Key); - var label = string.IsNullOrWhiteSpace(tab.Label) ? key : tab.Label; - var selected = string.Equals(key, normalizedCurrent, StringComparison.Ordinal); - return new - { - Key = key, - Label = label, - Selected = selected - }; - }) - .ToArray(); - - var commands = tabs - .Select(tab => new AetheriaRuntimeSurfaceCommandTemplate( - CommandFor(tab.Key), - tab.Label, - AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport)) - .ToArray(); - var buttons = tabs - .Select(tab => Button( - $"{SurfaceId}.{SafeId(tab.Key)}", - tab.Selected ? $"{tab.Label} *" : tab.Label, - CommandFor(tab.Key))) - .ToArray(); - - return new AetheriaRuntimeSurfaceDocument( - providerId: "aetheria", - providerKind: "runtime.menu", - title: "Runtime Menu Tabs", - version: version, - updatedAtUtc: updatedAtUtc ?? "", - surface: new AetheriaRuntimeSurfaceTree( - SurfaceId, - Node( - $"{SurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - Text( - $"{SurfaceId}.current", - $"Current: {normalizedCurrent}"), - ButtonRow($"{SurfaceId}.tabs", buttons)), - Array.Empty()), - commands: commands); - } - - private static string SafeId(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return "empty"; - - return new string(value - .Select(character => char.IsLetterOrDigit(character) ? char.ToLowerInvariant(character) : '-') - .ToArray()).Trim('-'); - } - - private static AetheriaRuntimeSurfaceComponent Text(string id, string value) - { - return Node(id, "text", new[] { ("value", value ?? "") }); - } - - private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) - { - return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); - } - - private static AetheriaRuntimeSurfaceComponent ButtonRow( - string id, - params AetheriaRuntimeSurfaceComponent[] children) - { - return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); - } - - private static AetheriaRuntimeSurfaceComponent Node( - string id, - string kind, - IEnumerable<(string Key, string Value)> props, - params AetheriaRuntimeSurfaceComponent[] children) - { - return new AetheriaRuntimeSurfaceComponent( - id, - kind, - props.ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), - children ?? Array.Empty()); - } - } - - public enum AetheriaRuntimeMenuTabCommandKind - { - Unknown = 0, - SelectTab = 1 - } - - public readonly struct AetheriaRuntimeMenuTabCommand - { - public AetheriaRuntimeMenuTabCommand( - AetheriaRuntimeMenuTabCommandKind kind, - string tabKey) - { - Kind = kind; - TabKey = tabKey ?? ""; - } - - public AetheriaRuntimeMenuTabCommandKind Kind { get; } - public string TabKey { get; } - } - - public static class AetheriaRuntimeMenuTabsSurfaceCommands - { - private const string CommandPrefix = "aetheria.runtime_menu.tab."; - - public static bool TryRead( - EveSurfaceCommandRequest request, - out AetheriaRuntimeMenuTabCommand command) - { - command = default; - if (request == null || - !string.Equals(request.SurfaceId, AetheriaRuntimeMenuTabsSurfaceBuilder.SurfaceId, StringComparison.Ordinal)) - return false; - - var commandText = request.Operation?.OperationId ?? ""; - if (!commandText.StartsWith(CommandPrefix, StringComparison.Ordinal)) - return false; - - var tabKey = commandText.Substring(CommandPrefix.Length); - if (string.IsNullOrWhiteSpace(tabKey)) - return false; - - command = new AetheriaRuntimeMenuTabCommand( - AetheriaRuntimeMenuTabCommandKind.SelectTab, - tabKey); - return true; - } - } -} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs.meta deleted file mode 100644 index df47f9655..000000000 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: df47c7665c31e014ba4522d79e65338f \ No newline at end of file