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; } } } }