2026-07-02 12:51:54 +01:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using GameCult.Eve.Surface;
|
|
|
|
|
using GameCult.Eve.UnityUIToolkit;
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEditor.UIElements;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.UIElements;
|
|
|
|
|
|
|
|
|
|
public sealed class AetheriaCultUiCompositionSurfaceWindow : EditorWindow
|
|
|
|
|
{
|
|
|
|
|
private const string DefaultSurfaceId = "aetheria.menu.draft";
|
|
|
|
|
private const string DefaultProviderId = "aetheria";
|
|
|
|
|
private const string DefaultProviderKind = "game.menu.draft";
|
|
|
|
|
|
|
|
|
|
private readonly List<MenuActionDraft> _actions = new List<MenuActionDraft>
|
|
|
|
|
{
|
|
|
|
|
new MenuActionDraft("Continue", "aetheria.menu.draft.continue"),
|
|
|
|
|
new MenuActionDraft("New Game", "aetheria.menu.draft.new_game"),
|
|
|
|
|
new MenuActionDraft("Settings", "aetheria.menu.draft.settings"),
|
|
|
|
|
new MenuActionDraft("Quit", "aetheria.menu.draft.quit")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private TextField _surfaceId = null!;
|
|
|
|
|
private TextField _providerId = null!;
|
|
|
|
|
private TextField _providerKind = null!;
|
|
|
|
|
private TextField _title = null!;
|
|
|
|
|
private TextField _subtitle = null!;
|
|
|
|
|
private TextField _note = null!;
|
|
|
|
|
private Toggle _showStatusMetrics = null!;
|
|
|
|
|
private Toggle _useButtonRow = null!;
|
|
|
|
|
private VisualElement _actionList = null!;
|
|
|
|
|
private VisualElement _previewHost = null!;
|
|
|
|
|
private TextField _builderText = null!;
|
|
|
|
|
private Label _commandTrace = null!;
|
|
|
|
|
private long _version = 1;
|
|
|
|
|
|
|
|
|
|
[MenuItem("Aetheria/CultUI/Live Composition Surface")]
|
|
|
|
|
public static void Open()
|
|
|
|
|
{
|
|
|
|
|
var window = GetWindow<AetheriaCultUiCompositionSurfaceWindow>();
|
|
|
|
|
window.titleContent = new GUIContent("CultUI Composition");
|
|
|
|
|
window.minSize = new Vector2(860f, 520f);
|
|
|
|
|
window.Show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CreateGUI()
|
|
|
|
|
{
|
|
|
|
|
rootVisualElement.Clear();
|
|
|
|
|
rootVisualElement.style.flexDirection = FlexDirection.Column;
|
|
|
|
|
rootVisualElement.style.backgroundColor = new Color(0.07f, 0.08f, 0.1f, 1f);
|
|
|
|
|
|
|
|
|
|
var toolbar = new Toolbar();
|
|
|
|
|
var addButton = new ToolbarButton(AddAction) { text = "+ Action" };
|
|
|
|
|
var copyButton = new ToolbarButton(CopyBuilder) { text = "Copy Builder" };
|
|
|
|
|
var resetButton = new ToolbarButton(ResetDraft) { text = "Reset" };
|
|
|
|
|
toolbar.Add(addButton);
|
|
|
|
|
toolbar.Add(copyButton);
|
|
|
|
|
toolbar.Add(resetButton);
|
|
|
|
|
rootVisualElement.Add(toolbar);
|
|
|
|
|
|
|
|
|
|
var body = new TwoPaneSplitView(0, 360, TwoPaneSplitViewOrientation.Horizontal);
|
|
|
|
|
rootVisualElement.Add(body);
|
|
|
|
|
body.style.flexGrow = 1f;
|
|
|
|
|
|
|
|
|
|
var controls = new ScrollView();
|
|
|
|
|
controls.style.paddingLeft = 10f;
|
|
|
|
|
controls.style.paddingRight = 10f;
|
|
|
|
|
controls.style.paddingTop = 10f;
|
|
|
|
|
controls.style.paddingBottom = 10f;
|
|
|
|
|
body.Add(controls);
|
|
|
|
|
|
|
|
|
|
_surfaceId = TextInput("Surface Id", DefaultSurfaceId, controls);
|
|
|
|
|
_providerId = TextInput("Provider Id", DefaultProviderId, controls);
|
|
|
|
|
_providerKind = TextInput("Provider Kind", DefaultProviderKind, controls);
|
|
|
|
|
_title = TextInput("Title", "AETHERIA", controls);
|
2026-07-05 18:07:21 +01:00
|
|
|
_subtitle = TextInput("Subtitle", "STARBRIDGE", controls);
|
2026-07-02 12:51:54 +01:00
|
|
|
_note = TextInput("Note", "Live CultUI draft lowered through the shared Eve UI Toolkit path.", controls);
|
|
|
|
|
_showStatusMetrics = ToggleInput("Status Metrics", true, controls);
|
|
|
|
|
_useButtonRow = ToggleInput("Button Row", false, controls);
|
|
|
|
|
|
|
|
|
|
controls.Add(SectionLabel("Actions"));
|
|
|
|
|
_actionList = new VisualElement();
|
|
|
|
|
_actionList.style.flexDirection = FlexDirection.Column;
|
|
|
|
|
controls.Add(_actionList);
|
|
|
|
|
|
|
|
|
|
_commandTrace = new Label("No commands invoked.");
|
|
|
|
|
_commandTrace.style.whiteSpace = WhiteSpace.Normal;
|
|
|
|
|
_commandTrace.style.marginTop = 10f;
|
|
|
|
|
controls.Add(_commandTrace);
|
|
|
|
|
|
|
|
|
|
var output = new VisualElement();
|
|
|
|
|
output.style.flexDirection = FlexDirection.Column;
|
|
|
|
|
output.style.flexGrow = 1f;
|
|
|
|
|
body.Add(output);
|
|
|
|
|
|
|
|
|
|
_previewHost = new VisualElement();
|
|
|
|
|
_previewHost.style.flexGrow = 1f;
|
|
|
|
|
_previewHost.style.paddingLeft = 16f;
|
|
|
|
|
_previewHost.style.paddingRight = 16f;
|
|
|
|
|
_previewHost.style.paddingTop = 16f;
|
|
|
|
|
_previewHost.style.paddingBottom = 16f;
|
|
|
|
|
_previewHost.style.backgroundColor = new Color(0.02f, 0.025f, 0.035f, 1f);
|
|
|
|
|
output.Add(_previewHost);
|
|
|
|
|
|
|
|
|
|
_builderText = new TextField("Provider Builder");
|
|
|
|
|
_builderText.multiline = true;
|
|
|
|
|
_builderText.isReadOnly = true;
|
|
|
|
|
_builderText.style.height = 170f;
|
|
|
|
|
_builderText.style.marginLeft = 8f;
|
|
|
|
|
_builderText.style.marginRight = 8f;
|
|
|
|
|
_builderText.style.marginBottom = 8f;
|
|
|
|
|
output.Add(_builderText);
|
|
|
|
|
|
|
|
|
|
RebuildActionList();
|
|
|
|
|
RebuildSurface();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private TextField TextInput(string label, string value, VisualElement parent)
|
|
|
|
|
{
|
|
|
|
|
var field = new TextField(label) { value = value };
|
|
|
|
|
field.RegisterValueChangedCallback(_ => RebuildSurface());
|
|
|
|
|
parent.Add(field);
|
|
|
|
|
return field;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Toggle ToggleInput(string label, bool value, VisualElement parent)
|
|
|
|
|
{
|
|
|
|
|
var toggle = new Toggle(label) { value = value };
|
|
|
|
|
toggle.RegisterValueChangedCallback(_ => RebuildSurface());
|
|
|
|
|
parent.Add(toggle);
|
|
|
|
|
return toggle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Label SectionLabel(string text)
|
|
|
|
|
{
|
|
|
|
|
var label = new Label(text);
|
|
|
|
|
label.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
|
|
|
label.style.marginTop = 12f;
|
|
|
|
|
label.style.marginBottom = 4f;
|
|
|
|
|
return label;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RebuildActionList()
|
|
|
|
|
{
|
|
|
|
|
_actionList.Clear();
|
|
|
|
|
for (var index = 0; index < _actions.Count; index++)
|
|
|
|
|
{
|
|
|
|
|
var action = _actions[index];
|
|
|
|
|
var row = new VisualElement();
|
|
|
|
|
row.style.flexDirection = FlexDirection.Row;
|
|
|
|
|
row.style.marginBottom = 4f;
|
|
|
|
|
|
|
|
|
|
var label = new TextField { value = action.Label };
|
|
|
|
|
label.style.flexGrow = 1f;
|
|
|
|
|
label.RegisterValueChangedCallback(evt =>
|
|
|
|
|
{
|
|
|
|
|
action.Label = evt.newValue ?? "";
|
|
|
|
|
RebuildSurface();
|
|
|
|
|
});
|
|
|
|
|
row.Add(label);
|
|
|
|
|
|
|
|
|
|
var command = new TextField { value = action.Command };
|
|
|
|
|
command.style.flexGrow = 1f;
|
|
|
|
|
command.RegisterValueChangedCallback(evt =>
|
|
|
|
|
{
|
|
|
|
|
action.Command = evt.newValue ?? "";
|
|
|
|
|
RebuildSurface();
|
|
|
|
|
});
|
|
|
|
|
row.Add(command);
|
|
|
|
|
|
|
|
|
|
var remove = new Button(() =>
|
|
|
|
|
{
|
|
|
|
|
_actions.Remove(action);
|
|
|
|
|
RebuildActionList();
|
|
|
|
|
RebuildSurface();
|
|
|
|
|
})
|
|
|
|
|
{
|
|
|
|
|
text = "X"
|
|
|
|
|
};
|
|
|
|
|
remove.style.width = 28f;
|
|
|
|
|
row.Add(remove);
|
|
|
|
|
_actionList.Add(row);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void AddAction()
|
|
|
|
|
{
|
|
|
|
|
var count = _actions.Count + 1;
|
|
|
|
|
var surfaceId = Clean(_surfaceId == null ? "" : _surfaceId.value, DefaultSurfaceId);
|
|
|
|
|
_actions.Add(new MenuActionDraft($"Action {count}", $"{surfaceId}.action_{count}"));
|
|
|
|
|
RebuildActionList();
|
|
|
|
|
RebuildSurface();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ResetDraft()
|
|
|
|
|
{
|
|
|
|
|
_actions.Clear();
|
|
|
|
|
_actions.Add(new MenuActionDraft("Continue", "aetheria.menu.draft.continue"));
|
|
|
|
|
_actions.Add(new MenuActionDraft("New Game", "aetheria.menu.draft.new_game"));
|
|
|
|
|
_actions.Add(new MenuActionDraft("Settings", "aetheria.menu.draft.settings"));
|
|
|
|
|
_actions.Add(new MenuActionDraft("Quit", "aetheria.menu.draft.quit"));
|
|
|
|
|
|
|
|
|
|
_surfaceId.value = DefaultSurfaceId;
|
|
|
|
|
_providerId.value = DefaultProviderId;
|
|
|
|
|
_providerKind.value = DefaultProviderKind;
|
|
|
|
|
_title.value = "AETHERIA";
|
2026-07-05 18:07:21 +01:00
|
|
|
_subtitle.value = "STARBRIDGE";
|
2026-07-02 12:51:54 +01:00
|
|
|
_note.value = "Live CultUI draft lowered through the shared Eve UI Toolkit path.";
|
|
|
|
|
_showStatusMetrics.value = true;
|
|
|
|
|
_useButtonRow.value = false;
|
|
|
|
|
RebuildActionList();
|
|
|
|
|
RebuildSurface();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RebuildSurface()
|
|
|
|
|
{
|
|
|
|
|
if (_previewHost == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var document = BuildSurface();
|
|
|
|
|
_previewHost.Clear();
|
|
|
|
|
|
|
|
|
|
var lowerer = new EveUiToolkitSurfaceLowerer();
|
|
|
|
|
var lowered = lowerer.Lower(document, request =>
|
|
|
|
|
{
|
|
|
|
|
_commandTrace.text = $"Command preview: {request.Operation.OperationId}";
|
|
|
|
|
});
|
|
|
|
|
ApplyPreviewFrame(lowered);
|
|
|
|
|
_previewHost.Add(lowered);
|
|
|
|
|
_builderText.value = BuildProviderSnippet(document);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private EveSurfaceDocument BuildSurface()
|
|
|
|
|
{
|
|
|
|
|
var surfaceId = Clean(_surfaceId == null ? "" : _surfaceId.value, DefaultSurfaceId);
|
|
|
|
|
var builder = EveSurface
|
|
|
|
|
.Create(surfaceId)
|
|
|
|
|
.Provider(
|
|
|
|
|
Clean(_providerId == null ? "" : _providerId.value, DefaultProviderId),
|
|
|
|
|
Clean(_providerKind == null ? "" : _providerKind.value, DefaultProviderKind))
|
|
|
|
|
.Version(++_version)
|
|
|
|
|
.UpdatedAtUtc(DateTime.UtcNow.ToString("O"));
|
|
|
|
|
|
|
|
|
|
foreach (var token in StyleTokens())
|
|
|
|
|
builder.Style(token.Name, token.Value);
|
|
|
|
|
|
|
|
|
|
var title = _title?.value ?? "";
|
|
|
|
|
var subtitle = _subtitle?.value ?? "";
|
|
|
|
|
if (string.IsNullOrWhiteSpace(subtitle))
|
|
|
|
|
builder.Title(title);
|
|
|
|
|
else
|
|
|
|
|
builder.TitleSubtitle(title, subtitle);
|
|
|
|
|
|
|
|
|
|
if (_note != null && !string.IsNullOrWhiteSpace(_note.value))
|
|
|
|
|
builder.Text(_note.value, $"{surfaceId}.note");
|
|
|
|
|
|
|
|
|
|
if (_showStatusMetrics?.value == true)
|
|
|
|
|
{
|
|
|
|
|
builder.Form(
|
|
|
|
|
$"{surfaceId}.status",
|
|
|
|
|
form => form
|
|
|
|
|
.Metric("Surface", surfaceId)
|
|
|
|
|
.Metric("Actions", _actions.Count.ToString())
|
|
|
|
|
.Metric("Version", _version.ToString()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_useButtonRow?.value == true)
|
|
|
|
|
{
|
|
|
|
|
builder.ButtonRow($"{surfaceId}.actions", group => AddButtons(group));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
builder.ButtonColumn($"{surfaceId}.actions", group => AddButtons(group));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return builder.Build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void AddButtons(EveSurfaceGroupBuilder group)
|
|
|
|
|
{
|
|
|
|
|
foreach (var action in _actions)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(action.Label) || string.IsNullOrWhiteSpace(action.Command))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
group.Button(action.Label, action.Command);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IReadOnlyList<EveStyleToken> StyleTokens()
|
|
|
|
|
{
|
|
|
|
|
return new[]
|
|
|
|
|
{
|
|
|
|
|
new EveStyleToken("font.title.family", "Montserrat"),
|
|
|
|
|
new EveStyleToken("font.title.style", "Thin"),
|
|
|
|
|
new EveStyleToken("font.body.family", "Ubuntu"),
|
|
|
|
|
new EveStyleToken("font.body.style", "Regular")
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ApplyPreviewFrame(VisualElement root)
|
|
|
|
|
{
|
|
|
|
|
root.style.maxWidth = 520f;
|
|
|
|
|
root.style.paddingLeft = 16f;
|
|
|
|
|
root.style.paddingRight = 16f;
|
|
|
|
|
root.style.paddingTop = 14f;
|
|
|
|
|
root.style.paddingBottom = 14f;
|
|
|
|
|
root.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.96f);
|
|
|
|
|
root.style.borderTopLeftRadius = 8f;
|
|
|
|
|
root.style.borderTopRightRadius = 8f;
|
|
|
|
|
root.style.borderBottomLeftRadius = 8f;
|
|
|
|
|
root.style.borderBottomRightRadius = 8f;
|
|
|
|
|
root.style.borderLeftWidth = 1f;
|
|
|
|
|
root.style.borderRightWidth = 1f;
|
|
|
|
|
root.style.borderTopWidth = 1f;
|
|
|
|
|
root.style.borderBottomWidth = 1f;
|
|
|
|
|
root.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f);
|
|
|
|
|
root.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f);
|
|
|
|
|
root.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f);
|
|
|
|
|
root.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CopyBuilder()
|
|
|
|
|
{
|
|
|
|
|
EditorGUIUtility.systemCopyBuffer = _builderText?.value ?? "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string BuildProviderSnippet(EveSurfaceDocument document)
|
|
|
|
|
{
|
|
|
|
|
var surfaceId = CSharpString(document.Surface.Id);
|
|
|
|
|
var builder = new StringBuilder();
|
|
|
|
|
builder.AppendLine("var surface = EveSurface");
|
|
|
|
|
builder.AppendLine($" .Create({surfaceId})");
|
|
|
|
|
builder.AppendLine($" .Provider({CSharpString(document.ProviderId)}, {CSharpString(document.ProviderKind)})");
|
|
|
|
|
builder.AppendLine(" .Version(version)");
|
|
|
|
|
builder.AppendLine(" .UpdatedAtUtc(updatedAtUtc)");
|
|
|
|
|
foreach (var token in document.Surface.Styles)
|
|
|
|
|
builder.AppendLine($" .Style({CSharpString(token.Name)}, {CSharpString(token.Value)})");
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(_subtitle?.value))
|
|
|
|
|
builder.AppendLine($" .Title({CSharpString(_title?.value ?? "")})");
|
|
|
|
|
else
|
|
|
|
|
builder.AppendLine($" .TitleSubtitle({CSharpString(_title?.value ?? "")}, {CSharpString(_subtitle?.value ?? "")})");
|
|
|
|
|
|
|
|
|
|
var note = _note == null ? "" : _note.value;
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(note))
|
|
|
|
|
builder.AppendLine($" .Text({CSharpString(note)}, {CSharpString(document.Surface.Id + ".note")})");
|
|
|
|
|
|
|
|
|
|
if (_showStatusMetrics?.value == true)
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLine($" .Form({CSharpString(document.Surface.Id + ".status")}, form => form");
|
|
|
|
|
builder.AppendLine($" .Metric(@\"Surface\", {CSharpString(document.Surface.Id)})");
|
|
|
|
|
builder.AppendLine($" .Metric(@\"Actions\", {_actions.Count}.ToString())");
|
|
|
|
|
builder.AppendLine(" .Metric(@\"Version\", version.ToString()))");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder.AppendLine($" .Button{(_useButtonRow?.value == true ? "Row" : "Column")}({CSharpString(document.Surface.Id + ".actions")}, actions =>");
|
|
|
|
|
builder.AppendLine(" {");
|
|
|
|
|
foreach (var action in _actions)
|
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(action.Label) && !string.IsNullOrWhiteSpace(action.Command))
|
|
|
|
|
builder.AppendLine($" actions.Button({CSharpString(action.Label)}, {CSharpString(action.Command)});");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder.AppendLine(" })");
|
|
|
|
|
builder.AppendLine(" .Build();");
|
|
|
|
|
return builder.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string Clean(string value, string fallback)
|
|
|
|
|
{
|
|
|
|
|
return string.IsNullOrWhiteSpace(value) ? fallback : value.Trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string CSharpString(string value)
|
|
|
|
|
{
|
|
|
|
|
return "@\"" + (value ?? "").Replace("\"", "\"\"") + "\"";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private sealed class MenuActionDraft
|
|
|
|
|
{
|
|
|
|
|
public MenuActionDraft(string label, string command)
|
|
|
|
|
{
|
|
|
|
|
Label = label;
|
|
|
|
|
Command = command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string Label;
|
|
|
|
|
public string Command;
|
|
|
|
|
}
|
|
|
|
|
}
|