#if UNITY_EDITOR using System; using System.Collections.Generic; using System.Reflection; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; namespace AlicizaX.UXTool { public static class ResolutionController { private const string EditorPrefsKey = "ResolutionViewInitialized"; private static bool hasInitialized { get { return EditorPrefs.GetBool(EditorPrefsKey, false); } set { EditorPrefs.SetBool(EditorPrefsKey, value); } } // per-SceneView UI instances (避免重复添加同一 VisualElement 到多个父节点) private static readonly Dictionary roots = new Dictionary(); private static readonly Dictionary containers = new Dictionary(); private static readonly HashSet guiSubscribedIds = new HashSet(); // 反射相关 private static MethodInfo gameViewSizePopupMethod; private static PropertyInfo selectedSizeIndexProperty; private static object gameViewInstance; private static int selectedSizeIndex = -1; private static object currentSizeGroupTypeObj; // 事件订阅标志 private static bool subscribedToEditorLoadEvents = false; private static bool subscribedToUpdate = false; // 布局参数(需要时可调整) private const float GuiWidth = 160f; private const float RootWidth = 180f; private const float RootHeight = 22f; private const float OffsetTop = -22f; private const float OffsetRight = -20f; [InitializeOnLoadMethod] public static void Initialize() { // 在初始化时恢复(确保干净状态),并注册自己的事件 RestoreOriginalToolbar(); UXDesinUtil.OnEnterDesignMode += RegisterEvents; UXDesinUtil.OnExitDesignMode += UnRegisterEvents; if (UXDesinUtil.InDesign) { RegisterEvents(); } else { UnRegisterEvents(); } } private static void RegisterEvents() { if (subscribedToEditorLoadEvents) return; EditorApplication.delayCall += OnEditorLoadDelay; AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload; AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; subscribedToEditorLoadEvents = true; } private static void UnRegisterEvents() { if (!subscribedToEditorLoadEvents) return; EditorApplication.delayCall -= OnEditorLoadDelay; AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; subscribedToEditorLoadEvents = false; } private static void OnEditorLoadDelay() { // EnsureInitialized 是幂等的 if (!hasInitialized) { EnsureInitialized(); } // 仅在设计模式下启动 per-frame 更新 if (UXDesinUtil.InDesign) { UpdateSceneViewUI(); // 立即尝试添加 if (!subscribedToUpdate) { EditorApplication.update += UpdateSceneViewUI; subscribedToUpdate = true; } } else { // 不在设计模式则移除所有 UI Cleanup(); } } private static void OnBeforeAssemblyReload() { Cleanup(); } private static void OnAfterAssemblyReload() { // 延迟调用以等待编辑器恢复 EditorApplication.delayCall += OnEditorLoadDelay; } private static void EnsureInitialized() { if (hasInitialized) return; bool ok = InitWindowData(); if (!ok) { hasInitialized = false; return; } // 不再在此创建单一 Root;Root 在 AddRootToSceneView 时为每个 SceneView 创建 hasInitialized = true; } private static bool InitWindowData() { try { // 尝试通过你项目已有的 Utils 获取主 GameView try { // 如果项目提供了 Utils.GetMainPlayModeView(),保留调用(你原代码中提到) var utilsType = Type.GetType("Utils"); if (utilsType != null) { var method = utilsType.GetMethod("GetMainPlayModeView", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); if (method != null) { gameViewInstance = method.Invoke(null, null); } } } catch { // 忽略 utils 获取失败,继续使用反射查找 } // 如果为空,尝试从类型查找已存在的 GameView 实例 if (gameViewInstance == null) { var gvType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GameView"); if (gvType != null) { var arr = Resources.FindObjectsOfTypeAll(gvType); if (arr != null && arr.Length > 0) gameViewInstance = arr[0]; } } if (gameViewInstance == null) return false; var editorGUILayoutType = Type.GetType("UnityEditor.EditorGUILayout,UnityEditor") ?? typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.EditorGUILayout"); if (editorGUILayoutType != null) { gameViewSizePopupMethod = editorGUILayoutType.GetMethod("GameViewSizePopup", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); } var gameViewType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GameView"); if (gameViewType == null) return false; selectedSizeIndexProperty = gameViewType.GetProperty("selectedSizeIndex", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (selectedSizeIndexProperty == null) return false; var currentSizeGroupProp = gameViewType.GetProperty("currentSizeGroupType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); if (currentSizeGroupProp != null) { var getMethod = currentSizeGroupProp.GetGetMethod(nonPublic: true); if (getMethod != null) { if (getMethod.IsStatic) currentSizeGroupTypeObj = currentSizeGroupProp.GetValue(null); else currentSizeGroupTypeObj = currentSizeGroupProp.GetValue(gameViewInstance); } } // 初始索引读取 selectedSizeIndex = (int)selectedSizeIndexProperty.GetValue(gameViewInstance); return true; } catch (Exception e) { Debug.LogWarning("[ResolutionController] InitWindowData failed: " + e); return false; } } private static void UpdateSceneViewUI() { try { // 仅当 UXDesinUtil 在设计模式时显示 if (!UXDesinUtil.InDesign) { if (hasInitialized) RestoreOriginalToolbar(); return; } EnsureInitialized(); if (!hasInitialized) return; // 为所有可用 SceneView 保证 UI(多 SceneView 情况下,每个 SceneView 一个实例,避免重复添加同一个 VE) foreach (SceneView sceneView in SceneView.sceneViews) { if (sceneView == null) continue; AddRootToSceneView(sceneView); } // 触发 repaint var last = SceneView.lastActiveSceneView; if (last != null) last.Repaint(); } catch (Exception e) { Debug.LogWarning("[ResolutionController] UpdateSceneViewUI exception: " + e); } } /// /// 为指定的 SceneView 创建并添加 Root + IMGUIContainer(如果尚未添加)。 /// 使用 sceneView.GetInstanceID() 作为 key,避免重复创建/添加。 /// private static void AddRootToSceneView(SceneView sceneView) { if (sceneView == null) return; int id = sceneView.GetInstanceID(); try { var rootVisual = sceneView.rootVisualElement; if (rootVisual == null) return; // 如果已经为该 SceneView 创建过 root,则确保它还在该 SceneView 下 if (roots.ContainsKey(id)) { var existingRoot = roots[id]; if (existingRoot == null || existingRoot.parent == null) { // 如果被移除则重加 if (existingRoot != null) { rootVisual.Add(existingRoot); } else { // 如果被 GC 或置 null,则删掉记录并重新创建 roots.Remove(id); if (containers.ContainsKey(id)) containers.Remove(id); } } return; } // 若 rootVisual 中已有同名元素(来自旧版本或其他插件残留),不要重复添加 const string rootName = "ResolutionController_Root"; var found = rootVisual.Q(rootName); if (found != null) { // 已经存在一个元素(可能来自之前的装载),把它当作该 SceneView 的 root roots[id] = found; // 如果需要,确保里面有 IMGUIContainer(若无,创建一个) var gui = found.Q("IMGUIContainer_ResolutionController"); if (gui == null) { var newGui = CreateAndAttachIMGUI(found, id); containers[id] = newGui; SubscribeGuiHandlerIfNeeded(id, newGui); } return; } // 创建新的 root + imGUIContainer 实例并加入到 sceneView.rootVisualElement var newRoot = new VisualElement(); newRoot.name = rootName; newRoot.style.position = Position.Absolute; newRoot.style.top = OffsetTop; newRoot.style.right = OffsetRight; newRoot.style.width = RootWidth; newRoot.style.height = RootHeight; newRoot.style.overflow = Overflow.Visible; var newGuiContainer = new IMGUIContainer(); newGuiContainer.name = "IMGUIContainer_ResolutionController"; newGuiContainer.style.width = RootWidth; newGuiContainer.style.height = RootHeight; newRoot.Add(newGuiContainer); rootVisual.Add(newRoot); roots[id] = newRoot; containers[id] = newGuiContainer; SubscribeGuiHandlerIfNeeded(id, newGuiContainer); } catch (Exception e) { Debug.LogWarning("[ResolutionController] AddRootToSceneView failed: " + e); } } private static IMGUIContainer CreateAndAttachIMGUI(VisualElement root, int id) { var gui = new IMGUIContainer(); gui.name = "IMGUIContainer_ResolutionController"; gui.style.width = RootWidth; gui.style.height = RootHeight; root.Add(gui); return gui; } private static void SubscribeGuiHandlerIfNeeded(int id, IMGUIContainer gui) { if (gui == null) return; if (guiSubscribedIds.Contains(id)) return; gui.onGUIHandler += () => OnGUIHandlerForId(id); guiSubscribedIds.Add(id); } private static void UnsubscribeGuiHandlerIfNeeded(int id) { if (!guiSubscribedIds.Contains(id)) return; if (containers.TryGetValue(id, out var gui) && gui != null) { // 无法直接移除匿名 lambda,因此改为移除所有并重新创建时控制(但为保险尝试移除单个引用) try { gui.onGUIHandler -= () => OnGUIHandlerForId(id); } catch { } } guiSubscribedIds.Remove(id); } private static void OnGUIHandlerForId(int id) { try { GUILayoutOption[] ops = new GUILayoutOption[] { GUILayout.Width(GuiWidth) }; if (gameViewSizePopupMethod != null) { try { // 兼容不同签名 gameViewSizePopupMethod.Invoke(null, new object[] { currentSizeGroupTypeObj, selectedSizeIndex, gameViewInstance, EditorStyles.toolbarPopup, ops }); } catch (TargetParameterCountException) { try { gameViewSizePopupMethod.Invoke(null, new object[] { selectedSizeIndex, gameViewInstance, EditorStyles.toolbarPopup, ops }); } catch { // 忽略 } } catch (Exception) { // 忽略单次调用错误 } } OnGUI(); } catch (Exception e) { Debug.LogWarning("[ResolutionController] OnGUIHandler exception: " + e); } } private static void OnGUI() { try { if (gameViewInstance == null || selectedSizeIndexProperty == null) return; int nowIndex = (int)selectedSizeIndexProperty.GetValue(gameViewInstance); if (nowIndex != selectedSizeIndex) { selectedSizeIndex = nowIndex; // 如果需要可在此触发回调或通知其他系统 } } catch (Exception e) { Debug.LogWarning("[ResolutionController] OnGUI exception: " + e); } } private static void RestoreOriginalToolbar() { try { // 取消每个 sceneView 的 gui handler 并移除 roots foreach (var kv in new List(roots.Keys)) { int id = kv; if (containers.TryGetValue(id, out var gui) && gui != null) { try { // 尝试移除我们的 handler(如果用了 lambda,直接移除可能无效,但我们会移除整个元素) gui.onGUIHandler = null; } catch { } } if (roots.TryGetValue(id, out var root) && root != null) { try { if (root.parent != null) root.parent.Remove(root); } catch { } } } } catch (Exception e) { Debug.LogWarning("[ResolutionController] RestoreOriginalToolbar failed: " + e); } finally { // 保持状态清理 hasInitialized = false; guiSubscribedIds.Clear(); containers.Clear(); roots.Clear(); } } private static void Cleanup() { try { if (subscribedToUpdate) { EditorApplication.update -= UpdateSceneViewUI; subscribedToUpdate = false; } // 移除所有 gui handlers 并删除 UI foreach (var id in new List(containers.Keys)) { try { var gui = containers[id]; if (gui != null) { try { gui.onGUIHandler = null; } catch { } } } catch { } } foreach (var kv in new List(roots.Keys)) { var root = roots[kv]; try { if (root != null && root.parent != null) { root.parent.Remove(root); } } catch { } } guiSubscribedIds.Clear(); containers.Clear(); roots.Clear(); gameViewSizePopupMethod = null; selectedSizeIndexProperty = null; gameViewInstance = null; currentSizeGroupTypeObj = null; hasInitialized = false; } catch (Exception e) { Debug.LogWarning("[ResolutionController] Cleanup exception: " + e); } } } } #endif