diff --git a/packages/org.gamecult.eve.unity-scene/README.md b/packages/org.gamecult.eve.unity-scene/README.md index 085a0f6..005e991 100644 --- a/packages/org.gamecult.eve.unity-scene/README.md +++ b/packages/org.gamecult.eve.unity-scene/README.md @@ -5,7 +5,12 @@ interactive worlds. Providers implement the surface-document, asset-manifest, command-sink, and receipt-source ports. `EveUnityPlayableWorldClientBootstrap` wires those ports -to the generic scene host, input driver, camera rig, and `GameObject` sink. +to the generic scene host, input driver, camera rig, `GameObject` sink, and UI +Toolkit surface overlay. The overlay lowers provider-owned panes, metrics, +progress, inventory, and controls from the same Eve document used by the scene. +Stable structures update their native controls in place; only structural +changes rebuild the visual tree. Scene, field, and reactive render subtrees are +owned by their native lowerers and are not duplicated into empty UI elements. `EveUnityCultMeshPlayableWorldProvider` is the runtime composition root for a networked client. Give it one Verse rendezvous endpoint and it discovers an diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs index 678b4a9..de42f6d 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs @@ -121,6 +121,9 @@ namespace GameCult.Eve.UnityScene var actionBar = GetComponent(); if (actionBar == null) actionBar = gameObject.AddComponent(); actionBar.Bind(this); + var uiOverlay = GetComponent(); + if (uiOverlay == null) uiOverlay = gameObject.AddComponent(); + uiOverlay.Bind(providerSurfaceDocuments!, commandSink!); var presentation = Runtime.Connect(); ConnectionEpoch++; diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityUiToolkitOverlay.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityUiToolkitOverlay.cs new file mode 100644 index 0000000..f4a6202 --- /dev/null +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityUiToolkitOverlay.cs @@ -0,0 +1,87 @@ +using System; +using GameCult.Eve.UnityUIToolkit; +using UnityEngine; +using UnityEngine.UIElements; + +#nullable enable + +namespace GameCult.Eve.UnityScene +{ + public sealed class EveUnityUiToolkitOverlay : MonoBehaviour + { + private IEveUnitySceneProviderSurfaceDocumentSource? _documents; + private IEveUnitySceneCommandSink? _commands; + private UIDocument? _document; + private EveUiToolkitSurfacePresenter? _presenter; + private VisualElement? _mountedRoot; + + public VisualElement? Root => _mountedRoot; + + public void Bind(MonoBehaviour documentSource, MonoBehaviour commandSink) + { + if (documentSource is not IEveUnitySceneProviderSurfaceDocumentSource documents) + throw new ArgumentException( + $"{nameof(documentSource)} must implement {nameof(IEveUnitySceneProviderSurfaceDocumentSource)}.", + nameof(documentSource)); + if (commandSink is not IEveUnitySceneCommandSink commands) + throw new ArgumentException( + $"{nameof(commandSink)} must implement {nameof(IEveUnitySceneCommandSink)}.", + nameof(commandSink)); + + Unsubscribe(); + _documents = documents; + _commands = commands; + _documents.DocumentAvailable += OnDocumentAvailable; + EnsureDocument(); + Present(_documents.CurrentDocument); + } + + private void OnDestroy() => Unsubscribe(); + + private void OnDocumentAvailable(EveUnitySceneProviderSurfaceDocument document) => Present(document); + + private void Present(EveUnitySceneProviderSurfaceDocument document) + { + if (document == null || _commands == null) return; + _presenter ??= new EveUiToolkitSurfacePresenter(); + var replaced = _presenter.Present(document.SurfaceDocument, _commands.Submit); + if (replaced && _presenter.Root != null) + { + _mountedRoot?.RemoveFromHierarchy(); + _mountedRoot = _presenter.Root; + _mountedRoot.AddToClassList("eve-playable-world-ui-overlay"); + } + AttachToDocument(); + } + + private void EnsureDocument() + { + _document = GetComponent(); + if (_document != null) return; + var panel = ScriptableObject.CreateInstance(); + panel.name = "Eve playable-world surface panel"; + panel.scaleMode = PanelScaleMode.ScaleWithScreenSize; + panel.referenceResolution = new Vector2Int(1920, 1080); + panel.match = 0.5f; + _document = gameObject.AddComponent(); + _document.panelSettings = panel; + _document.sortingOrder = 100; + } + + private void AttachToDocument() + { + EnsureDocument(); + var documentRoot = _document?.rootVisualElement; + if (_mountedRoot != null && _mountedRoot.parent == null && documentRoot != null) + documentRoot.Add(_mountedRoot); + } + + private void Unsubscribe() + { + if (_documents != null) + _documents.DocumentAvailable -= OnDocumentAvailable; + _documents = null; + _commands = null; + } + } +} diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityUiToolkitOverlay.cs.meta b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityUiToolkitOverlay.cs.meta new file mode 100644 index 0000000..672f84b --- /dev/null +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityUiToolkitOverlay.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 843283a94b8a41bab85db52bd84aa59a diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/GameCult.Eve.UnityScene.asmdef b/packages/org.gamecult.eve.unity-scene/Runtime/GameCult.Eve.UnityScene.asmdef index 334de2e..6f91767 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/GameCult.Eve.UnityScene.asmdef +++ b/packages/org.gamecult.eve.unity-scene/Runtime/GameCult.Eve.UnityScene.asmdef @@ -4,6 +4,7 @@ "references": [ "GameCult.Eve.Surface", "GameCult.Eve.PluginFields", + "GameCult.Eve.UnityUIToolkit", "Unity.Mathematics", "Unity.RenderPipelines.Core.Runtime", "Unity.RenderPipelines.Universal.Runtime" diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs index bd12229..70f2f48 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -1098,6 +1098,8 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(bootstrap.SceneRoot!.childCount, Is.EqualTo(3)); Assert.That(hostObject.GetComponent(), Is.Not.Null); Assert.That(hostObject.GetComponent(), Is.Not.Null); + Assert.That(hostObject.GetComponent(), Is.Not.Null); + Assert.That(hostObject.GetComponent().Root, Is.Not.Null); Assert.That(bootstrap.CameraTransform, Is.Not.Null); Assert.That(provider.RefreshCount, Is.EqualTo(1)); diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef b/packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef index 75ef6c3..5617adc 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef @@ -5,6 +5,7 @@ "GameCult.Eve.Surface", "GameCult.Eve.UnityScene", "GameCult.Eve.UnityScene.CultMesh", + "GameCult.Eve.UnityUIToolkit", "GameCult.Eve.PluginFields", "Unity.RenderPipelines.Core.Runtime", "Unity.RenderPipelines.Universal.Runtime" diff --git a/packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json b/packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json index 8603e65..4e07fed 100644 --- a/packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json +++ b/packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json @@ -113,6 +113,7 @@ "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityUiToolkitOverlay.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientBootstrap.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs", @@ -159,10 +160,17 @@ "requiredPackageDependencies": [ { "packageName": "org.gamecult.eve.surface", - "version": "0.1.0", + "version": "0.2.4", "packageManager": "upm", "ownerRepo": "EveUnity", "purpose": "Shared Unity DTOs for gamecult.eve.surface.v1 documents." + }, + { + "packageName": "org.gamecult.eve.unity-uitoolkit", + "version": "0.1.2", + "packageManager": "upm", + "ownerRepo": "EveUnity", + "purpose": "Generic UI Toolkit lowering for provider-owned Eve panes, metrics, progress, text, and controls." } ] }, diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index 16751a7..afe87df 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -1,13 +1,14 @@ { "name": "org.gamecult.eve.unity-scene", "displayName": "Eve Unity Scene Lowering", - "version": "0.3.100", + "version": "0.3.101", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": { "org.gamecult.cultlib": "1.0.43", "org.gamecult.eve.plugin-fields": "0.2.3", "org.gamecult.eve.surface": "0.2.4", + "org.gamecult.eve.unity-uitoolkit": "0.1.2", "com.unity.mathematics": "1.3.2", "com.unity.render-pipelines.universal": "17.4.0", "com.unity.modules.particlesystem": "1.0.0"