Add shared Unity Eve packages
This commit is contained in:
parent
83dee183f7
commit
39a994a879
10 changed files with 514 additions and 1 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -1,5 +1,10 @@
|
||||||
.theos/
|
.theos/
|
||||||
packages/
|
packages/*
|
||||||
|
!packages/.gitkeep
|
||||||
|
!packages/org.gamecult.eve.surface/
|
||||||
|
!packages/org.gamecult.eve.surface/**
|
||||||
|
!packages/org.gamecult.eve.unity-uitoolkit/
|
||||||
|
!packages/org.gamecult.eve.unity-uitoolkit/**
|
||||||
obj/
|
obj/
|
||||||
artifacts/
|
artifacts/
|
||||||
tools/deps/
|
tools/deps/
|
||||||
|
|
|
||||||
1
packages/.gitkeep
Normal file
1
packages/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
154
packages/org.gamecult.eve.surface/Runtime/EveSurfaceDocument.cs
Normal file
154
packages/org.gamecult.eve.surface/Runtime/EveSurfaceDocument.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace GameCult.Eve.Surface
|
||||||
|
{
|
||||||
|
public sealed class EveSurfaceDocument
|
||||||
|
{
|
||||||
|
public EveSurfaceDocument(
|
||||||
|
string type,
|
||||||
|
string schema,
|
||||||
|
string providerId,
|
||||||
|
string providerKind,
|
||||||
|
string title,
|
||||||
|
long version,
|
||||||
|
string updatedAtUtc,
|
||||||
|
EveSurfaceTree surface,
|
||||||
|
IReadOnlyList<EveCommandTemplate> commands)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Schema = schema;
|
||||||
|
ProviderId = providerId;
|
||||||
|
ProviderKind = providerKind;
|
||||||
|
Title = title;
|
||||||
|
Version = version;
|
||||||
|
UpdatedAtUtc = updatedAtUtc;
|
||||||
|
Surface = surface;
|
||||||
|
Commands = commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Type { get; }
|
||||||
|
|
||||||
|
public string Schema { get; }
|
||||||
|
|
||||||
|
public string ProviderId { get; }
|
||||||
|
|
||||||
|
public string ProviderKind { get; }
|
||||||
|
|
||||||
|
public string Title { get; }
|
||||||
|
|
||||||
|
public long Version { get; }
|
||||||
|
|
||||||
|
public string UpdatedAtUtc { get; }
|
||||||
|
|
||||||
|
public EveSurfaceTree Surface { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<EveCommandTemplate> Commands { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class EveSurfaceTree
|
||||||
|
{
|
||||||
|
public EveSurfaceTree(string id, EveSurfaceComponent root, IReadOnlyList<EveStyleToken> styles)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Root = root;
|
||||||
|
Styles = styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id { get; }
|
||||||
|
|
||||||
|
public EveSurfaceComponent Root { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<EveStyleToken> Styles { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class EveSurfaceComponent
|
||||||
|
{
|
||||||
|
public EveSurfaceComponent(
|
||||||
|
string id,
|
||||||
|
string kind,
|
||||||
|
IReadOnlyDictionary<string, string> props,
|
||||||
|
IReadOnlyList<EveSurfaceComponent> children)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Kind = kind;
|
||||||
|
Props = props;
|
||||||
|
Children = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id { get; }
|
||||||
|
|
||||||
|
public string Kind { get; }
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, string> Props { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<EveSurfaceComponent> Children { get; }
|
||||||
|
|
||||||
|
public string GetProp(string key, string fallback = "")
|
||||||
|
{
|
||||||
|
return Props.TryGetValue(key, out var value) ? value : fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class EveStyleToken
|
||||||
|
{
|
||||||
|
public EveStyleToken(string name, string value)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public string Value { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class EveCommandTemplate
|
||||||
|
{
|
||||||
|
public EveCommandTemplate(string command, string label, string transport)
|
||||||
|
{
|
||||||
|
Command = command;
|
||||||
|
Label = label;
|
||||||
|
Transport = transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Command { get; }
|
||||||
|
|
||||||
|
public string Label { get; }
|
||||||
|
|
||||||
|
public string Transport { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class EveSurfaceCommandRequest
|
||||||
|
{
|
||||||
|
public EveSurfaceCommandRequest(
|
||||||
|
string providerId,
|
||||||
|
string surfaceId,
|
||||||
|
string command,
|
||||||
|
IReadOnlyDictionary<string, string> payload,
|
||||||
|
DateTimeOffset issuedAt,
|
||||||
|
string clientId)
|
||||||
|
{
|
||||||
|
ProviderId = providerId;
|
||||||
|
SurfaceId = surfaceId;
|
||||||
|
Command = command;
|
||||||
|
Payload = payload;
|
||||||
|
IssuedAt = issuedAt;
|
||||||
|
ClientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ProviderId { get; }
|
||||||
|
|
||||||
|
public string SurfaceId { get; }
|
||||||
|
|
||||||
|
public string Command { get; }
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, string> Payload { get; }
|
||||||
|
|
||||||
|
public DateTimeOffset IssuedAt { get; }
|
||||||
|
|
||||||
|
public string ClientId { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"name": "GameCult.Eve.Surface",
|
||||||
|
"rootNamespace": "GameCult.Eve.Surface",
|
||||||
|
"references": [],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": true
|
||||||
|
}
|
||||||
15
packages/org.gamecult.eve.surface/package.json
Normal file
15
packages/org.gamecult.eve.surface/package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "org.gamecult.eve.surface",
|
||||||
|
"displayName": "Eve Surface Contract",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"unity": "2021.3",
|
||||||
|
"description": "Unity-compatible DTOs for gamecult.eve.surface.v1 documents.",
|
||||||
|
"keywords": [
|
||||||
|
"Eve",
|
||||||
|
"CultUI",
|
||||||
|
"CultMesh",
|
||||||
|
"Surface"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"category": "Scripting"
|
||||||
|
}
|
||||||
12
packages/org.gamecult.eve.unity-uitoolkit/README.md
Normal file
12
packages/org.gamecult.eve.unity-uitoolkit/README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Eve UI Toolkit Lowering
|
||||||
|
|
||||||
|
This package lowers `gamecult.eve.surface.v1` retained surface documents into
|
||||||
|
Unity UI Toolkit `VisualElement` trees.
|
||||||
|
|
||||||
|
It owns native projection only. Providers still own truth, accepted state,
|
||||||
|
style token values, and command effects through CultMesh/CultNet. Unknown
|
||||||
|
component kinds degrade to inert containers instead of gaining local semantics.
|
||||||
|
|
||||||
|
This package lives in the Eve repository as the shared Unity lowering target.
|
||||||
|
Aetheria and other Unity consumers should import it from Eve instead of
|
||||||
|
carrying local copies.
|
||||||
|
|
@ -0,0 +1,269 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GameCult.Eve.Surface;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace GameCult.Eve.UnityUIToolkit
|
||||||
|
{
|
||||||
|
public sealed class EveUiToolkitSurfaceLowerer
|
||||||
|
{
|
||||||
|
private readonly EveUiToolkitSurfaceOptions _options;
|
||||||
|
|
||||||
|
public EveUiToolkitSurfaceLowerer(EveUiToolkitSurfaceOptions? options = null)
|
||||||
|
{
|
||||||
|
_options = options ?? EveUiToolkitSurfaceOptions.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VisualElement Lower(EveSurfaceDocument document, Action<EveSurfaceCommandRequest>? commandSink = null)
|
||||||
|
{
|
||||||
|
if (document == null) throw new ArgumentNullException(nameof(document));
|
||||||
|
|
||||||
|
var root = LowerComponent(document.Surface.Root, document, commandSink);
|
||||||
|
root.name = string.IsNullOrWhiteSpace(root.name) ? document.Surface.Id : root.name;
|
||||||
|
root.AddToClassList("eve-surface-root");
|
||||||
|
ApplyStyleTokens(root, document.Surface.Styles);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VisualElement LowerComponent(
|
||||||
|
EveSurfaceComponent component,
|
||||||
|
EveSurfaceDocument document,
|
||||||
|
Action<EveSurfaceCommandRequest>? commandSink)
|
||||||
|
{
|
||||||
|
var element = CreateElement(component, document, commandSink);
|
||||||
|
element.name = SafeName(component.Id);
|
||||||
|
element.AddToClassList("eve-component");
|
||||||
|
element.AddToClassList($"eve-kind-{SafeClass(component.Kind)}");
|
||||||
|
element.userData = component;
|
||||||
|
|
||||||
|
foreach (var child in component.Children)
|
||||||
|
element.Add(LowerComponent(child, document, commandSink));
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VisualElement CreateElement(
|
||||||
|
EveSurfaceComponent component,
|
||||||
|
EveSurfaceDocument document,
|
||||||
|
Action<EveSurfaceCommandRequest>? commandSink)
|
||||||
|
{
|
||||||
|
switch (NormalizeKind(component.Kind))
|
||||||
|
{
|
||||||
|
case "surface":
|
||||||
|
{
|
||||||
|
var element = new VisualElement();
|
||||||
|
element.style.flexGrow = 1;
|
||||||
|
element.style.flexDirection = FlexDirection.Column;
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
case "grid":
|
||||||
|
{
|
||||||
|
var element = new VisualElement();
|
||||||
|
element.style.flexDirection = FlexDirection.Row;
|
||||||
|
element.style.flexWrap = Wrap.Wrap;
|
||||||
|
element.style.alignItems = Align.Stretch;
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
case "card":
|
||||||
|
{
|
||||||
|
var card = new VisualElement();
|
||||||
|
card.AddToClassList("eve-card");
|
||||||
|
card.style.flexDirection = FlexDirection.Column;
|
||||||
|
var title = component.GetProp("title");
|
||||||
|
if (!string.IsNullOrWhiteSpace(title))
|
||||||
|
card.Add(TitleLabel(title));
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
case "metric":
|
||||||
|
{
|
||||||
|
var metric = new VisualElement();
|
||||||
|
metric.AddToClassList("eve-metric");
|
||||||
|
metric.style.flexDirection = FlexDirection.Column;
|
||||||
|
metric.Add(MutedLabel(component.GetProp("label")));
|
||||||
|
metric.Add(ValueLabel(component.GetProp("value")));
|
||||||
|
return metric;
|
||||||
|
}
|
||||||
|
case "inspector.kv":
|
||||||
|
{
|
||||||
|
var inspector = new VisualElement();
|
||||||
|
inspector.AddToClassList("eve-inspector");
|
||||||
|
inspector.style.flexDirection = FlexDirection.Column;
|
||||||
|
return inspector;
|
||||||
|
}
|
||||||
|
case "row":
|
||||||
|
{
|
||||||
|
var row = new VisualElement();
|
||||||
|
row.AddToClassList("eve-row");
|
||||||
|
row.style.flexDirection = FlexDirection.Row;
|
||||||
|
row.style.flexWrap = Wrap.Wrap;
|
||||||
|
foreach (var prop in component.Props)
|
||||||
|
row.Add(FieldLabel(prop.Key, prop.Value));
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
case "text":
|
||||||
|
{
|
||||||
|
return BodyLabel(component.GetProp("text", component.GetProp("value", component.GetProp("title"))));
|
||||||
|
}
|
||||||
|
case "control.button":
|
||||||
|
{
|
||||||
|
var label = component.GetProp("label", component.GetProp("title", "Invoke"));
|
||||||
|
var command = component.GetProp("command", component.GetProp("action", "invoke"));
|
||||||
|
return new Button(() => EmitCommand(document, component, command, commandSink)) { text = label };
|
||||||
|
}
|
||||||
|
case "control.text":
|
||||||
|
{
|
||||||
|
var label = component.GetProp("label", component.GetProp("title", "Value"));
|
||||||
|
var command = component.GetProp("command", component.GetProp("action", "set"));
|
||||||
|
var value = component.GetProp("value");
|
||||||
|
var field = new TextField(label);
|
||||||
|
field.SetValueWithoutNotify(value);
|
||||||
|
field.RegisterValueChangedCallback(evt =>
|
||||||
|
{
|
||||||
|
if (string.Equals(evt.newValue, value, StringComparison.Ordinal))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var payload = new Dictionary<string, string>(component.Props, StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
["value"] = evt.newValue ?? ""
|
||||||
|
};
|
||||||
|
EmitCommand(document, component, command, payload, commandSink);
|
||||||
|
});
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
var element = new VisualElement();
|
||||||
|
element.AddToClassList("eve-unknown");
|
||||||
|
var title = component.GetProp("title", component.GetProp("label"));
|
||||||
|
if (!string.IsNullOrWhiteSpace(title))
|
||||||
|
element.Add(BodyLabel(title));
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EmitCommand(
|
||||||
|
EveSurfaceDocument document,
|
||||||
|
EveSurfaceComponent component,
|
||||||
|
string command,
|
||||||
|
Action<EveSurfaceCommandRequest>? commandSink)
|
||||||
|
{
|
||||||
|
EmitCommand(
|
||||||
|
document,
|
||||||
|
component,
|
||||||
|
command,
|
||||||
|
new Dictionary<string, string>(component.Props, StringComparer.Ordinal),
|
||||||
|
commandSink);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EmitCommand(
|
||||||
|
EveSurfaceDocument document,
|
||||||
|
EveSurfaceComponent component,
|
||||||
|
string command,
|
||||||
|
IReadOnlyDictionary<string, string> payload,
|
||||||
|
Action<EveSurfaceCommandRequest>? commandSink)
|
||||||
|
{
|
||||||
|
if (commandSink == null || string.IsNullOrWhiteSpace(command))
|
||||||
|
return;
|
||||||
|
|
||||||
|
commandSink(new EveSurfaceCommandRequest(
|
||||||
|
document.ProviderId,
|
||||||
|
document.Surface.Id,
|
||||||
|
command,
|
||||||
|
payload,
|
||||||
|
DateTimeOffset.UtcNow,
|
||||||
|
"unity-uitoolkit"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label TitleLabel(string text)
|
||||||
|
{
|
||||||
|
var label = new Label(text);
|
||||||
|
label.AddToClassList("eve-title");
|
||||||
|
label.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label BodyLabel(string text)
|
||||||
|
{
|
||||||
|
var label = new Label(text);
|
||||||
|
label.AddToClassList("eve-text");
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label MutedLabel(string text)
|
||||||
|
{
|
||||||
|
var label = new Label(text);
|
||||||
|
label.AddToClassList("eve-muted");
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label ValueLabel(string text)
|
||||||
|
{
|
||||||
|
var label = new Label(text);
|
||||||
|
label.AddToClassList("eve-value");
|
||||||
|
label.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VisualElement FieldLabel(string key, string value)
|
||||||
|
{
|
||||||
|
var field = new VisualElement();
|
||||||
|
field.AddToClassList("eve-field");
|
||||||
|
field.style.flexDirection = FlexDirection.Row;
|
||||||
|
field.Add(MutedLabel(key));
|
||||||
|
field.Add(BodyLabel(value));
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyStyleTokens(VisualElement root, IReadOnlyList<EveStyleToken> tokens)
|
||||||
|
{
|
||||||
|
foreach (var token in tokens)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(token.Name) || string.IsNullOrWhiteSpace(token.Value))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
root.AddToClassList($"eve-style-token-{SafeClass(token.Name)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeKind(string kind)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(kind))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
if (kind == "panel.dialogue")
|
||||||
|
return "panel";
|
||||||
|
if (kind.StartsWith("text.", StringComparison.Ordinal))
|
||||||
|
return "text";
|
||||||
|
if (kind.StartsWith("control.button.", StringComparison.Ordinal))
|
||||||
|
return "control.button";
|
||||||
|
if (kind.StartsWith("control.text.", StringComparison.Ordinal))
|
||||||
|
return "control.text";
|
||||||
|
|
||||||
|
return kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SafeName(string value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(value) ? "eve.component" : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SafeClass(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return "unknown";
|
||||||
|
|
||||||
|
var characters = value.ToCharArray();
|
||||||
|
for (var index = 0; index < characters.Length; index++)
|
||||||
|
{
|
||||||
|
if (!char.IsLetterOrDigit(characters[index]))
|
||||||
|
characters[index] = '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string(characters).Trim('-').ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace GameCult.Eve.UnityUIToolkit
|
||||||
|
{
|
||||||
|
public sealed class EveUiToolkitSurfaceOptions
|
||||||
|
{
|
||||||
|
public static EveUiToolkitSurfaceOptions Default { get; } = new EveUiToolkitSurfaceOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "GameCult.Eve.UnityUIToolkit",
|
||||||
|
"rootNamespace": "GameCult.Eve.UnityUIToolkit",
|
||||||
|
"references": [
|
||||||
|
"GameCult.Eve.Surface"
|
||||||
|
],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
||||||
18
packages/org.gamecult.eve.unity-uitoolkit/package.json
Normal file
18
packages/org.gamecult.eve.unity-uitoolkit/package.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "org.gamecult.eve.unity-uitoolkit",
|
||||||
|
"displayName": "Eve UI Toolkit Lowering",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"unity": "2021.3",
|
||||||
|
"description": "Unity UI Toolkit lowering for gamecult.eve.surface.v1 documents.",
|
||||||
|
"dependencies": {
|
||||||
|
"org.gamecult.eve.surface": "0.1.0"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Eve",
|
||||||
|
"CultUI",
|
||||||
|
"UI Toolkit",
|
||||||
|
"Unity"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"category": "Scripting"
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue