Aetheria/Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs

438 lines
14 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
2026-06-17 08:41:06 +01:00
using System.Globalization;
using System.Linq;
2026-06-17 08:41:06 +01:00
using GameCult.Aetheria.State.Unity;
using GameCult.Eve.Surface;
using GameCult.Eve.UnityUIToolkit;
using UnityEngine;
using UnityEngine.InputSystem;
2026-06-17 08:41:06 +01:00
using UnityEngine.InputSystem.Utilities;
using UnityEngine.UIElements;
public class InputDisplayLayout : MonoBehaviour
{
2026-06-17 08:41:06 +01:00
private static readonly string[] DefaultActionBarCandidatePaths =
{
2026-06-17 08:41:06 +01:00
"<Mouse>/leftButton",
"<Mouse>/rightButton",
"<Mouse>/middleButton",
"<Mouse>/forwardButton",
"<Mouse>/backButton",
"<Keyboard>/1",
"<Keyboard>/2",
"<Keyboard>/3",
"<Keyboard>/4",
"<Keyboard>/5",
"<Keyboard>/leftShift"
};
private UIDocument _surfaceDocument;
private AetheriaInput _ownedInput;
private InputAction _captureAction;
private InputActionAsset _input;
private string _captureActionName = "";
private int _captureBindingIndex = -1;
private string _captureBindingLabel = "";
public InputActionAsset Input
{
get => _input;
set
{
_input = value;
if (isActiveAndEnabled)
{
RenderSurface();
}
}
}
2026-06-17 08:41:06 +01:00
private void Start()
{
2026-06-17 08:41:06 +01:00
EnsureInputAsset();
EnsureSurfaceDocument();
EnsureCaptureAction();
HideLegacyChildren();
RenderSurface();
}
2026-06-13 04:56:50 +01:00
2026-06-17 08:41:06 +01:00
private void OnEnable()
{
2026-06-17 08:41:06 +01:00
EnsureInputAsset();
EnsureSurfaceDocument();
EnsureCaptureAction();
HideLegacyChildren();
RenderSurface();
}
2026-06-17 08:41:06 +01:00
private void OnDisable()
{
ClearCapture();
if (_captureAction != null)
{
2026-06-17 08:41:06 +01:00
_captureAction.Disable();
}
2026-06-13 04:56:50 +01:00
2026-06-17 08:41:06 +01:00
if (_surfaceDocument != null)
{
2026-06-17 08:41:06 +01:00
_surfaceDocument.rootVisualElement.Clear();
}
2026-06-17 08:41:06 +01:00
}
2026-06-13 04:56:50 +01:00
2026-06-17 08:41:06 +01:00
private void OnDestroy()
{
_captureAction?.Dispose();
_captureAction = null;
_ownedInput?.Dispose();
_ownedInput = null;
if (_surfaceDocument != null)
{
2026-06-17 08:41:06 +01:00
Destroy(_surfaceDocument.gameObject);
_surfaceDocument = null;
}
2026-06-17 08:41:06 +01:00
}
2026-06-17 08:41:06 +01:00
private void EnsureInputAsset()
{
if (Input != null)
{
2026-06-17 08:41:06 +01:00
return;
}
2026-06-17 08:41:06 +01:00
_ownedInput ??= new AetheriaInput();
Input = _ownedInput.asset;
}
2026-06-17 08:41:06 +01:00
private void EnsureSurfaceDocument()
{
2026-06-17 08:41:06 +01:00
if (_surfaceDocument != null)
{
2026-06-17 08:41:06 +01:00
return;
}
var host = new GameObject("Aetheria Input Surface");
host.transform.SetParent(transform, false);
host.layer = gameObject.layer;
_surfaceDocument = host.AddComponent<UIDocument>();
_surfaceDocument.sortingOrder = 1000;
}
2026-06-17 08:41:06 +01:00
private void HideLegacyChildren()
{
2026-06-17 08:41:06 +01:00
for (var index = 0; index < transform.childCount; index++)
{
2026-06-17 08:41:06 +01:00
var child = transform.GetChild(index);
if (_surfaceDocument != null && child.gameObject == _surfaceDocument.gameObject)
{
2026-06-17 08:41:06 +01:00
continue;
}
2026-06-17 08:41:06 +01:00
child.gameObject.SetActive(false);
}
}
2026-06-17 08:41:06 +01:00
private void EnsureCaptureAction()
{
2026-06-17 08:41:06 +01:00
if (_captureAction != null)
{
2026-06-17 08:41:06 +01:00
return;
}
2026-06-17 08:41:06 +01:00
_captureAction = new InputAction("Aetheria Input Capture");
_captureAction.AddBinding("<Keyboard>/anyKey");
_captureAction.AddBinding("<Mouse>/leftButton");
_captureAction.AddBinding("<Mouse>/rightButton");
_captureAction.AddBinding("<Mouse>/middleButton");
_captureAction.AddBinding("<Mouse>/forwardButton");
_captureAction.AddBinding("<Mouse>/backButton");
_captureAction.performed += OnCapturePerformed;
}
2026-06-17 08:41:06 +01:00
private void RenderSurface()
{
2026-06-17 08:41:06 +01:00
if (!isActiveAndEnabled)
{
return;
}
EnsureInputAsset();
EnsureSurfaceDocument();
EnsureCaptureAction();
var document = ToEveSurfaceDocument(
AetheriaRuntimeInputSettingsSurfaceBuilder.Build(ProjectSurfaceState()));
var root = _surfaceDocument.rootVisualElement;
root.Clear();
root.style.flexGrow = 1;
root.style.justifyContent = Justify.Center;
root.style.alignItems = Align.Center;
root.style.paddingLeft = 24;
root.style.paddingRight = 24;
root.style.paddingTop = 24;
root.style.paddingBottom = 24;
root.style.backgroundColor = new Color(0f, 0f, 0f, 0.72f);
var shell = new VisualElement();
shell.style.flexDirection = FlexDirection.Column;
shell.style.width = 1080;
shell.style.maxWidth = 1080;
shell.style.maxHeight = 900;
shell.style.flexGrow = 1;
shell.style.paddingLeft = 20;
shell.style.paddingRight = 20;
shell.style.paddingTop = 20;
shell.style.paddingBottom = 20;
shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.96f);
root.Add(shell);
var lowerer = new EveUiToolkitSurfaceLowerer();
shell.Add(lowerer.Lower(document, HandleSurfaceCommand));
}
2026-06-17 08:41:06 +01:00
private AetheriaRuntimeInputSettingsSurfaceState ProjectSurfaceState()
{
2026-06-17 08:41:06 +01:00
return new AetheriaRuntimeInputSettingsSurfaceState(
ProjectBindingRows(),
ProjectActionBarRows(),
capturePending: _captureBindingIndex >= 0 && !string.IsNullOrWhiteSpace(_captureActionName),
capturePrompt: BuildCapturePrompt(),
updatedAtUtc: DateTime.UtcNow.ToString("O"));
}
2026-06-17 08:41:06 +01:00
private IReadOnlyList<AetheriaRuntimeInputBindingSurfaceState> ProjectBindingRows()
{
2026-06-17 08:41:06 +01:00
if (Input == null)
{
2026-06-17 08:41:06 +01:00
return Array.Empty<AetheriaRuntimeInputBindingSurfaceState>();
}
2026-06-17 08:41:06 +01:00
return Input
.Where(action => action.actionMap.name != "UI")
.SelectMany(action =>
action.bindings
.Select((binding, bindingIndex) => new { action, binding, bindingIndex }))
.Where(entry =>
!entry.binding.isComposite &&
!string.IsNullOrWhiteSpace(entry.binding.effectivePath) &&
IsSupportedCapturePath(entry.binding.effectivePath))
.Select(entry => new AetheriaRuntimeInputBindingSurfaceState(
entry.action.name,
entry.bindingIndex,
DescribeBinding(entry.action.name, entry.binding),
DescribeInputPath(entry.binding.effectivePath)))
.OrderBy(entry => entry.BindingLabel, StringComparer.Ordinal)
.ToArray();
}
2026-06-17 08:41:06 +01:00
private IReadOnlyList<AetheriaRuntimeActionBarInputSurfaceState> ProjectActionBarRows()
{
2026-06-17 08:41:06 +01:00
var runtimeSettings = ActionGameManager.RuntimePlayerSettings.InputSettings;
var candidates = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var defaultPath in DefaultActionBarCandidatePaths)
{
2026-06-17 08:41:06 +01:00
candidates[defaultPath] = DescribeInputPath(defaultPath);
}
2026-06-17 08:41:06 +01:00
foreach (var inputPath in runtimeSettings.ActionBarInputs)
{
2026-06-17 08:41:06 +01:00
if (!string.IsNullOrWhiteSpace(inputPath))
{
candidates[inputPath] = DescribeInputPath(inputPath);
}
}
2026-06-17 08:41:06 +01:00
if (Input != null)
{
2026-06-17 08:41:06 +01:00
foreach (var action in Input.Where(action => action.actionMap.name != "UI"))
{
2026-06-17 08:41:06 +01:00
foreach (var binding in action.bindings)
{
2026-06-17 08:41:06 +01:00
if (binding.isComposite || string.IsNullOrWhiteSpace(binding.effectivePath) || !IsSupportedCapturePath(binding.effectivePath))
{
2026-06-17 08:41:06 +01:00
continue;
}
2026-06-17 08:41:06 +01:00
candidates[binding.effectivePath] = DescribeInputPath(binding.effectivePath);
}
}
}
2026-06-17 08:41:06 +01:00
return candidates
.Select(entry => new AetheriaRuntimeActionBarInputSurfaceState(
entry.Key,
entry.Value,
runtimeSettings.ActionBarInputs.Contains(entry.Key)))
.ToArray();
}
2026-06-13 04:56:50 +01:00
2026-06-17 08:41:06 +01:00
private static bool IsSupportedCapturePath(string path)
{
return !string.IsNullOrWhiteSpace(path) &&
(path.StartsWith("<Keyboard>/", StringComparison.Ordinal) ||
path.StartsWith("<Mouse>/", StringComparison.Ordinal));
}
2026-06-17 08:41:06 +01:00
private static string DescribeBinding(string actionName, InputBinding binding)
{
2026-06-17 08:41:06 +01:00
if (binding.isPartOfComposite && !string.IsNullOrWhiteSpace(binding.name))
{
2026-06-17 08:41:06 +01:00
return $"{actionName} {binding.name}";
}
2026-06-17 08:41:06 +01:00
return actionName;
}
2026-06-17 08:41:06 +01:00
private static string DescribeInputPath(string inputPath)
{
2026-06-17 08:41:06 +01:00
if (string.IsNullOrWhiteSpace(inputPath))
{
2026-06-17 08:41:06 +01:00
return "Unbound";
}
2026-06-17 08:41:06 +01:00
var label = InputControlPath.ToHumanReadableString(
inputPath,
InputControlPath.HumanReadableStringOptions.OmitDevice);
return string.IsNullOrWhiteSpace(label) ? inputPath : label;
}
2026-06-17 08:41:06 +01:00
private string BuildCapturePrompt()
{
2026-06-17 08:41:06 +01:00
if (_captureBindingIndex < 0 || string.IsNullOrWhiteSpace(_captureActionName))
{
return "";
}
return $"Capture pending for {_captureBindingLabel}. Press a keyboard or mouse input now.";
}
2026-06-17 08:41:06 +01:00
private void HandleSurfaceCommand(EveSurfaceCommandRequest request)
{
2026-06-17 08:41:06 +01:00
switch (request.Command)
{
case var command when string.Equals(command, AetheriaRuntimeInputSettingsCommands.Refresh, StringComparison.Ordinal):
RenderSurface();
return;
case var command when string.Equals(command, AetheriaRuntimeInputSettingsCommands.CancelCapture, StringComparison.Ordinal):
ClearCapture();
RenderSurface();
return;
case var command when string.Equals(command, AetheriaRuntimeInputSettingsCommands.BeginCapture, StringComparison.Ordinal):
BeginCapture(request.Payload);
return;
case var command when string.Equals(command, AetheriaRuntimeInputSettingsCommands.ToggleActionBar, StringComparison.Ordinal):
ToggleActionBarInput(request.Payload);
return;
default:
Debug.LogWarning($"Unknown input-settings command: {request.Command}");
return;
}
}
2026-06-17 08:41:06 +01:00
private void BeginCapture(IReadOnlyDictionary<string, string> payload)
{
2026-06-17 08:41:06 +01:00
if (payload == null ||
!payload.TryGetValue("actionName", out var actionName) ||
!payload.TryGetValue("bindingIndex", out var bindingIndexText) ||
!int.TryParse(bindingIndexText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var bindingIndex))
{
Debug.LogWarning("Input capture requested without a valid action name and binding index.");
return;
}
_captureActionName = actionName;
_captureBindingIndex = bindingIndex;
_captureBindingLabel = payload.TryGetValue("bindingLabel", out var bindingLabel)
? bindingLabel ?? actionName
: actionName;
_captureAction?.Enable();
RenderSurface();
}
2026-06-17 08:41:06 +01:00
private void ToggleActionBarInput(IReadOnlyDictionary<string, string> payload)
{
2026-06-17 08:41:06 +01:00
if (payload == null ||
!payload.TryGetValue("inputPath", out var inputPath) ||
!payload.TryGetValue("enabled", out var enabledText) ||
!bool.TryParse(enabledText, out var enabled))
{
2026-06-17 08:41:06 +01:00
Debug.LogWarning("Action-bar toggle requested without a valid input path and enabled state.");
return;
}
2026-06-17 08:41:06 +01:00
ActionGameManager.CommitRuntimeActionBarInput(inputPath, enabled);
RenderSurface();
}
2026-06-17 08:41:06 +01:00
private void OnCapturePerformed(InputAction.CallbackContext context)
{
if (_captureBindingIndex < 0 || string.IsNullOrWhiteSpace(_captureActionName))
{
return;
}
2026-06-17 08:41:06 +01:00
var inputPath = context.control?.path ?? "";
if (string.IsNullOrWhiteSpace(inputPath) ||
inputPath.EndsWith("anyKey", StringComparison.Ordinal) ||
!IsSupportedCapturePath(inputPath) ||
Input == null)
{
return;
}
2026-06-13 04:56:50 +01:00
2026-06-17 08:41:06 +01:00
var action = Input.FindAction(_captureActionName, throwIfNotFound: false);
if (action == null || _captureBindingIndex >= action.bindings.Count)
{
2026-06-17 08:41:06 +01:00
Debug.LogWarning($"Input capture target no longer exists for {_captureActionName}:{_captureBindingIndex}.");
ClearCapture();
RenderSurface();
return;
}
2026-06-17 08:41:06 +01:00
action.ApplyBindingOverride(_captureBindingIndex, inputPath);
ActionGameManager.CommitRuntimeInputBindingOverride(action.name, _captureBindingIndex, inputPath);
ClearCapture();
RenderSurface();
}
2026-06-17 08:41:06 +01:00
private void ClearCapture()
{
2026-06-17 08:41:06 +01:00
_captureActionName = "";
_captureBindingIndex = -1;
_captureBindingLabel = "";
}
2026-06-17 08:41:06 +01:00
private static EveSurfaceDocument ToEveSurfaceDocument(AetheriaRuntimeSurfaceDocument document)
{
2026-06-17 08:41:06 +01:00
return new EveSurfaceDocument(
"surface-state",
"gamecult.eve.surface.v1",
document.ProviderId,
document.ProviderKind,
document.Title,
document.Version,
document.UpdatedAtUtc,
new EveSurfaceTree(
document.Surface.Id,
ToEveSurfaceComponent(document.Surface.Root),
document.Surface.Styles
.Select(style => new EveStyleToken(style.Name, style.Value))
.ToArray()),
document.Commands
.Select(command => new EveCommandTemplate(command.Command, command.Label, command.Transport))
.ToArray());
}
private static EveSurfaceComponent ToEveSurfaceComponent(AetheriaRuntimeSurfaceComponent component)
{
return new EveSurfaceComponent(
component.Id,
component.Kind,
new Dictionary<string, string>(component.Props, StringComparer.Ordinal),
component.Children.Select(ToEveSurfaceComponent).ToArray());
}
}