AlicizaX/Client/Packages/com.alicizax.uxtool/Editor/UXGUI/SceneView/Resolution/ResolutionController.cs

518 lines
19 KiB
C#
Raw Normal View History

2025-12-01 16:46:28 +08:00
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace AlicizaX.UXTool
2025-12-01 16:46:28 +08:00
{
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<int, VisualElement> roots = new Dictionary<int, VisualElement>();
private static readonly Dictionary<int, IMGUIContainer> containers = new Dictionary<int, IMGUIContainer>();
private static readonly HashSet<int> guiSubscribedIds = new HashSet<int>();
// 反射相关
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;
}
// 不再在此创建单一 RootRoot 在 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");
2025-12-01 16:46:28 +08:00
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");
2025-12-01 16:46:28 +08:00
if (editorGUILayoutType != null)
{
gameViewSizePopupMethod = editorGUILayoutType.GetMethod("GameViewSizePopup", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
}
var gameViewType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GameView");
2025-12-01 16:46:28 +08:00
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);
}
}
/// <summary>
/// 为指定的 SceneView 创建并添加 Root + IMGUIContainer如果尚未添加
/// 使用 sceneView.GetInstanceID() 作为 key避免重复创建/添加。
/// </summary>
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>("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<int>(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<int>(containers.Keys))
{
try
{
var gui = containers[id];
if (gui != null)
{
try
{
gui.onGUIHandler = null;
}
catch { }
}
}
catch { }
}
foreach (var kv in new List<int>(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