#if UNITY_EDITOR using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; namespace TyphoonEvent { [CustomEditor(typeof(EventKitImpl))] internal class EventKitImplEditor : Editor { private static Color LightSelctColor = new Color(0.19f, 0.4f, 0.7f, 1f); private static Color DarkSelctColor = new Color(0.19f, 0.3f, 0.45f, 1f); private static Color[] Colors = new[] { new Color(1.0f, 0.0f, 0.0f, 1.0f), // 红色 new Color(0.0f, 1.0f, 0.0f, 1.0f), // 绿色 new Color(0.0f, 0.0f, 1.0f, 1.0f), // 蓝色 new Color(1.0f, 1.0f, 0.0f, 1.0f), // 黄色 new Color(1.0f, 0.0f, 1.0f, 1.0f), // 洋红 new Color(0.0f, 1.0f, 1.0f, 1.0f), // 青色 new Color(1.0f, 0.5f, 0.0f, 1.0f), // 橙色 new Color(0.5f, 1.0f, 0.0f, 1.0f), // 浅绿 new Color(0.0f, 0.5f, 1.0f, 1.0f), // 浅蓝 new Color(1.0f, 0.0f, 0.5f, 1.0f), // 粉红 new Color(0.5f, 0.0f, 1.0f, 1.0f), // 紫色 new Color(1.0f, 0.5f, 0.5f, 1.0f), // 浅红 new Color(0.5f, 1.0f, 0.5f, 1.0f), // 浅青 new Color(0.5f, 0.5f, 1.0f, 1.0f), // 浅紫 new Color(0.75f, 0.25f, 0.0f, 1.0f), // 深橙 new Color(0.75f, 0.0f, 0.25f, 1.0f), // 深粉 new Color(0.25f, 0.0f, 0.75f, 1.0f), // 深紫 new Color(0.0f, 0.25f, 0.75f, 1.0f), // 深蓝 new Color(0.25f, 0.75f, 0.0f, 1.0f), // 深绿 new Color(0.0f, 0.75f, 0.25f, 1.0f), // 深青 new Color(0.75f, 0.75f, 0.0f, 1.0f), // 橄榄 new Color(0.75f, 0.0f, 0.75f, 1.0f), // 灰粉 new Color(0.0f, 0.75f, 0.75f, 1.0f), // 蓝绿 new Color(0.75f, 0.25f, 0.25f, 1.0f), // 深玫瑰 new Color(0.25f, 0.75f, 0.25f, 1.0f), // 深碧绿 new Color(0.25f, 0.25f, 0.75f, 1.0f), // 深湖蓝 }; public const float OPTION_BAR_HEIGHT = 22; public const float TABLE_HEADER_HEIGHT = 16; private const float CONTENT_HEIGHT = 600; private const float ELEMENT_HEIGHT = 18; private const float COLLECTION_NUMBER_WIDTH = 90; private const float COLLECTION_TYPE_WIDTH = 180; private const float COLLECTION_HANDLER_TARGET_WIDTH = 180; private const float COLLECTION_SPACE = 10; private const float OPERATION_ID_WIDTH = 90; private const float OPERATION_COUNT_WIDTH = 140; private const float OPERATION_TYPE_WIDTH = 120; private const float OPERATION_HANDLER_TARGET_WIDTH = 140; private static string[] ViewOptions = new[] { "总览", "仅 Operation" }; private static int OptionIndex = 0; private static Color SelectColor => EditorGUIUtility.isProSkin ? DarkSelctColor : LightSelctColor; private class State { public bool Foldout { get; set; } = false; public Rect RendererRect { get; set; } public Color ThemeColor { get; set; } } private Dictionary _collectionStates = new Dictionary(); private Dictionary _operationStates = new Dictionary(); private EventKitImpl Target => target as EventKitImpl; private Dictionary Collections => Target.Collections; private Dictionary Groups => Target.Groups; private GUIStyle _leftLabelStyle; private StringBuilder StringBuilder { get; set; } = new StringBuilder(); private GUIStyle LeftLabelStyle { get { if (_leftLabelStyle == null) { _leftLabelStyle = new GUIStyle(GUI.skin.label); _leftLabelStyle.alignment = TextAnchor.MiddleLeft; _leftLabelStyle.margin = new RectOffset(0, 0, 0, 0); _leftLabelStyle.padding = new RectOffset(4, 2, 2, 2); _leftLabelStyle.border = new RectOffset(0, 0, 0, 0); _leftLabelStyle.fontSize = 11; } return _leftLabelStyle; } } private Rect _optionBarRect; private Rect _contentRect; private Rect _tableHeaderRect; private Vector2 Scroll { get; set; } private Vector2 CollectionGUIRendererRange { get; set; } private Vector2 ScrollOperation { get; set; } private Vector2 OperationGUIRendererRange { get; set; } public override void OnInspectorGUI() { base.OnInspectorGUI(); DrawGUI(OPTION_BAR_HEIGHT, TABLE_HEADER_HEIGHT, CONTENT_HEIGHT); Repaint(); } public void DrawGUI(float optionHeight, float tableHeaderHeight, float contentHeight) { GUILayout.Label("", GUILayout.Height(optionHeight)); RecordLastRect(ref _optionBarRect); GUILayout.Label("", GUILayout.Height(tableHeaderHeight)); RecordLastRect(ref _tableHeaderRect); GUILayout.Label("", GUILayout.Height(contentHeight)); RecordLastRect(ref _contentRect); switch (OptionIndex) { case 0: { DrawOptionBarGUI(_optionBarRect); DrawCollectionHeaderGUI(_tableHeaderRect); DrawCollectionGUI(_contentRect); } break; case 1: { DrawOptionBarGUI(_optionBarRect); DrawOperationHeader(_tableHeaderRect); DrawOperationGUI(_contentRect); } break; } } private void DrawOptionBarGUI(Rect rect) { var maxItemWidth = 120f; GUILayout.BeginArea(rect); var width = ViewOptions.Length * maxItemWidth; if (width > rect.width) { width = rect.width; } var contentRect = new Rect(Vector2.zero, rect.size); var drawRect = contentRect; drawRect.width = width; OptionIndex = GUI.SelectionGrid(drawRect, OptionIndex, ViewOptions, ViewOptions.Length); OptionIndex = Mathf.Clamp(OptionIndex, 0, ViewOptions.Length - 1); GUILayout.EndArea(); } #region Proxy Collection GUI private void DrawCollectionHeaderGUI(Rect rect) { var drawRect = new Rect(Vector2.zero, rect.size); GUILayout.BeginArea(rect); SplitRects(drawRect, COLLECTION_NUMBER_WIDTH, COLLECTION_TYPE_WIDTH, out var r1, out var r2, out var r3); var b1 = r1; b1.width = 1; var b2 = r2; b2.width = 1; var b3 = r3; b3.width = 1; EditorGUI.DrawRect(b1, Color.black * 0.5f); EditorGUI.DrawRect(b2, Color.black * 0.5f); EditorGUI.DrawRect(b3, Color.black * 0.5f); DrawLeftLabel(r1, "序号"); DrawLeftLabel(r2, "消息类型"); DrawLeftLabel(r3, "订阅数量"); GUILayout.EndArea(); } private State CreateOrGetCollectionState(Type type) { if (_collectionStates.TryGetValue(type, out var match)) { return match; } var hash = Mathf.Abs(RandomHashInt(type.Name)); var index = hash % Colors.Length; var col = Colors[index]; col = new Color(col.r, col.g, col.b, 0.3f); var state = new State() { ThemeColor = col, }; _collectionStates.Add(type, state); return state; } private void DrawCollectionGUI(Rect area) { var rect = new Rect(Vector2.zero, area.size); GUILayout.BeginArea(area); var viewRect = new Rect(0, 0, rect.width - 16, 0); var height = 0f; foreach (var pair in Collections) { var startY = height; height += ELEMENT_HEIGHT; var type = pair.Key; var collection = pair.Value; var state = CreateOrGetCollectionState(type); if (state.Foldout) { height += ELEMENT_HEIGHT; height += collection.Proxies.Count * ELEMENT_HEIGHT; height += COLLECTION_SPACE; } state.RendererRect = new Rect(0, startY, viewRect.width, height - startY); } viewRect.height = height; //计算渲染范围 var rendererStart = Scroll.y - 60; var rendererEnd = rendererStart + rect.height + 120; CollectionGUIRendererRange = new Vector2(rendererStart, rendererEnd); //绘制 Scroll = GUI.BeginScrollView(rect, Scroll, viewRect); var collectionIndex = 0; foreach (var pair in Collections) { var number = collectionIndex + 1; var type = pair.Key; var collection = pair.Value; var state = CreateOrGetCollectionState(type); var foldout = state.Foldout; // Debug.Log($"{type}-{state.CollectionGUIRendererRect}"); if (!IgnoreRect(state.RendererRect, CollectionGUIRendererRange)) { //绘制标签 var titleRect = new Rect(state.RendererRect); titleRect.height = ELEMENT_HEIGHT; DrawCollectionInfoGUI(titleRect, state.ThemeColor, number, type, collection, state); if (foldout) { var headerRect = titleRect; headerRect.y += ELEMENT_HEIGHT; DrawCollectionDetailHeader(headerRect, state.ThemeColor); var proxies = collection.Proxies; var proxyIndex = 0; foreach (var proxy in proxies) { var proxyItemRect = titleRect; proxyItemRect.y += (proxyIndex + 2) * ELEMENT_HEIGHT; DrawCollectionDetailGUI(proxyItemRect, state.ThemeColor, number, proxyIndex + 1, proxy); proxyIndex += 1; } } } collectionIndex += 1; } GUI.EndScrollView(); GUILayout.EndArea(); } private void DrawCollectionInfoGUI(Rect rect, Color themeColor, int number, Type type, ProxyCollection collection, State state) { if (IgnoreRect(rect, CollectionGUIRendererRange)) { return; } SplitRects(rect, COLLECTION_NUMBER_WIDTH, COLLECTION_TYPE_WIDTH, out var r1, out var r2, out var r3); if (number % 2 != 0) { EditorGUI.DrawRect(rect, Color.black * 0.05f); } var foldoutRect = r3; foldoutRect.width = 60; foldoutRect.x = r3.xMax - foldoutRect.width; DrawLeftLabel(r1, number.ToString()); DrawLeftLabel(r2, $"{type.Name}"); DrawLeftLabel(r3, $"{collection.Proxies.Count}"); state.Foldout = EditorGUI.Foldout(foldoutRect, state.Foldout, "详情"); var markRect = rect; markRect.width = 2; EditorGUI.DrawRect(markRect, themeColor); if (Event.current != null && rect.Contains(Event.current.mousePosition)) { EditorGUI.DrawRect(rect, SelectColor * 0.5f); } if (Event.current != null && Event.current.type == EventType.MouseDown && Event.current.button == 0 && rect.Contains(Event.current.mousePosition)) { state.Foldout = !state.Foldout; Event.current.Use(); } } private void DrawCollectionDetailHeader(Rect rect, Color themeColor) { SplitRects(rect, 8, COLLECTION_NUMBER_WIDTH - 8, COLLECTION_HANDLER_TARGET_WIDTH, out var r1, out var r2, out var r3, out var r4); var line = rect; line.width = 2; line.x += 2; EditorGUI.DrawRect(line, themeColor); DrawLeftLabel(r2, $"索引"); DrawLeftLabel(r3, $"目标"); DrawLeftLabel(r4, $"方法"); } private void DrawCollectionDetailGUI(Rect rect, Color themeColor, int collectionNumber, int number, IEventProxy proxy) { var eventProxy = proxy as EventProxy; SplitRects(rect, 8, COLLECTION_NUMBER_WIDTH - 8, COLLECTION_HANDLER_TARGET_WIDTH, out var r1, out var r2, out var r3, out var r4); if (eventProxy != null) { var line = rect; line.width = 2; line.x += 2; EditorGUI.DrawRect(line, themeColor); DrawLeftLabel(r2, $"{collectionNumber}-{number}"); if (eventProxy.Handler != null) { var handlerTarget = eventProxy.Handler.Target; var methodName = eventProxy.Handler.Method.Name; var component = handlerTarget as Object; if (component != null) { EditorGUI.ObjectField(r3, component, typeof(Object)); } else { DrawLeftLabel(r3, $"{handlerTarget}"); } DrawLeftLabel(r4, methodName); } else { DrawLeftLabel(r3, $"null"); DrawLeftLabel(r4, $"---"); } var enableMark = r4; enableMark.width = 16; enableMark.x = r4.xMax - enableMark.width; var center = enableMark.center; enableMark.width = 8; enableMark.height = 8; enableMark.center = center; EditorGUI.DrawRect(enableMark, proxy.IsEnable ? Color.green : Color.red); } else { DrawLeftLabel(r2, number.ToString()); DrawLeftLabel(r3, $"{proxy}"); } if (Event.current != null && rect.Contains(Event.current.mousePosition)) { var tipLine = rect; tipLine.height = 1; var tipLine2 = tipLine; tipLine2.y = rect.yMax - 1; EditorGUI.DrawRect(tipLine, SelectColor); EditorGUI.DrawRect(tipLine2, SelectColor); } } #endregion #region Event Operation GUI private void DrawOperationHeader(Rect rect) { var drawRect = new Rect(Vector2.zero, rect.size); GUILayout.BeginArea(rect); SplitRects(drawRect, OPERATION_ID_WIDTH, OPERATION_COUNT_WIDTH, out var r1, out var r2, out var r3); var b1 = r1; b1.width = 1; var b2 = r2; b2.width = 1; var b3 = r3; b3.width = 1; EditorGUI.DrawRect(b1, Color.black * 0.5f); EditorGUI.DrawRect(b2, Color.black * 0.5f); EditorGUI.DrawRect(b3, Color.black * 0.5f); DrawLeftLabel(r1, "ID"); DrawLeftLabel(r2, "订阅数"); DrawLeftLabel(r3, "消息类型"); GUILayout.EndArea(); } private void DrawOperationGUI(Rect area) { var groups = Groups; var rect = new Rect(Vector2.zero, area.size); GUILayout.BeginArea(area); var viewRect = new Rect(0, 0, rect.width - 16, 0); var height = 0f; foreach (var pair in groups) { var startY = height; height += ELEMENT_HEIGHT; var id = pair.Key; var group = pair.Value; var state = CreateOrGetOperationState(id); if (state.Foldout) { height += ELEMENT_HEIGHT; height += group.Proxies.Length * ELEMENT_HEIGHT; height += COLLECTION_SPACE; } state.RendererRect = new Rect(0, startY, viewRect.width, height - startY); } viewRect.height = height; //计算渲染范围 var rendererStart = Scroll.y - 60; var rendererEnd = rendererStart + rect.height + 120; OperationGUIRendererRange = new Vector2(rendererStart, rendererEnd); ScrollOperation = GUI.BeginScrollView(rect, ScrollOperation, viewRect); var operationIndex = 0; foreach (var pair in groups) { var number = operationIndex + 1; var id = pair.Key; var group = pair.Value; var state = CreateOrGetOperationState(id); var foldout = state.Foldout; if (!IgnoreRect(state.RendererRect, OperationGUIRendererRange)) { //绘制标签 var titleRect = new Rect(state.RendererRect); titleRect.height = ELEMENT_HEIGHT; DrawOperationInfoGUI(titleRect, state.ThemeColor, number, id, group, state); if (foldout) { var headerRect = titleRect; headerRect.y += ELEMENT_HEIGHT; //绘制子表头 DrawOperationDetailHeader(headerRect, state.ThemeColor); var proxies = group.Proxies; var proxyIndex = 0; foreach (var proxy in proxies) { var proxyItemRect = titleRect; proxyItemRect.y += (proxyIndex + 2) * ELEMENT_HEIGHT; DrawOperationProxyDetailGUI(proxyItemRect, state.ThemeColor, id, proxyIndex + 1, proxy); proxyIndex += 1; } } } operationIndex += 1; } GUI.EndScrollView(); GUILayout.EndArea(); } private void DrawOperationDetailHeader(Rect rect, Color themeColor) { SplitRects(rect, 8, OPERATION_ID_WIDTH - 8, OPERATION_TYPE_WIDTH, OPERATION_HANDLER_TARGET_WIDTH, out var r1, out var r2, out var r3, out var r4, out var r5); DrawLeftLabel(r2, "序号"); DrawLeftLabel(r3, "消息类型"); DrawLeftLabel(r4, "目标"); DrawLeftLabel(r5, "方法"); var mark = r1; mark.width = 2; mark.x += 2; EditorGUI.DrawRect(mark, themeColor); } private void DrawOperationProxyDetailGUI(Rect rect, Color themeColor, uint operationID, int proxyNumber, IEventProxy proxy) { var eventProxy = proxy as EventProxy; //序号,类型,对象,method,开启/关闭 SplitRects(rect, 8, OPERATION_ID_WIDTH - 8, OPERATION_TYPE_WIDTH, OPERATION_HANDLER_TARGET_WIDTH, out var r1, out var r2, out var r3, out var r4, out var r5); if (eventProxy != null) { var line = rect; line.width = 2; line.x += 2; EditorGUI.DrawRect(line, themeColor); DrawLeftLabel(r2, $"{operationID}-{proxyNumber}"); DrawLeftLabel(r3, eventProxy.MessageType.Name); if (eventProxy.Handler != null) { var handlerTarget = eventProxy.Handler.Target; var methodName = eventProxy.Handler.Method.Name; var component = handlerTarget as Object; if (component != null) { EditorGUI.ObjectField(r4, component, typeof(Object)); } else { DrawLeftLabel(r4, $"{handlerTarget}"); } DrawLeftLabel(r5, methodName); } else { DrawLeftLabel(r4, $"null"); DrawLeftLabel(r5, $"---"); } var enableMark = r5; enableMark.width = 16; enableMark.x = r5.xMax - enableMark.width; var center = enableMark.center; enableMark.width = 8; enableMark.height = 8; enableMark.center = center; EditorGUI.DrawRect(enableMark, proxy.IsEnable ? Color.green : Color.red); } else { DrawLeftLabel(r2, $"{operationID}-{proxyNumber}"); DrawLeftLabel(r3, $"{proxy}"); } if (Event.current != null && rect.Contains(Event.current.mousePosition)) { var tipLine = rect; tipLine.height = 1; var tipLine2 = tipLine; tipLine2.y = rect.yMax - 1; EditorGUI.DrawRect(tipLine, SelectColor); EditorGUI.DrawRect(tipLine2, SelectColor); } } private void DrawOperationInfoGUI(Rect titleRect, Color themeColor, int number, uint id, ProxyGroup group, State state) { //序号,ID,数量,message类型 if (IgnoreRect(titleRect, OperationGUIRendererRange)) { return; } SplitRects(titleRect, OPERATION_ID_WIDTH, OPERATION_COUNT_WIDTH, out var r1, out var r2, out var r3); if (number % 2 != 0) { EditorGUI.DrawRect(titleRect, Color.black * 0.05f); } StringBuilder.Clear(); if (group != null) { var index = 0; foreach (var proxy in group.Proxies) { if (index > 0) { StringBuilder.Append(","); } StringBuilder.Append(proxy?.MessageType.Name ?? string.Empty); index += 1; } } DrawLeftLabel(r1, $"{id}"); DrawLeftLabel(r2, $"{group.Proxies.Length}"); DrawLeftLabel(r3, $"{StringBuilder}"); var markRect = titleRect; markRect.width = 2; EditorGUI.DrawRect(markRect, themeColor); if (Event.current != null && titleRect.Contains(Event.current.mousePosition)) { EditorGUI.DrawRect(titleRect, SelectColor * 0.5f); } if (Event.current != null && Event.current.type == EventType.MouseDown && Event.current.button == 0 && titleRect.Contains(Event.current.mousePosition)) { state.Foldout = !state.Foldout; Event.current.Use(); } } private State CreateOrGetOperationState(uint id) { if (_operationStates.TryGetValue(id, out var match)) { return match; } var index = id % Colors.Length; var col = Colors[index]; col = new Color(col.r, col.g, col.b, 0.3f); var state = new State() { ThemeColor = col, }; _operationStates.Add(id, state); return state; } #endregion #region 通用 private int RandomHashInt(string input) { using (SHA256 sha256 = SHA256.Create()) { byte[] inputBytes = Encoding.UTF8.GetBytes(input); byte[] hashBytes = sha256.ComputeHash(inputBytes); int randomValue = BitConverter.ToInt32(hashBytes, 0); return randomValue; } } private bool IgnoreRect(Rect check, Vector2 range) { return check.yMax < range.x || check.yMin > range.y; } private void DrawLeftLabel(Rect rect, string label) { GUI.Label(rect, label, LeftLabelStyle); } private void SplitRects(Rect input, float w1, float w2, out Rect r1, out Rect r2, out Rect r3) { r1 = input; r1.width = w1; r2 = r1; r2.x += r2.width; r2.width = w2; r3 = r2; r3.x += r3.width; r3.width = input.xMax - r3.x; } private void SplitRects(Rect input, float w1, float w2, float w3, out Rect r1, out Rect r2, out Rect r3, out Rect r4) { r1 = input; r1.width = w1; r2 = r1; r2.x += r2.width; r2.width = w2; r3 = r2; r3.x += r3.width; r3.width = w3; r4 = r3; r4.x += r4.width; r4.width = input.xMax - r4.x; } private void SplitRects(Rect input, float w1, float w2, float w3, float w4, out Rect r1, out Rect r2, out Rect r3, out Rect r4, out Rect r5) { r1 = input; r1.width = w1; r2 = r1; r2.x += r2.width; r2.width = w2; r3 = r2; r3.x += r3.width; r3.width = w3; r4 = r3; r4.x += r4.width; r4.width = w4; r5 = r4; r5.x += r5.width; r5.width = input.xMax - r5.x; } private void RecordLastRect(ref Rect input) { if (Event.current.type == EventType.Repaint) { var rect = GUILayoutUtility.GetLastRect(); input = rect; } } #endregion } } #endif