using System;
using System.Collections.Generic;
using System.IO;
using Uigenx.Protocol;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Uigenx.Editor
{
///
/// Rebuilds `UgxScreenRegistry.asset` from the bundle manifest. Runs after every import so the
/// runtime never looks up a screen the project no longer has.
///
public static class UgxScreenRegistryBuilder
{
/// Under Resources so a player build resolves the registry without a scene reference.
public const string GeneratedResourcesFolder = "Generated/Resources";
public const string RegistryFileName = "UgxScreenRegistry.asset";
public const string SettingsFileName = "UgxSettings.asset";
public static string RegistryPath(string assetsUiFolder)
=> assetsUiFolder.TrimEnd('/') + "/" + GeneratedResourcesFolder + "/" + RegistryFileName;
public static string SettingsPath(string assetsUiFolder)
=> assetsUiFolder.TrimEnd('/') + "/" + GeneratedResourcesFolder + "/" + SettingsFileName;
///
/// Builds the registry from `Generated/ugx-manifest.json` instead of a live bundle. This is
/// the offline path: `ugx export --to unity` with no daemon running. Returns null when
/// there is no readable manifest, so the caller can tell "nothing to do" from "rebuilt".
///
public static UgxScreenRegistry RebuildFromDisk(string assetsUiFolder = null)
{
if (string.IsNullOrEmpty(assetsUiFolder)) assetsUiFolder = UgxProjectSettings.Load().AssetsUiFolder;
if (!UgxManifestFile.TryLoad(assetsUiFolder, out var manifest, out var problem))
{
Debug.LogWarning("[ugx] " + problem);
return null;
}
var registry = Rebuild(manifest.ToManifest(), assetsUiFolder);
Debug.Log("[ugx] rebuilt the screen registry from " + UgxManifestFile.PathFor(assetsUiFolder) +
": " + registry.Entries.Count + " screen(s)");
return registry;
}
/// Entry point for `-executeMethod` in a headless export.
public static void RebuildFromDiskCommandLine() => RebuildFromDisk();
public static UgxScreenRegistry Rebuild(UgxBundleManifest manifest, string assetsUiFolder)
{
if (string.IsNullOrEmpty(assetsUiFolder)) assetsUiFolder = UgxProjectSettings.Load().AssetsUiFolder;
var path = RegistryPath(assetsUiFolder);
EnsureFolder(Path.GetDirectoryName(path).Replace('\\', '/'));
var registry = AssetDatabase.LoadAssetAtPath(path);
if (registry == null)
{
registry = ScriptableObject.CreateInstance();
AssetDatabase.CreateAsset(registry, path);
}
var entries = new List(manifest?.Screens.Count ?? 0);
if (manifest != null)
{
foreach (var screen in manifest.Screens)
{
if (string.IsNullOrEmpty(screen.Key)) continue;
var uxmlPath = UgxBundleImporter.CombineAssetPath(assetsUiFolder, screen.Uxml);
var uxml = AssetDatabase.LoadAssetAtPath(uxmlPath);
if (uxml == null)
Debug.LogWarning("[ugx] screen '" + screen.Key + "' has no VisualTreeAsset at " + uxmlPath);
if (!UgxLayers.TryParse(screen.Layer, out var layer))
layer = UgxLayer.Hud;
entries.Add(new UgxScreenEntry(screen.Key, uxml, screen.ClassName, layer,
LoadSiblingStyles(uxmlPath)));
}
}
registry.SetEntries(entries.ToArray());
EditorUtility.SetDirty(registry);
AssetDatabase.SaveAssets();
var panels = UgxPanelBuilder.Build(manifest?.Panels, assetsUiFolder);
foreach (var warning in panels.Warnings) Debug.LogWarning("[ugx] " + warning);
EnsureSettings(assetsUiFolder, registry, panels.Default);
UgxScreenRegistry.Active = registry;
return registry;
}
/// Creates the runtime settings asset if the project has none, and points it at the registry.
public static UgxSettings EnsureSettings(string assetsUiFolder, UgxScreenRegistry registry,
PanelSettings defaultPanel = null)
{
var existing = Resources.Load(UgxSettings.ResourcePath);
if (existing != null)
{
LinkRegistry(existing, registry);
LinkDefaultPanel(existing, defaultPanel);
return existing;
}
var path = SettingsPath(assetsUiFolder);
EnsureFolder(Path.GetDirectoryName(path).Replace('\\', '/'));
var settings = ScriptableObject.CreateInstance();
AssetDatabase.CreateAsset(settings, path);
LinkRegistry(settings, registry);
LinkDefaultPanel(settings, defaultPanel);
UgxSettings.Override(settings);
return settings;
}
/// The field is private on purpose; the editor writes it through SerializedObject.
static void LinkRegistry(UgxSettings settings, UgxScreenRegistry registry)
{
if (settings == null || registry == null) return;
var serialized = new SerializedObject(settings);
var property = serialized.FindProperty("screenRegistry");
if (property == null || property.objectReferenceValue == registry) return;
property.objectReferenceValue = registry;
serialized.ApplyModifiedPropertiesWithoutUndo();
AssetDatabase.SaveAssets();
}
///
/// Points the settings at a panel, but only when the project has not chosen one. Which panel
/// the game draws on is a decision an export must not take back.
///
static void LinkDefaultPanel(UgxSettings settings, PanelSettings panel)
{
if (settings == null || panel == null) return;
var serialized = new SerializedObject(settings);
var property = serialized.FindProperty("defaultPanelSettings");
if (property == null || property.objectReferenceValue != null) return;
property.objectReferenceValue = panel;
serialized.ApplyModifiedPropertiesWithoutUndo();
AssetDatabase.SaveAssets();
}
static StyleSheet[] LoadSiblingStyles(string uxmlPath)
{
var ussPath = Path.ChangeExtension(uxmlPath, ".uss");
var sheet = AssetDatabase.LoadAssetAtPath(ussPath);
return sheet != null ? new[] { sheet } : Array.Empty();
}
static void EnsureFolder(string folder)
{
if (string.IsNullOrEmpty(folder) || AssetDatabase.IsValidFolder(folder)) return;
var parts = folder.Split('/');
var current = parts[0];
for (int i = 1; i < parts.Length; i++)
{
var next = current + "/" + parts[i];
if (!AssetDatabase.IsValidFolder(next)) AssetDatabase.CreateFolder(current, parts[i]);
current = next;
}
}
}
}