AlicizaX/Client/Packages/com.alicizax.uxtool/Editor/UXGUI/SceneView/Resolution/ResolutionController.cs
陈思海 5862fb2af1 add
1.新增手柄相关适配
2.新增设备重映射绑定
3.新增设备映射图标更新
2025-12-09 20:31:44 +08:00

518 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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<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");
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);
}
}
/// <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