930 lines
31 KiB
C#
930 lines
31 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using GameCult.Aetheria.EveRuntime;
|
|
using GameCult.Aetheria.State.Verse;
|
|
using GameCult.Eve.Surface;
|
|
using GameCult.Mesh;
|
|
using TMPro;
|
|
using UniRx;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.UIElements;
|
|
using UiButton = UnityEngine.UIElements.Button;
|
|
using TextCoreFontAsset = UnityEngine.TextCore.Text.FontAsset;
|
|
using GlyphRenderMode = UnityEngine.TextCore.LowLevel.GlyphRenderMode;
|
|
|
|
public class MainMenu : MonoBehaviour
|
|
{
|
|
public VolumeCloudRenderer CloudRenderer;
|
|
public GameSettings Settings;
|
|
public ConfirmationDialog Dialog;
|
|
public bool InGame;
|
|
public float FadeTime = 0.5f;
|
|
public float FadeDistance = 512f;
|
|
public float FadeAlphaExponent = 2f;
|
|
public float FadePositionExponent = 2f;
|
|
public float HoverSpacing = 8f;
|
|
public float HoverAnimationDuration = 0.16f;
|
|
|
|
private UIDocument _currentMenuSurfaceDocument;
|
|
private UIDocument _nextMenuSurfaceDocument;
|
|
private Func<bool> _canOpenRuntimeInputScreen;
|
|
private Action _openRuntimeInputScreen;
|
|
private bool _fadeFromRight;
|
|
private bool _fading;
|
|
private float _fadeLerp;
|
|
private readonly List<MenuHoverButton> _hoverButtons = new List<MenuHoverButton>();
|
|
private static TextCoreFontAsset _titleFont;
|
|
private static TextCoreFontAsset _labelFont;
|
|
private readonly AetheriaEveUnitySurfaceChrome _menuSurfaceChrome = new AetheriaEveUnitySurfaceChrome
|
|
{
|
|
UseShell = false,
|
|
RootAlignItems = Align.FlexStart,
|
|
RootJustifyContent = Justify.FlexStart,
|
|
RootPaddingLeft = 96f,
|
|
RootPaddingTop = 92f,
|
|
RootPaddingRight = 24f,
|
|
RootPaddingBottom = 24f,
|
|
RootBackgroundColor = new Color(0f, 0f, 0f, 0f),
|
|
RootPickingMode = PickingMode.Position
|
|
};
|
|
|
|
void Start()
|
|
{
|
|
ShowMain(false);
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
UpdateMenuFade();
|
|
UpdateHoverButtons();
|
|
}
|
|
|
|
private void ShowMain(bool animateFromRight = true)
|
|
{
|
|
var stateBoot = CurrentStateBoot();
|
|
var sectorMap = ResolveSectorMap(stateBoot);
|
|
var verseHost = ResolveVerseHostSettings(stateBoot);
|
|
var playerSettings = ResolvePlayerSettings(stateBoot);
|
|
RenderMenuSurface(
|
|
AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot(
|
|
stateBoot,
|
|
sectorMap,
|
|
verseHost,
|
|
playerSettings,
|
|
CanOpenRuntimeInputScreen(),
|
|
InGame,
|
|
DateTime.UtcNow.ToString("O")),
|
|
HandleMainSurfaceCommand,
|
|
animateFromRight);
|
|
}
|
|
|
|
private void HandleMainSurfaceCommand(EveSurfaceCommandRequest request)
|
|
{
|
|
if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command))
|
|
{
|
|
Debug.LogWarning("Unknown main menu command.");
|
|
return;
|
|
}
|
|
|
|
switch (command.Kind)
|
|
{
|
|
case AetheriaRuntimeMainMenuCommandKind.ContinueRun:
|
|
if (ResolveSectorMap(CurrentStateBoot()) == null)
|
|
{
|
|
Debug.LogWarning("Main-menu Continue requested without a typed Aetheria sector-map projection.");
|
|
ShowMain(false);
|
|
return;
|
|
}
|
|
|
|
ContinueGame();
|
|
return;
|
|
case AetheriaRuntimeMainMenuCommandKind.NewGame:
|
|
StartNewGame();
|
|
return;
|
|
case AetheriaRuntimeMainMenuCommandKind.ShowSettings:
|
|
ShowSettings(true);
|
|
return;
|
|
case AetheriaRuntimeMainMenuCommandKind.Quit:
|
|
Application.Quit();
|
|
return;
|
|
default:
|
|
Debug.LogWarning($"Unhandled main menu command kind: {command.Kind}");
|
|
return;
|
|
}
|
|
}
|
|
|
|
private void StartNewGame()
|
|
{
|
|
if (!TryStartDaemonObservedGame("Starting Daemon Run"))
|
|
{
|
|
ShowMain(false);
|
|
}
|
|
}
|
|
|
|
private bool TryStartDaemonObservedGame(string title)
|
|
{
|
|
var stateBoot = CurrentStateBoot();
|
|
var sectorMap = ResolveSectorMap(stateBoot);
|
|
if (sectorMap == null)
|
|
{
|
|
Debug.LogWarning($"Cannot start Aetheria observer scene without a typed Aetheria sector-map projection in {stateBoot.StateFilePath}.");
|
|
return false;
|
|
}
|
|
|
|
var generatorState = "Loading daemon sector";
|
|
Action<string> setState = s => generatorState = s;
|
|
|
|
HideMenuSurface();
|
|
Dialog.Clear();
|
|
Dialog.Title.text = title;
|
|
Dialog.AddProperty(() => generatorState);
|
|
Dialog.Show();
|
|
|
|
setState($"Observing sector-map frame {sectorMap.FrameId}");
|
|
SceneManager.LoadScene("ARPG");
|
|
return true;
|
|
}
|
|
|
|
private static AetheriaRuntimeStateBootReport CurrentStateBoot()
|
|
{
|
|
return AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory);
|
|
}
|
|
|
|
private AetheriaRuntimeSectorMapDocument ResolveSectorMap(AetheriaRuntimeStateBootReport stateBoot)
|
|
{
|
|
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
return AetheriaUnityRuntimeClientProvider
|
|
.RuntimeState(stateBoot, "unity-main-menu")
|
|
.SectorMap
|
|
.Latest();
|
|
}
|
|
catch (KeyNotFoundException ex) when (IsMissingDaemonFrame(ex))
|
|
{
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"Failed to bind typed Aetheria sector-map state for the main menu: {ex}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private AetheriaRuntimePlayerSettingsDocument ResolvePlayerSettings(AetheriaRuntimeStateBootReport stateBoot)
|
|
{
|
|
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
return AetheriaUnityRuntimeClientProvider
|
|
.RuntimeState(stateBoot, "unity-main-menu")
|
|
.PlayerSettings
|
|
.Latest();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"Failed to bind typed Aetheria player settings for the main menu: {ex}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private AetheriaRuntimeVerseHostSettingsDocument ResolveVerseHostSettings(AetheriaRuntimeStateBootReport stateBoot)
|
|
{
|
|
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
return AetheriaUnityRuntimeClientProvider
|
|
.RuntimeState(stateBoot, "unity-main-menu")
|
|
.VerseHostSettings
|
|
.Latest();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"Failed to bind typed Aetheria Verse host settings for the main menu: {ex}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static bool IsMissingDaemonFrame(KeyNotFoundException ex)
|
|
{
|
|
return ex?.Message?.Contains("daemon:aetheria.frame.latest.v1", StringComparison.Ordinal) == true;
|
|
}
|
|
|
|
private void ContinueGame()
|
|
{
|
|
if (!TryStartDaemonObservedGame("Continuing Daemon Run"))
|
|
{
|
|
ShowMain(false);
|
|
}
|
|
}
|
|
|
|
private void ShowSettings(bool animateFromRight = true)
|
|
{
|
|
RenderMenuSurface(
|
|
AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(DateTime.UtcNow.ToString("O")),
|
|
HandleSettingsSurfaceCommand,
|
|
animateFromRight);
|
|
}
|
|
|
|
private void ShowInputSettings(bool animateFromRight = true)
|
|
{
|
|
var stateBoot = CurrentStateBoot();
|
|
RenderMenuSurface(
|
|
AetheriaRuntimeMainMenuSurfaceBuilder.BuildInputSettings(
|
|
stateBoot,
|
|
ResolvePlayerSettings(stateBoot),
|
|
CanOpenRuntimeInputScreen(),
|
|
InGame,
|
|
DateTime.UtcNow.ToString("O")),
|
|
HandleInputSettingsSurfaceCommand,
|
|
animateFromRight);
|
|
}
|
|
|
|
private void ShowPlayerSettingsSurface(bool animateFromRight = true)
|
|
{
|
|
RenderMenuSurface(
|
|
AetheriaRuntimeMainMenuSurfaceBuilder.BuildPlayerSettingsShell(
|
|
ResolvePlayerSettings(CurrentStateBoot()),
|
|
DateTime.UtcNow.ToString("O")),
|
|
HandlePlayerSettingsSurfaceCommand,
|
|
animateFromRight);
|
|
}
|
|
|
|
private void ShowVerseSettingsSurface(bool animateFromRight = true)
|
|
{
|
|
var stateBoot = CurrentStateBoot();
|
|
RenderMenuSurface(
|
|
AetheriaRuntimeMainMenuSurfaceBuilder.BuildVerseSettingsShell(
|
|
AetheriaRuntimeClientTargetSurfaceBuilder.Build(
|
|
stateBoot,
|
|
ResolveVerseHostSettings(stateBoot),
|
|
DateTime.UtcNow.ToString("O"))),
|
|
HandleVerseSettingsSurfaceCommand,
|
|
animateFromRight);
|
|
}
|
|
|
|
private void RenderMenuSurface(
|
|
AetheriaRuntimeSurfaceDocument document,
|
|
Action<EveSurfaceCommandRequest> commandHandler,
|
|
bool animateFromRight)
|
|
{
|
|
var initialRender = _currentMenuSurfaceDocument == null;
|
|
var targetDocument = initialRender ? _currentMenuSurfaceDocument : _nextMenuSurfaceDocument;
|
|
targetDocument = AetheriaEveUnitySurfaceHost.RenderRuntime(
|
|
transform,
|
|
targetDocument,
|
|
initialRender ? "Aetheria Menu Surface" : "Aetheria Menu Surface Transition",
|
|
document,
|
|
commandHandler,
|
|
_menuSurfaceChrome,
|
|
sortingOrder: initialRender ? 1000 : 1001);
|
|
|
|
ApplyMenuStyles(targetDocument.rootVisualElement, IsRootMainMenu(document));
|
|
SetDocumentTransition(targetDocument, initialRender ? 0f : (animateFromRight ? FadeDistance : -FadeDistance), initialRender ? 1f : 0f);
|
|
|
|
if (initialRender)
|
|
{
|
|
_currentMenuSurfaceDocument = targetDocument;
|
|
return;
|
|
}
|
|
|
|
_nextMenuSurfaceDocument = targetDocument;
|
|
_fadeFromRight = animateFromRight;
|
|
_fadeLerp = 0f;
|
|
_fading = true;
|
|
}
|
|
|
|
private static bool IsRootMainMenu(AetheriaRuntimeSurfaceDocument document)
|
|
{
|
|
return string.Equals(
|
|
document?.Surface?.Id,
|
|
AetheriaRuntimeMainMenuCommands.RootSurfaceId,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
private void UpdateMenuFade()
|
|
{
|
|
if (!_fading)
|
|
return;
|
|
|
|
_fadeLerp += Time.unscaledDeltaTime / Mathf.Max(0.01f, FadeTime);
|
|
var t = Mathf.Clamp01(_fadeLerp);
|
|
var outgoing = Mathf.Pow(t, FadePositionExponent);
|
|
var incoming = Mathf.Pow(1f - t, FadePositionExponent);
|
|
var direction = _fadeFromRight ? 1f : -1f;
|
|
|
|
SetDocumentTransition(
|
|
_currentMenuSurfaceDocument,
|
|
-direction * FadeDistance * outgoing,
|
|
Mathf.Pow(1f - t, FadeAlphaExponent));
|
|
SetDocumentTransition(
|
|
_nextMenuSurfaceDocument,
|
|
direction * FadeDistance * incoming,
|
|
Mathf.Pow(t, FadeAlphaExponent));
|
|
|
|
if (_fadeLerp <= 1f)
|
|
return;
|
|
|
|
_fading = false;
|
|
AetheriaEveUnitySurfaceHost.Hide(_currentMenuSurfaceDocument);
|
|
var previous = _currentMenuSurfaceDocument;
|
|
_currentMenuSurfaceDocument = _nextMenuSurfaceDocument;
|
|
_nextMenuSurfaceDocument = previous;
|
|
SetDocumentTransition(_currentMenuSurfaceDocument, 0f, 1f);
|
|
}
|
|
|
|
private static void SetDocumentTransition(UIDocument document, float x, float opacity)
|
|
{
|
|
if (document?.rootVisualElement == null)
|
|
return;
|
|
|
|
document.rootVisualElement.style.translate = new Translate(new Length(x, LengthUnit.Pixel), 0f, 0f);
|
|
document.rootVisualElement.style.opacity = opacity;
|
|
}
|
|
|
|
private void UpdateHoverButtons()
|
|
{
|
|
for (var i = _hoverButtons.Count - 1; i >= 0; i--)
|
|
{
|
|
var hover = _hoverButtons[i];
|
|
if (hover.Button == null)
|
|
{
|
|
_hoverButtons.RemoveAt(i);
|
|
continue;
|
|
}
|
|
|
|
var direction = hover.Hovering && hover.Button.enabledInHierarchy ? 1f : -1f;
|
|
hover.Lerp = Mathf.Clamp01(hover.Lerp + Time.unscaledDeltaTime / Mathf.Max(0.01f, HoverAnimationDuration) * direction);
|
|
hover.Button.style.letterSpacing = HoverSpacing * hover.Lerp;
|
|
}
|
|
}
|
|
|
|
private void ApplyMenuStyles(VisualElement root, bool rootMainMenu)
|
|
{
|
|
if (root == null)
|
|
return;
|
|
|
|
StyleMenuTree(root, rootMainMenu);
|
|
ApplyDefaultTextFont(root);
|
|
|
|
var surface = rootMainMenu
|
|
? FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.root")
|
|
: FindFirstKind(root, "surface");
|
|
if (surface != null)
|
|
{
|
|
surface.style.flexGrow = 0f;
|
|
surface.style.width = rootMainMenu ? 720f : 560f;
|
|
surface.style.alignItems = Align.FlexStart;
|
|
surface.pickingMode = PickingMode.Position;
|
|
}
|
|
|
|
var title = FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.title") as Label;
|
|
if (title != null)
|
|
{
|
|
title.style.unityFontDefinition = new StyleFontDefinition(TitleFont);
|
|
title.style.fontSize = 86f;
|
|
title.style.color = new Color(0.82f, 0.95f, 1f, 0.96f);
|
|
title.style.unityFontStyleAndWeight = FontStyle.Normal;
|
|
title.style.unityTextAlign = TextAnchor.MiddleRight;
|
|
title.style.marginLeft = 0f;
|
|
title.style.marginBottom = -12f;
|
|
title.style.width = 720f;
|
|
}
|
|
|
|
var subtitle = FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.subtitle") as Label;
|
|
if (subtitle != null)
|
|
{
|
|
subtitle.style.unityFontDefinition = new StyleFontDefinition(TitleFont);
|
|
subtitle.style.fontSize = 39f;
|
|
subtitle.style.color = new Color(0.82f, 0.95f, 1f, 0.92f);
|
|
subtitle.style.unityFontStyleAndWeight = FontStyle.Normal;
|
|
subtitle.style.unityTextAlign = TextAnchor.MiddleRight;
|
|
subtitle.style.marginTop = 0f;
|
|
subtitle.style.marginBottom = 36f;
|
|
subtitle.style.width = 720f;
|
|
}
|
|
|
|
RegisterHoverButtons(root);
|
|
}
|
|
|
|
private static void ApplyDefaultTextFont(VisualElement root)
|
|
{
|
|
root.Query<TextElement>().ForEach(element =>
|
|
element.style.unityFontDefinition = new StyleFontDefinition(LabelFont));
|
|
}
|
|
|
|
private void StyleMenuTree(VisualElement element, bool rootMainMenu)
|
|
{
|
|
if (element == null)
|
|
return;
|
|
|
|
element.pickingMode = PickingMode.Position;
|
|
|
|
if (element is UiButton button)
|
|
StyleMenuButton(button, rootMainMenu);
|
|
else if (element is TextField textField)
|
|
StyleMenuTextField(textField, rootMainMenu);
|
|
else if (element is Label label)
|
|
StyleMenuLabel(label, rootMainMenu);
|
|
else
|
|
StyleMenuContainer(element, rootMainMenu);
|
|
|
|
foreach (var child in element.Children())
|
|
StyleMenuTree(child, rootMainMenu);
|
|
}
|
|
|
|
private static void StyleMenuContainer(VisualElement element, bool rootMainMenu)
|
|
{
|
|
element.style.backgroundColor = new Color(0f, 0f, 0f, 0f);
|
|
element.style.borderLeftWidth = 0f;
|
|
element.style.borderRightWidth = 0f;
|
|
element.style.borderTopWidth = 0f;
|
|
element.style.borderBottomWidth = 0f;
|
|
element.style.marginLeft = 0f;
|
|
element.style.marginTop = 0f;
|
|
element.style.marginBottom = 0f;
|
|
|
|
if (element.ClassListContains("eve-row"))
|
|
{
|
|
element.style.flexDirection = FlexDirection.Row;
|
|
element.style.flexWrap = Wrap.Wrap;
|
|
element.style.alignItems = Align.Center;
|
|
element.style.marginTop = 2f;
|
|
element.style.marginBottom = 6f;
|
|
return;
|
|
}
|
|
|
|
if (element.ClassListContains("eve-card"))
|
|
{
|
|
element.style.flexDirection = FlexDirection.Column;
|
|
element.style.alignItems = Align.FlexStart;
|
|
element.style.width = rootMainMenu ? 680f : 540f;
|
|
element.style.marginBottom = 18f;
|
|
element.style.paddingTop = 0f;
|
|
element.style.paddingBottom = 2f;
|
|
return;
|
|
}
|
|
|
|
if (element.ClassListContains("eve-metric") || element.ClassListContains("eve-field"))
|
|
{
|
|
element.style.flexDirection = FlexDirection.Row;
|
|
element.style.alignItems = Align.Center;
|
|
element.style.width = rootMainMenu ? 320f : 520f;
|
|
element.style.marginBottom = 5f;
|
|
return;
|
|
}
|
|
|
|
if (element.ClassListContains("eve-kind-control-text"))
|
|
{
|
|
element.style.flexDirection = FlexDirection.Row;
|
|
element.style.alignItems = Align.Center;
|
|
element.style.width = rootMainMenu ? 520f : 520f;
|
|
element.style.marginBottom = rootMainMenu ? 6f : 10f;
|
|
return;
|
|
}
|
|
|
|
if (element.ClassListContains("eve-surface-root") || element.ClassListContains("eve-kind-surface"))
|
|
{
|
|
element.style.flexDirection = FlexDirection.Column;
|
|
element.style.alignItems = Align.FlexStart;
|
|
element.style.width = rootMainMenu ? 680f : 560f;
|
|
return;
|
|
}
|
|
|
|
element.style.flexDirection = FlexDirection.Column;
|
|
element.style.alignItems = Align.FlexStart;
|
|
}
|
|
|
|
private static void StyleMenuLabel(Label label, bool rootMainMenu)
|
|
{
|
|
label.style.color = new Color(0.86f, 0.98f, 1f, 0.94f);
|
|
label.style.unityFontDefinition = new StyleFontDefinition(LabelFont);
|
|
label.style.unityFontStyleAndWeight = FontStyle.Normal;
|
|
label.style.unityTextAlign = TextAnchor.MiddleLeft;
|
|
label.style.whiteSpace = WhiteSpace.Normal;
|
|
label.style.marginLeft = 0f;
|
|
label.style.marginTop = 0f;
|
|
label.style.marginBottom = 0f;
|
|
|
|
if (label.ClassListContains("eve-title"))
|
|
{
|
|
label.style.fontSize = rootMainMenu ? 24f : 24f;
|
|
label.style.unityFontStyleAndWeight = FontStyle.Normal;
|
|
label.style.marginTop = rootMainMenu ? 0f : 2f;
|
|
label.style.marginBottom = rootMainMenu ? 0f : 10f;
|
|
label.style.maxWidth = rootMainMenu ? 620f : 520f;
|
|
return;
|
|
}
|
|
|
|
if (label.ClassListContains("eve-muted"))
|
|
{
|
|
label.style.fontSize = rootMainMenu ? 18f : 15f;
|
|
label.style.color = new Color(0.78f, 0.92f, 0.98f, 0.84f);
|
|
label.style.width = rootMainMenu ? 120f : 190f;
|
|
label.style.marginRight = 12f;
|
|
return;
|
|
}
|
|
|
|
if (label.ClassListContains("eve-value"))
|
|
{
|
|
label.style.fontSize = rootMainMenu ? 20f : 16f;
|
|
label.style.unityFontStyleAndWeight = FontStyle.Normal;
|
|
label.style.maxWidth = rootMainMenu ? 360f : 300f;
|
|
return;
|
|
}
|
|
|
|
label.style.fontSize = rootMainMenu ? 24f : 16f;
|
|
label.style.maxWidth = rootMainMenu ? 620f : 520f;
|
|
label.style.marginBottom = rootMainMenu ? 0f : 10f;
|
|
}
|
|
|
|
private static void StyleMenuTextField(TextField field, bool rootMainMenu)
|
|
{
|
|
field.style.width = rootMainMenu ? 360f : 520f;
|
|
field.style.height = rootMainMenu ? 30f : 28f;
|
|
field.style.marginLeft = 0f;
|
|
field.style.marginTop = 0f;
|
|
field.style.marginBottom = rootMainMenu ? 4f : 8f;
|
|
field.style.color = new Color(0.86f, 0.98f, 1f, 0.96f);
|
|
field.style.unityFontDefinition = new StyleFontDefinition(LabelFont);
|
|
field.style.fontSize = rootMainMenu ? 18f : 15f;
|
|
field.pickingMode = PickingMode.Position;
|
|
|
|
field.labelElement.style.minWidth = rootMainMenu ? 100f : 190f;
|
|
field.labelElement.style.width = rootMainMenu ? 100f : 190f;
|
|
field.labelElement.style.color = new Color(0.78f, 0.92f, 0.98f, 0.84f);
|
|
field.labelElement.style.unityFontDefinition = new StyleFontDefinition(LabelFont);
|
|
}
|
|
|
|
private static void StyleMenuButton(UiButton button, bool rootMainMenu)
|
|
{
|
|
button.style.backgroundColor = new Color(0f, 0f, 0f, 0f);
|
|
button.style.borderLeftWidth = 0f;
|
|
button.style.borderRightWidth = 0f;
|
|
button.style.borderTopWidth = 0f;
|
|
button.style.borderBottomWidth = 0f;
|
|
button.style.color = new Color(0.86f, 0.98f, 1f, 0.96f);
|
|
button.style.unityFontDefinition = new StyleFontDefinition(LabelFont);
|
|
button.style.fontSize = rootMainMenu ? 24f : 16f;
|
|
button.style.unityFontStyleAndWeight = FontStyle.Normal;
|
|
button.style.unityTextAlign = TextAnchor.MiddleLeft;
|
|
button.style.width = rootMainMenu ? 220f : 230f;
|
|
button.style.height = rootMainMenu ? 32f : 24f;
|
|
button.style.marginLeft = 0f;
|
|
button.style.marginTop = 0f;
|
|
button.style.marginRight = rootMainMenu ? 0f : 14f;
|
|
button.style.marginBottom = rootMainMenu ? 0f : 4f;
|
|
button.style.paddingLeft = 0f;
|
|
button.style.paddingRight = 0f;
|
|
button.style.paddingTop = 0f;
|
|
button.style.paddingBottom = 0f;
|
|
button.style.letterSpacing = 0f;
|
|
button.focusable = true;
|
|
button.pickingMode = PickingMode.Position;
|
|
}
|
|
|
|
private void RegisterHoverButtons(VisualElement root)
|
|
{
|
|
foreach (var button in FindButtons(root))
|
|
{
|
|
var hover = new MenuHoverButton(button);
|
|
_hoverButtons.Add(hover);
|
|
button.RegisterCallback<PointerEnterEvent>(_ => hover.Hovering = true);
|
|
button.RegisterCallback<PointerLeaveEvent>(_ => hover.Hovering = false);
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<UiButton> FindButtons(VisualElement root)
|
|
{
|
|
if (root == null)
|
|
yield break;
|
|
|
|
if (root is UiButton button)
|
|
yield return button;
|
|
|
|
foreach (var child in root.Children())
|
|
{
|
|
foreach (var nested in FindButtons(child))
|
|
yield return nested;
|
|
}
|
|
}
|
|
|
|
private static VisualElement FindFirstKind(VisualElement root, string kind)
|
|
{
|
|
if (root == null)
|
|
return null;
|
|
|
|
if (root.ClassListContains($"eve-kind-{kind}"))
|
|
return root;
|
|
|
|
foreach (var child in root.Children())
|
|
{
|
|
var match = FindFirstKind(child, kind);
|
|
if (match != null)
|
|
return match;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static VisualElement FindElement(VisualElement root, string name)
|
|
{
|
|
if (root == null)
|
|
return null;
|
|
|
|
if (string.Equals(root.name, name, StringComparison.Ordinal))
|
|
return root;
|
|
|
|
foreach (var child in root.Children())
|
|
{
|
|
var match = FindElement(child, name);
|
|
if (match != null)
|
|
return match;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void HandleSettingsSurfaceCommand(EveSurfaceCommandRequest request)
|
|
{
|
|
if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command))
|
|
{
|
|
Debug.LogWarning("Unknown settings menu command.");
|
|
return;
|
|
}
|
|
|
|
switch (command.Kind)
|
|
{
|
|
case AetheriaRuntimeMainMenuCommandKind.ShowPlayerSettings:
|
|
ShowPlayerSettingsSurface(true);
|
|
return;
|
|
case AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings:
|
|
ShowVerseSettingsSurface(true);
|
|
return;
|
|
case AetheriaRuntimeMainMenuCommandKind.ShowInputSettings:
|
|
ShowInputSettings(true);
|
|
return;
|
|
case AetheriaRuntimeMainMenuCommandKind.BackToMain:
|
|
ShowMain(false);
|
|
return;
|
|
default:
|
|
Debug.LogWarning($"Unhandled settings menu command kind: {command.Kind}");
|
|
return;
|
|
}
|
|
}
|
|
|
|
private void HandleInputSettingsSurfaceCommand(EveSurfaceCommandRequest request)
|
|
{
|
|
if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command))
|
|
{
|
|
Debug.LogWarning("Unknown input settings command.");
|
|
return;
|
|
}
|
|
|
|
switch (command.Kind)
|
|
{
|
|
case AetheriaRuntimeMainMenuCommandKind.BackToSettings:
|
|
ShowSettings(false);
|
|
return;
|
|
case AetheriaRuntimeMainMenuCommandKind.OpenRuntimeInputScreen:
|
|
if (!TryOpenRuntimeInputScreen())
|
|
{
|
|
ShowInputSettings(false);
|
|
}
|
|
|
|
return;
|
|
default:
|
|
Debug.LogWarning($"Unhandled input settings command kind: {command.Kind}");
|
|
return;
|
|
}
|
|
}
|
|
|
|
private void HandlePlayerSettingsSurfaceCommand(EveSurfaceCommandRequest request)
|
|
{
|
|
if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command))
|
|
{
|
|
Debug.LogWarning("Unknown player-settings command.");
|
|
return;
|
|
}
|
|
|
|
switch (command.Kind)
|
|
{
|
|
case AetheriaRuntimeMainMenuCommandKind.BackToSettings:
|
|
ShowSettings(false);
|
|
return;
|
|
case AetheriaRuntimeMainMenuCommandKind.PlayerSettingsCommand:
|
|
SendKnownAetheriaEveCommand(request, "player-settings");
|
|
ShowPlayerSettingsSurface(false);
|
|
return;
|
|
default:
|
|
Debug.LogWarning($"Unhandled player-settings command kind: {command.Kind}");
|
|
return;
|
|
}
|
|
}
|
|
|
|
private void HandleVerseSettingsSurfaceCommand(EveSurfaceCommandRequest request)
|
|
{
|
|
if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command))
|
|
{
|
|
Debug.LogWarning("Unknown verse-settings command.");
|
|
return;
|
|
}
|
|
|
|
switch (command.Kind)
|
|
{
|
|
case AetheriaRuntimeMainMenuCommandKind.BackToSettings:
|
|
ShowSettings(false);
|
|
return;
|
|
case AetheriaRuntimeMainMenuCommandKind.ClientTargetCommand:
|
|
RequestClientTargetCommand(request);
|
|
ShowVerseSettingsSurface(false);
|
|
return;
|
|
case AetheriaRuntimeMainMenuCommandKind.VerseHostCommand:
|
|
SendKnownAetheriaEveCommand(request, "Verse-host");
|
|
ShowVerseSettingsSurface(false);
|
|
return;
|
|
default:
|
|
Debug.LogWarning($"Unhandled verse-settings command kind: {command.Kind}");
|
|
return;
|
|
}
|
|
}
|
|
|
|
private void HideMenuSurface()
|
|
{
|
|
AetheriaEveUnitySurfaceHost.Hide(_currentMenuSurfaceDocument);
|
|
AetheriaEveUnitySurfaceHost.Hide(_nextMenuSurfaceDocument);
|
|
}
|
|
|
|
public void SetRuntimeInputScreenShell(Func<bool> canOpenRuntimeInputScreen, Action openRuntimeInputScreen)
|
|
{
|
|
_canOpenRuntimeInputScreen = canOpenRuntimeInputScreen;
|
|
_openRuntimeInputScreen = openRuntimeInputScreen;
|
|
}
|
|
|
|
private bool CanOpenRuntimeInputScreen()
|
|
{
|
|
return InGame &&
|
|
_canOpenRuntimeInputScreen?.Invoke() == true;
|
|
}
|
|
|
|
private bool TryOpenRuntimeInputScreen()
|
|
{
|
|
if (!CanOpenRuntimeInputScreen())
|
|
return false;
|
|
|
|
HideMenuSurface();
|
|
gameObject.SetActive(false);
|
|
_openRuntimeInputScreen?.Invoke();
|
|
return true;
|
|
}
|
|
|
|
private static void RequestClientTargetCommand(EveSurfaceCommandRequest request)
|
|
{
|
|
try
|
|
{
|
|
if (!AetheriaRuntimeClientTargetSurfaceCommands.TryRequest(
|
|
AetheriaState.At(AetheriaUnityRuntimePaths.GameDataDirectory).ClientTarget,
|
|
request,
|
|
out _))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"Failed to update typed Aetheria client target: {ex}");
|
|
}
|
|
}
|
|
|
|
private void SendKnownAetheriaEveCommand(
|
|
EveSurfaceCommandRequest request,
|
|
string label)
|
|
{
|
|
if (request == null)
|
|
return;
|
|
|
|
var stateBoot = CurrentStateBoot();
|
|
if (!CanSendLocalEveCommand(stateBoot, label))
|
|
return;
|
|
|
|
try
|
|
{
|
|
var submitted = AetheriaUnityRuntimeClientProvider
|
|
.Ui(stateBoot, "unity-main-menu")
|
|
.SurfaceCommandAsync(request, "unity-main-menu")
|
|
.GetAwaiter()
|
|
.GetResult();
|
|
|
|
Debug.Log($"Submitted Aetheria {label} Eve operation: {submitted.OperationId}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"Failed to send Aetheria {label} Eve command: {ex}");
|
|
}
|
|
}
|
|
|
|
private static bool CanSendLocalEveCommand(
|
|
AetheriaRuntimeStateBootReport stateBoot,
|
|
string label)
|
|
{
|
|
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
|
|
{
|
|
Debug.LogWarning(
|
|
$"Cannot send {label} command because the active target is not a readable local Verse state file.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (_currentMenuSurfaceDocument != null)
|
|
{
|
|
Destroy(_currentMenuSurfaceDocument.gameObject);
|
|
_currentMenuSurfaceDocument = null;
|
|
}
|
|
|
|
if (_nextMenuSurfaceDocument != null)
|
|
{
|
|
Destroy(_nextMenuSurfaceDocument.gameObject);
|
|
_nextMenuSurfaceDocument = null;
|
|
}
|
|
}
|
|
|
|
private static TextCoreFontAsset TitleFont =>
|
|
_titleFont ??= ResolveFontAsset(
|
|
"Assets/Fonts/Ubuntu/Montserrat-Thin SDF.asset",
|
|
"Montserrat Thin",
|
|
"Montserrat");
|
|
|
|
private static TextCoreFontAsset LabelFont =>
|
|
_labelFont ??= ResolveFontAsset(
|
|
"Assets/Fonts/Ubuntu/Ubuntu-R SDF.asset",
|
|
"Ubuntu",
|
|
"Ubuntu Regular");
|
|
|
|
private static TextCoreFontAsset ResolveFontAsset(string assetPath, params string[] osFontNames)
|
|
{
|
|
#if UNITY_EDITOR
|
|
foreach (var asset in UnityEditor.AssetDatabase.LoadAllAssetsAtPath(assetPath))
|
|
{
|
|
if (asset is TextCoreFontAsset editorFontAsset)
|
|
return editorFontAsset;
|
|
}
|
|
#endif
|
|
|
|
foreach (var osFontName in osFontNames)
|
|
{
|
|
try
|
|
{
|
|
var fontAsset = TextCoreFontAsset.CreateFontAsset(
|
|
osFontName,
|
|
"Regular",
|
|
90,
|
|
9,
|
|
GlyphRenderMode.SDFAA);
|
|
if (fontAsset != null)
|
|
return fontAsset;
|
|
}
|
|
catch
|
|
{
|
|
// Try the next configured face name before falling back.
|
|
}
|
|
}
|
|
|
|
Debug.LogWarning($"Failed to resolve UI font asset '{assetPath}'. Falling back to Arial.");
|
|
var fallback = TextCoreFontAsset.CreateFontAsset(
|
|
"Arial",
|
|
"Regular",
|
|
90,
|
|
9,
|
|
GlyphRenderMode.SDFAA);
|
|
if (fallback != null)
|
|
return fallback;
|
|
|
|
throw new InvalidOperationException($"Failed to create fallback UI font asset for '{assetPath}'.");
|
|
}
|
|
|
|
private sealed class MenuHoverButton
|
|
{
|
|
public MenuHoverButton(UiButton button)
|
|
{
|
|
Button = button;
|
|
}
|
|
|
|
public UiButton Button { get; }
|
|
public bool Hovering { get; set; }
|
|
public float Lerp { get; set; }
|
|
}
|
|
}
|