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/
|
||||
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/
|
||||
artifacts/
|
||||
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