538 lines
22 KiB
C#
538 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using GameCult.Eve.Surface;
|
|
|
|
namespace GameCult.Aetheria.State.Verse
|
|
{
|
|
public sealed class AetheriaRuntimeInputPathSurfaceLabel
|
|
{
|
|
public AetheriaRuntimeInputPathSurfaceLabel(string inputPath, string label)
|
|
{
|
|
InputPath = inputPath ?? "";
|
|
Label = label ?? "";
|
|
}
|
|
|
|
public string InputPath { get; }
|
|
|
|
public string Label { get; }
|
|
}
|
|
|
|
public sealed class AetheriaRuntimeObservedInputBinding
|
|
{
|
|
public AetheriaRuntimeObservedInputBinding(
|
|
string actionName,
|
|
int bindingIndex,
|
|
string bindingLabel,
|
|
string inputPath,
|
|
string inputLabel,
|
|
bool include)
|
|
{
|
|
ActionName = actionName ?? "";
|
|
BindingIndex = bindingIndex;
|
|
BindingLabel = bindingLabel ?? "";
|
|
InputPath = inputPath ?? "";
|
|
InputLabel = inputLabel ?? "";
|
|
Include = include;
|
|
}
|
|
|
|
public string ActionName { get; }
|
|
public int BindingIndex { get; }
|
|
public string BindingLabel { get; }
|
|
public string InputPath { get; }
|
|
public string InputLabel { get; }
|
|
public bool Include { get; }
|
|
}
|
|
|
|
public static class AetheriaRuntimeInputSettingsSurfaceBuilder
|
|
{
|
|
public static readonly IReadOnlyList<string> DefaultActionBarCandidatePaths = new[]
|
|
{
|
|
"<Mouse>/leftButton",
|
|
"<Mouse>/rightButton",
|
|
"<Mouse>/middleButton",
|
|
"<Mouse>/forwardButton",
|
|
"<Mouse>/backButton",
|
|
"<Keyboard>/1",
|
|
"<Keyboard>/2",
|
|
"<Keyboard>/3",
|
|
"<Keyboard>/4",
|
|
"<Keyboard>/5",
|
|
"<Keyboard>/leftShift"
|
|
};
|
|
|
|
public static readonly IReadOnlyList<AetheriaRuntimeInputPathSurfaceLabel> DefaultActionBarCandidateInputPaths =
|
|
new[]
|
|
{
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Mouse>/leftButton", "Left Button"),
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Mouse>/rightButton", "Right Button"),
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Mouse>/middleButton", "Middle Button"),
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Mouse>/forwardButton", "Forward Button"),
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Mouse>/backButton", "Back Button"),
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Keyboard>/1", "1"),
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Keyboard>/2", "2"),
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Keyboard>/3", "3"),
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Keyboard>/4", "4"),
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Keyboard>/5", "5"),
|
|
new AetheriaRuntimeInputPathSurfaceLabel("<Keyboard>/leftShift", "Left Shift")
|
|
};
|
|
|
|
public static bool IsSupportedCapturePath(string path)
|
|
{
|
|
return !string.IsNullOrWhiteSpace(path) &&
|
|
(path.StartsWith("<Keyboard>/", StringComparison.Ordinal) ||
|
|
path.StartsWith("<Mouse>/", StringComparison.Ordinal));
|
|
}
|
|
|
|
public static AetheriaRuntimeSurfaceDocument Build(
|
|
IEnumerable<AetheriaRuntimeObservedInputBinding> observedBindings,
|
|
IEnumerable<string> enabledActionBarInputPaths,
|
|
bool capturePending,
|
|
string capturePrompt,
|
|
string updatedAtUtc,
|
|
long version = 1)
|
|
{
|
|
var observed = (observedBindings ?? Array.Empty<AetheriaRuntimeObservedInputBinding>())
|
|
.Where(binding => binding != null)
|
|
.ToArray();
|
|
var actionBarCandidates = ComposeActionBarCandidates(observed, enabledActionBarInputPaths);
|
|
var bindings = ComposeBindingInputs(observed);
|
|
var actionBarInputs = ComposeActionBarInputs(enabledActionBarInputPaths, actionBarCandidates);
|
|
|
|
return Build(
|
|
bindings,
|
|
actionBarInputs,
|
|
capturePending,
|
|
capturePrompt,
|
|
updatedAtUtc,
|
|
version);
|
|
}
|
|
|
|
private static IReadOnlyList<InputBindingRow> ComposeBindingInputs(
|
|
IEnumerable<AetheriaRuntimeObservedInputBinding> observedBindings)
|
|
{
|
|
return (observedBindings ?? Array.Empty<AetheriaRuntimeObservedInputBinding>())
|
|
.Where(binding => binding != null &&
|
|
binding.Include &&
|
|
!string.IsNullOrWhiteSpace(binding.InputPath) &&
|
|
IsSupportedCapturePath(binding.InputPath))
|
|
.Select(binding => new InputBindingRow(
|
|
binding.ActionName,
|
|
binding.BindingIndex,
|
|
binding.BindingLabel,
|
|
string.IsNullOrWhiteSpace(binding.InputLabel) ? binding.InputPath : binding.InputLabel))
|
|
.OrderBy(binding => binding.BindingLabel, StringComparer.Ordinal)
|
|
.ThenBy(binding => binding.ActionName, StringComparer.Ordinal)
|
|
.ThenBy(binding => binding.BindingIndex)
|
|
.ToArray();
|
|
}
|
|
|
|
private static IReadOnlyList<AetheriaRuntimeInputPathSurfaceLabel> ComposeActionBarCandidates(
|
|
IEnumerable<AetheriaRuntimeObservedInputBinding> observedBindings,
|
|
IEnumerable<string> enabledInputPaths)
|
|
{
|
|
var candidates = new List<AetheriaRuntimeInputPathSurfaceLabel>();
|
|
candidates.AddRange(DefaultActionBarCandidateInputPaths);
|
|
|
|
foreach (var inputPath in enabledInputPaths ?? Array.Empty<string>())
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(inputPath))
|
|
{
|
|
candidates.Add(new AetheriaRuntimeInputPathSurfaceLabel(
|
|
inputPath,
|
|
ReadInputLabel(inputPath, observedBindings)));
|
|
}
|
|
}
|
|
|
|
foreach (var binding in observedBindings ?? Array.Empty<AetheriaRuntimeObservedInputBinding>())
|
|
{
|
|
if (binding == null ||
|
|
!binding.Include ||
|
|
string.IsNullOrWhiteSpace(binding.InputPath) ||
|
|
!IsSupportedCapturePath(binding.InputPath))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
candidates.Add(new AetheriaRuntimeInputPathSurfaceLabel(
|
|
binding.InputPath,
|
|
string.IsNullOrWhiteSpace(binding.InputLabel) ? binding.InputPath : binding.InputLabel));
|
|
}
|
|
|
|
return candidates;
|
|
}
|
|
|
|
private static IReadOnlyList<ActionBarInputRow> ComposeActionBarInputs(
|
|
IEnumerable<string> enabledInputPaths,
|
|
IEnumerable<AetheriaRuntimeInputPathSurfaceLabel> candidateInputPaths)
|
|
{
|
|
var enabledPaths = new HashSet<string>(
|
|
(enabledInputPaths ?? Array.Empty<string>()).Where(path => !string.IsNullOrWhiteSpace(path)),
|
|
StringComparer.Ordinal);
|
|
var candidates = new SortedDictionary<string, string>(StringComparer.Ordinal);
|
|
|
|
foreach (var candidate in candidateInputPaths ?? Array.Empty<AetheriaRuntimeInputPathSurfaceLabel>())
|
|
{
|
|
if (candidate == null || string.IsNullOrWhiteSpace(candidate.InputPath))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
candidates[candidate.InputPath] = string.IsNullOrWhiteSpace(candidate.Label)
|
|
? candidate.InputPath
|
|
: candidate.Label;
|
|
}
|
|
|
|
foreach (var enabledPath in enabledPaths)
|
|
{
|
|
if (!candidates.ContainsKey(enabledPath))
|
|
{
|
|
candidates[enabledPath] = enabledPath;
|
|
}
|
|
}
|
|
|
|
return candidates
|
|
.Select(entry => new ActionBarInputRow(
|
|
entry.Key,
|
|
entry.Value,
|
|
enabledPaths.Contains(entry.Key)))
|
|
.ToArray();
|
|
}
|
|
|
|
private static string ReadInputLabel(
|
|
string inputPath,
|
|
IEnumerable<AetheriaRuntimeObservedInputBinding> observedBindings)
|
|
{
|
|
foreach (var binding in observedBindings ?? Array.Empty<AetheriaRuntimeObservedInputBinding>())
|
|
{
|
|
if (binding == null ||
|
|
!string.Equals(binding.InputPath, inputPath, StringComparison.Ordinal) ||
|
|
string.IsNullOrWhiteSpace(binding.InputLabel))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return binding.InputLabel;
|
|
}
|
|
|
|
return inputPath;
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceDocument Build(
|
|
IReadOnlyList<InputBindingRow> bindings,
|
|
IReadOnlyList<ActionBarInputRow> actionBarInputs,
|
|
bool capturePending,
|
|
string capturePrompt,
|
|
string updatedAtUtc,
|
|
long version)
|
|
{
|
|
bindings ??= Array.Empty<InputBindingRow>();
|
|
actionBarInputs ??= Array.Empty<ActionBarInputRow>();
|
|
return new AetheriaRuntimeSurfaceDocument(
|
|
providerId: "aetheria",
|
|
providerKind: "game.runtime",
|
|
title: "Aetheria Input Settings",
|
|
version: version,
|
|
updatedAtUtc: updatedAtUtc ?? "",
|
|
surface: new AetheriaRuntimeSurfaceTree(
|
|
AetheriaRuntimeInputSettingsCommands.SurfaceId,
|
|
Node(
|
|
"aetheria.inputSettings.root",
|
|
"surface",
|
|
Array.Empty<(string Key, string Value)>(),
|
|
Node(
|
|
"aetheria.inputSettings.summary",
|
|
"card",
|
|
new[] { ("title", "Input Settings") },
|
|
Metric(
|
|
"aetheria.inputSettings.summary.bindings",
|
|
"Rebindable Inputs",
|
|
bindings.Count.ToString()),
|
|
Metric(
|
|
"aetheria.inputSettings.summary.actionBar",
|
|
"Action-Bar Inputs",
|
|
actionBarInputs.Count(entry => entry.Enabled).ToString()),
|
|
Text(
|
|
"aetheria.inputSettings.summary.note",
|
|
"Low-level InputSystem edits flow through this Eve surface as typed input-setting requests.")),
|
|
Node(
|
|
"aetheria.inputSettings.capture",
|
|
"card",
|
|
new[] { ("title", "Capture") },
|
|
Text(
|
|
"aetheria.inputSettings.capture.note",
|
|
capturePending
|
|
? capturePrompt ?? ""
|
|
: "Choose a binding row, then press a keyboard or mouse input to capture it."),
|
|
ButtonRow(
|
|
"aetheria.inputSettings.capture.actions",
|
|
capturePending
|
|
? Button(
|
|
"aetheria.inputSettings.capture.cancel",
|
|
"Cancel Capture",
|
|
AetheriaRuntimeInputSettingsCommands.CancelCapture)
|
|
: Button(
|
|
"aetheria.inputSettings.capture.refresh",
|
|
"Refresh",
|
|
AetheriaRuntimeInputSettingsCommands.Refresh))),
|
|
Node(
|
|
"aetheria.inputSettings.bindings",
|
|
"card",
|
|
new[] { ("title", "Bindings") },
|
|
Node(
|
|
"aetheria.inputSettings.bindings.grid",
|
|
"grid",
|
|
Array.Empty<(string Key, string Value)>(),
|
|
bindings.Select(BuildBindingCard).ToArray())),
|
|
Node(
|
|
"aetheria.inputSettings.actionBarInputs",
|
|
"card",
|
|
new[] { ("title", "Action-Bar Inputs") },
|
|
Node(
|
|
"aetheria.inputSettings.actionBarInputs.grid",
|
|
"grid",
|
|
Array.Empty<(string Key, string Value)>(),
|
|
actionBarInputs.Select(BuildActionBarCard).ToArray()))),
|
|
Array.Empty<AetheriaRuntimeSurfaceStyleToken>()),
|
|
commands: new[]
|
|
{
|
|
new AetheriaRuntimeSurfaceCommandTemplate(
|
|
AetheriaRuntimeInputSettingsCommands.Refresh,
|
|
"Refresh",
|
|
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
|
|
new AetheriaRuntimeSurfaceCommandTemplate(
|
|
AetheriaRuntimeInputSettingsCommands.BeginCapture,
|
|
"Capture Binding",
|
|
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
|
|
new AetheriaRuntimeSurfaceCommandTemplate(
|
|
AetheriaRuntimeInputSettingsCommands.CancelCapture,
|
|
"Cancel Capture",
|
|
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
|
|
new AetheriaRuntimeSurfaceCommandTemplate(
|
|
AetheriaRuntimeInputSettingsCommands.ToggleActionBar,
|
|
"Toggle Action-Bar Input",
|
|
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport)
|
|
});
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceComponent BuildBindingCard(
|
|
InputBindingRow binding)
|
|
{
|
|
return Node(
|
|
$"aetheria.inputSettings.binding.{binding.ActionName}.{binding.BindingIndex}",
|
|
"card",
|
|
new[] { ("title", binding.BindingLabel) },
|
|
Metric(
|
|
$"aetheria.inputSettings.binding.{binding.ActionName}.{binding.BindingIndex}.current",
|
|
"Current Input",
|
|
binding.CurrentInputLabel),
|
|
Button(
|
|
$"aetheria.inputSettings.binding.{binding.ActionName}.{binding.BindingIndex}.capture",
|
|
"Capture Binding",
|
|
AetheriaRuntimeInputSettingsCommands.BeginCapture,
|
|
("actionName", binding.ActionName),
|
|
("bindingIndex", binding.BindingIndex.ToString()),
|
|
("bindingLabel", binding.BindingLabel)));
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceComponent BuildActionBarCard(
|
|
ActionBarInputRow input)
|
|
{
|
|
return Node(
|
|
$"aetheria.inputSettings.actionBar.{input.Label}",
|
|
"card",
|
|
new[] { ("title", input.Label) },
|
|
Metric(
|
|
$"aetheria.inputSettings.actionBar.{input.Label}.state",
|
|
"Enabled",
|
|
input.Enabled ? "Yes" : "No"),
|
|
Button(
|
|
$"aetheria.inputSettings.actionBar.{input.Label}.toggle",
|
|
input.Enabled ? "Disable" : "Enable",
|
|
AetheriaRuntimeInputSettingsCommands.ToggleActionBar,
|
|
("inputPath", input.InputPath),
|
|
("enabled", input.Enabled ? "false" : "true"),
|
|
("label", input.Label)));
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceComponent Metric(string id, string label, string value)
|
|
{
|
|
return Node(id, "metric", new[] { ("label", label), ("value", value) });
|
|
}
|
|
|
|
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,
|
|
params (string Key, string Value)[] extraProps)
|
|
{
|
|
var props = new List<(string Key, string Value)>
|
|
{
|
|
("label", label),
|
|
("command", command)
|
|
};
|
|
if (extraProps != null)
|
|
{
|
|
props.AddRange(extraProps);
|
|
}
|
|
|
|
return Node(id, "control.button", props);
|
|
}
|
|
|
|
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<AetheriaRuntimeSurfaceComponent>());
|
|
}
|
|
|
|
private sealed class InputBindingRow
|
|
{
|
|
public InputBindingRow(
|
|
string actionName,
|
|
int bindingIndex,
|
|
string bindingLabel,
|
|
string currentInputLabel)
|
|
{
|
|
ActionName = actionName ?? "";
|
|
BindingIndex = bindingIndex;
|
|
BindingLabel = bindingLabel ?? "";
|
|
CurrentInputLabel = currentInputLabel ?? "";
|
|
}
|
|
|
|
public string ActionName { get; }
|
|
|
|
public int BindingIndex { get; }
|
|
|
|
public string BindingLabel { get; }
|
|
|
|
public string CurrentInputLabel { get; }
|
|
}
|
|
|
|
private sealed class ActionBarInputRow
|
|
{
|
|
public ActionBarInputRow(string inputPath, string label, bool enabled)
|
|
{
|
|
InputPath = inputPath ?? "";
|
|
Label = label ?? "";
|
|
Enabled = enabled;
|
|
}
|
|
|
|
public string InputPath { get; }
|
|
|
|
public string Label { get; }
|
|
|
|
public bool Enabled { get; }
|
|
}
|
|
}
|
|
|
|
public enum AetheriaRuntimeInputSettingsCommandKind
|
|
{
|
|
Unknown = 0,
|
|
Refresh = 1,
|
|
BeginCapture = 2,
|
|
CancelCapture = 3,
|
|
ToggleActionBar = 4
|
|
}
|
|
|
|
public readonly struct AetheriaRuntimeInputSettingsSurfaceCommand
|
|
{
|
|
public AetheriaRuntimeInputSettingsSurfaceCommand(
|
|
AetheriaRuntimeInputSettingsCommandKind kind,
|
|
string actionName = "",
|
|
int bindingIndex = -1,
|
|
string bindingLabel = "",
|
|
string inputPath = "",
|
|
bool enabled = false)
|
|
{
|
|
Kind = kind;
|
|
ActionName = actionName ?? "";
|
|
BindingIndex = bindingIndex;
|
|
BindingLabel = bindingLabel ?? "";
|
|
InputPath = inputPath ?? "";
|
|
Enabled = enabled;
|
|
}
|
|
|
|
public AetheriaRuntimeInputSettingsCommandKind Kind { get; }
|
|
public string ActionName { get; }
|
|
public int BindingIndex { get; }
|
|
public string BindingLabel { get; }
|
|
public string InputPath { get; }
|
|
public bool Enabled { get; }
|
|
}
|
|
|
|
public static class AetheriaRuntimeInputSettingsSurfaceCommands
|
|
{
|
|
public static bool TryRead(
|
|
EveSurfaceCommandRequest request,
|
|
out AetheriaRuntimeInputSettingsSurfaceCommand command)
|
|
{
|
|
command = default;
|
|
if (request == null ||
|
|
!string.Equals(request.SurfaceId, AetheriaRuntimeInputSettingsCommands.SurfaceId, StringComparison.Ordinal))
|
|
return false;
|
|
|
|
switch (request.Operation?.OperationId ?? "")
|
|
{
|
|
case AetheriaRuntimeInputSettingsCommands.Refresh:
|
|
command = new AetheriaRuntimeInputSettingsSurfaceCommand(
|
|
AetheriaRuntimeInputSettingsCommandKind.Refresh);
|
|
return true;
|
|
case AetheriaRuntimeInputSettingsCommands.CancelCapture:
|
|
command = new AetheriaRuntimeInputSettingsSurfaceCommand(
|
|
AetheriaRuntimeInputSettingsCommandKind.CancelCapture);
|
|
return true;
|
|
case AetheriaRuntimeInputSettingsCommands.BeginCapture:
|
|
var actionName = ReadString(request, "actionName");
|
|
command = new AetheriaRuntimeInputSettingsSurfaceCommand(
|
|
AetheriaRuntimeInputSettingsCommandKind.BeginCapture,
|
|
actionName: actionName,
|
|
bindingIndex: ReadInt(request, "bindingIndex", -1),
|
|
bindingLabel: ReadString(request, "bindingLabel", actionName));
|
|
return true;
|
|
case AetheriaRuntimeInputSettingsCommands.ToggleActionBar:
|
|
command = new AetheriaRuntimeInputSettingsSurfaceCommand(
|
|
AetheriaRuntimeInputSettingsCommandKind.ToggleActionBar,
|
|
inputPath: ReadString(request, "inputPath"),
|
|
enabled: ReadBool(request, "enabled", false));
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static string ReadString(EveSurfaceCommandRequest request, string key, string defaultValue = "")
|
|
{
|
|
return request.Payload.GetString(key, defaultValue);
|
|
}
|
|
|
|
private static int ReadInt(EveSurfaceCommandRequest request, string key, int defaultValue)
|
|
{
|
|
return request.Payload.GetInt32(key, defaultValue);
|
|
}
|
|
|
|
private static bool ReadBool(EveSurfaceCommandRequest request, string key, bool defaultValue)
|
|
{
|
|
return request.Payload.GetBoolean(key, defaultValue);
|
|
}
|
|
}
|
|
}
|