using System; using AlicizaX.Editor; using AlicizaX.UI.Runtime; using UnityEditor; using UnityEngine; namespace AlicizaX.UI.Editor { [CustomEditor(typeof(UIComponent))] internal sealed class UIComponentInspector : GameFrameworkInspector { private const int CacheDebugInfoCapacity = 64; private static readonly Color SectionColor = new Color(0.18f, 0.18f, 0.18f, 1f); private static readonly Color OkColor = new Color(0.3f, 0.75f, 0.35f, 1f); private static readonly Color WarningColor = new Color(1f, 0.72f, 0.18f, 1f); private static readonly Color ErrorColor = new Color(1f, 0.32f, 0.28f, 1f); private static readonly Color MutedColor = new Color(0.62f, 0.62f, 0.62f, 1f); private readonly UIServiceDebugInfo _serviceInfo = new UIServiceDebugInfo(); private readonly UILayerDebugInfo _layerInfo = new UILayerDebugInfo(); private readonly UIWindowDebugInfo _windowInfo = new UIWindowDebugInfo(); private readonly UIWindowDebugInfo[] _cacheInfos = new UIWindowDebugInfo[CacheDebugInfoCapacity]; private SerializedProperty uiRoot; private SerializedProperty _isOrthographic; private bool _showRuntimeDebug = true; private bool _showReferences; private bool _showLayers = true; private bool _showCache = true; private bool _showEmptyLayers; private Vector2 _runtimeScroll; public override void OnInspectorGUI() { base.OnInspectorGUI(); serializedObject.Update(); EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode); { if (uiRoot.objectReferenceValue == null) { EditorGUILayout.HelpBox("uiroot can not be null!", MessageType.Error); } EditorGUILayout.BeginHorizontal(); GameObject rootPrefab = (GameObject)EditorGUILayout.ObjectField("UI Root Prefab", uiRoot.objectReferenceValue, typeof(GameObject), false); if (rootPrefab != uiRoot.objectReferenceValue) { uiRoot.objectReferenceValue = rootPrefab; } if (uiRoot.objectReferenceValue == null) { if (GUILayout.Button("Set Default")) { GameObject defaultPrefab = AssetDatabase.LoadAssetAtPath(UIGlobalPath.UIPrefabPath); uiRoot.objectReferenceValue = defaultPrefab; } } EditorGUILayout.EndHorizontal(); EditorGUILayout.PropertyField(_isOrthographic); } EditorGUI.EndDisabledGroup(); serializedObject.ApplyModifiedProperties(); DrawRuntimeDebugInfo(); if (EditorApplication.isPlaying) { Repaint(); } } private void OnEnable() { uiRoot = serializedObject.FindProperty("uiRoot"); _isOrthographic = serializedObject.FindProperty("_isOrthographic"); for (int i = 0; i < _cacheInfos.Length; i++) { _cacheInfos[i] = new UIWindowDebugInfo(); } } private void DrawRuntimeDebugInfo() { EditorGUILayout.Space(); _showRuntimeDebug = EditorGUILayout.Foldout(_showRuntimeDebug, "Runtime Debug", true, EditorStyles.foldoutHeader); if (!_showRuntimeDebug) { return; } if (!EditorApplication.isPlaying) { EditorGUILayout.HelpBox("Enter Play Mode to inspect runtime UI state.", MessageType.Info); return; } if (!AppServices.TryGet(out IUIService uiService) || uiService is not IUIDebugService debugService) { EditorGUILayout.HelpBox("UI service is not initialized.", MessageType.Warning); return; } debugService.FillServiceDebugInfo(_serviceInfo); DrawServiceSummary(); DrawRuntimeOptions(); _runtimeScroll = EditorGUILayout.BeginScrollView(_runtimeScroll, GUILayout.MinHeight(260f), GUILayout.MaxHeight(720f)); DrawReferences(); DrawLayerDebugInfo(debugService); DrawCacheDebugInfo(debugService); EditorGUILayout.EndScrollView(); } private void DrawRuntimeOptions() { EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); _showLayers = GUILayout.Toggle(_showLayers, "Layers", EditorStyles.toolbarButton); _showCache = GUILayout.Toggle(_showCache, "Cache", EditorStyles.toolbarButton); _showReferences = GUILayout.Toggle(_showReferences, "References", EditorStyles.toolbarButton); _showEmptyLayers = GUILayout.Toggle(_showEmptyLayers, "Empty Layers", EditorStyles.toolbarButton); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); } private void DrawServiceSummary() { DrawSectionBegin("Service Summary"); EditorGUILayout.BeginHorizontal(); DrawCounter("Initialized", _serviceInfo.Initialized ? "Yes" : "No", _serviceInfo.Initialized ? OkColor : ErrorColor); DrawCounter("Mode", _serviceInfo.Orthographic ? "Orthographic" : "Perspective", Color.white); DrawCounter("Layers", _serviceInfo.LayerCount.ToString(), Color.white); DrawCounter("Open", _serviceInfo.OpenWindowCount.ToString(), _serviceInfo.OpenWindowCount > 0 ? OkColor : MutedColor); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); DrawCounter("Cache", _serviceInfo.CacheWindowCount.ToString(), _serviceInfo.CacheWindowCount > 0 ? WarningColor : MutedColor); DrawCounter("Update", _serviceInfo.UpdateWindowCount.ToString(), _serviceInfo.UpdateWindowCount > 0 ? OkColor : MutedColor); DrawCounter("Block", _serviceInfo.BlockActive ? "Active" : "Inactive", _serviceInfo.BlockActive ? WarningColor : MutedColor); DrawCounter("Block Timer", _serviceInfo.BlockTimerHandle.ToString(), _serviceInfo.BlockTimerHandle != 0UL ? WarningColor : MutedColor); EditorGUILayout.EndHorizontal(); DrawSectionEnd(); } private void DrawReferences() { if (!_showReferences) { return; } DrawSectionBegin("References"); EditorGUILayout.ObjectField("Root", _serviceInfo.Root, typeof(Transform), true); EditorGUILayout.ObjectField("Canvas Root", _serviceInfo.CanvasRoot, typeof(Transform), true); EditorGUILayout.ObjectField("Canvas", _serviceInfo.Canvas, typeof(Canvas), true); EditorGUILayout.ObjectField("Camera", _serviceInfo.Camera, typeof(Camera), true); DrawSectionEnd(); } private void DrawLayerDebugInfo(IUIDebugService debugService) { if (!_showLayers) { return; } DrawSectionBegin("Open Windows"); bool hasWindow = false; for (int layerIndex = 0; layerIndex < debugService.LayerCount; layerIndex++) { if (!debugService.FillLayerDebugInfo(layerIndex, _layerInfo)) { continue; } if (_layerInfo.WindowCount == 0 && !_showEmptyLayers) { continue; } hasWindow |= _layerInfo.WindowCount > 0; DrawLayerHeader(_layerInfo); EditorGUI.indentLevel++; for (int windowIndex = 0; windowIndex < _layerInfo.WindowCount; windowIndex++) { if (debugService.FillWindowDebugInfo(layerIndex, windowIndex, _windowInfo)) { DrawWindowDebugInfo(_windowInfo, false); } } EditorGUI.indentLevel--; } if (!hasWindow && !_showEmptyLayers) { DrawEmptyLabel("No open UI windows."); } DrawSectionEnd(); } private void DrawCacheDebugInfo(IUIDebugService debugService) { if (!_showCache) { return; } DrawSectionBegin("Cached Windows"); int count = debugService.FillCacheDebugInfo(_cacheInfos, _cacheInfos.Length); if (count == 0) { DrawEmptyLabel("No cached UI windows."); } for (int i = 0; i < count; i++) { DrawWindowDebugInfo(_cacheInfos[i], true); } if (debugService.CacheWindowCount > _cacheInfos.Length) { EditorGUILayout.HelpBox("Cache list is truncated by inspector buffer capacity.", MessageType.Warning); } DrawSectionEnd(); } private static void DrawLayerHeader(UILayerDebugInfo info) { Rect rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight + 4f); EditorGUI.DrawRect(rect, SectionColor); rect.x += 6f; rect.width -= 12f; rect.y += 2f; EditorGUI.LabelField( rect, info.Layer.ToString(), "Windows " + info.WindowCount + " | Last Fullscreen " + info.LastFullscreenIndex, EditorStyles.boldLabel); } private static void DrawWindowDebugInfo(UIWindowDebugInfo info, bool cached) { Rect rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight + 4f); Color color = GetWindowColor(info, cached); EditorGUI.DrawRect(new Rect(rect.x, rect.y + 2f, 4f, rect.height - 4f), color); rect.x += 8f; rect.width -= 8f; EditorGUI.ObjectField(rect, GetWindowTitle(info, cached), info.HolderTransform, typeof(Transform), true); EditorGUI.indentLevel++; EditorGUILayout.LabelField("State", GetStateLine(info)); EditorGUILayout.LabelField("Flags", GetFlagLine(info)); if (cached || info.CacheTime > 0f || info.CacheTimerHandle != 0UL) { EditorGUILayout.LabelField("Cache", GetCacheLine(info)); } EditorGUILayout.LabelField("Holder", string.IsNullOrEmpty(info.HolderTypeName) ? "None" : info.HolderTypeName); EditorGUI.indentLevel--; } private static string GetWindowTitle(UIWindowDebugInfo info, bool cached) { string logicName = string.IsNullOrEmpty(info.LogicTypeName) ? "Unknown" : info.LogicTypeName; string prefix = cached ? "[Cache] " : ""; return prefix + "#" + info.OrderIndex + " L" + info.LayerIndex + " " + logicName; } private static string GetStateLine(UIWindowDebugInfo info) { return info.State + " | Visible " + info.Visible + " | Depth " + info.Depth + " | FullScreen " + info.FullScreen; } private static string GetFlagLine(UIWindowDebugInfo info) { return "Update " + info.NeedUpdate + " | InCache " + info.InCache + " | ShowOp " + info.ShowInProgress + " | CloseOp " + info.CloseInProgress; } private static string GetCacheLine(UIWindowDebugInfo info) { return "Time " + info.CacheTime.ToString("F2") + " | Timer " + info.CacheTimerHandle; } private static Color GetWindowColor(UIWindowDebugInfo info, bool cached) { if (info.ShowInProgress || info.CloseInProgress) { return WarningColor; } if (cached || info.InCache) { return MutedColor; } if (!info.Visible) { return ErrorColor; } return info.FullScreen ? OkColor : Color.cyan; } private static void DrawCounter(string label, string value, Color valueColor) { EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.MinWidth(82f)); EditorGUILayout.LabelField(label, EditorStyles.miniLabel); Color oldColor = GUI.color; GUI.color = valueColor; EditorGUILayout.LabelField(value, EditorStyles.boldLabel); GUI.color = oldColor; EditorGUILayout.EndVertical(); } private static void DrawSectionBegin(string title) { EditorGUILayout.Space(4f); Rect rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight + 6f); EditorGUI.DrawRect(rect, SectionColor); rect.x += 6f; rect.y += 3f; rect.width -= 12f; EditorGUI.LabelField(rect, title, EditorStyles.boldLabel); EditorGUILayout.BeginVertical(GUI.skin.box); } private static void DrawSectionEnd() { EditorGUILayout.EndVertical(); } private static void DrawEmptyLabel(string text) { Color oldColor = GUI.color; GUI.color = MutedColor; EditorGUILayout.LabelField(text, EditorStyles.miniLabel); GUI.color = oldColor; } } }