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

434 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Reflection;
using AlicizaX.UXTool;
using UnityEditor;
using UnityEditor.Experimental.SceneManagement;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.Callbacks;
using AlicizaX.UI;
using AlicizaX.UI.Runtime;
public static class UXControllerSceneOverlayManager
{
private static readonly Dictionary<int, UXControllerOverlayVE> s_map = new Dictionary<int, UXControllerOverlayVE>();
[InitializeOnLoadMethod]
public static void Initialize()
{
UXDesinUtil.OnEnterDesignMode += RegisterEvents;
UXDesinUtil.OnExitDesignMode += UnRegisterEvents;
if (UXDesinUtil.InDesign)
{
RegisterEvents();
}
else
{
UnRegisterEvents();
}
}
private static void RegisterEvents()
{
SceneView.duringSceneGui += OnSceneGui;
EditorApplication.update += EditorUpdate;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
private static void UnRegisterEvents()
{
SceneView.duringSceneGui -= OnSceneGui;
EditorApplication.update -= EditorUpdate;
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
RemoveAllOverlays();
}
private static void RemoveAllOverlays()
{
var views = SceneView.sceneViews;
foreach (var obj in views)
{
var sv = obj as SceneView;
if (sv == null) continue;
try
{
var root = sv.rootVisualElement;
if (root == null) continue;
var existing = root.Q<VisualElement>("ux-controller-ve");
if (existing != null)
{
root.Remove(existing);
}
}
catch (Exception)
{
}
}
// 清理字典
s_map.Clear();
}
private static void OnPlayModeStateChanged(PlayModeStateChange state)
{
SceneView.RepaintAll();
}
private static void OnSceneGui(SceneView sv)
{
if (sv == null) return;
if (PrefabStageUtils.InEmptyStage) return;
// ensure overlay exists for this SceneView
var root = sv.rootVisualElement;
if (root == null) return;
int id = sv.GetInstanceID();
if (!s_map.ContainsKey(id))
{
// avoid duplicate q by name
var existing = root.Q<VisualElement>("ux-controller-ve");
if (existing != null)
{
// if already present (from another vm), keep it and track
var ve = existing as UXControllerOverlayVE;
if (ve != null) s_map[id] = ve;
}
else
{
var ve = new UXControllerOverlayVE();
ve.name = "ux-controller-ve";
// place at top so it overlays scene view content
ve.style.position = Position.Absolute;
ve.style.left = 0;
ve.style.top = 0;
ve.style.width = Length.Percent(100);
// z-index: bring to front
// ve.style.unityZIndex = 1000;
root.Add(ve);
s_map[id] = ve;
}
}
}
private static void EditorUpdate()
{
// Refresh all overlays (they check for changes internally and early out)
foreach (var kv in s_map)
{
var ve = kv.Value;
if (ve != null) ve.EditorUpdate();
}
}
}
public class UXControllerOverlayVE : VisualElement
{
private VisualElement _root;
private VisualElement _controllerList;
private Button _addBtn;
private PrefabStage _currentPrefabStage;
private UXController _currentUX;
private int _lastControllersHash = 0;
public UXControllerOverlayVE()
{
style.flexDirection = FlexDirection.Column;
style.paddingLeft = 6;
style.paddingTop = 6;
style.paddingRight = 6;
style.paddingBottom = 6;
style.width = Length.Percent(100);
style.unityBackgroundImageTintColor = Color.white;
style.height = Length.Percent(5);
style.backgroundColor = new StyleColor(new Color(0.12f, 0.12f, 0.12f, 0.95f));
style.borderTopWidth = 1;
style.borderBottomWidth = 1;
style.borderLeftWidth = 1;
style.borderRightWidth = 1;
style.borderTopColor = new StyleColor(new Color(0, 0, 0, 0.6f));
style.borderBottomColor = new StyleColor(new Color(0, 0, 0, 0.6f));
style.borderLeftColor = new StyleColor(new Color(0, 0, 0, 0.6f));
style.borderRightColor = new StyleColor(new Color(0, 0, 0, 0.6f));
style.paddingBottom = 8;
style.paddingTop = 6;
style.paddingLeft = 6;
style.paddingRight = 6;
_addBtn = new Button(() =>
{
if (_currentUX != null)
{
UXControllerAddWindow.ShowWindow(_currentUX);
}
else
{
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage != null && prefabStage.prefabContentsRoot != null)
{
Undo.AddComponent<UXController>(prefabStage.prefabContentsRoot);
EditorUtility.SetDirty(prefabStage.prefabContentsRoot);
}
}
Refresh();
});
_addBtn.style.width = 22;
_addBtn.style.height = 22;
_addBtn.style.backgroundColor = new StyleColor(new Color(0.26f, 0.26f, 0.26f));
_addBtn.style.marginRight = 0;
_addBtn.style.borderBottomLeftRadius = 4;
_addBtn.style.borderBottomRightRadius = 4;
_addBtn.style.borderTopLeftRadius = 4;
_addBtn.style.borderTopRightRadius = 4;
_addBtn.style.alignSelf = Align.Center;
_addBtn.style.justifyContent = Justify.Center;
_addBtn.tooltip = "添加新的控制器";
var cLabel = new Label("+");
cLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
cLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
cLabel.style.color = new StyleColor(Color.white);
cLabel.style.fontSize = 12;
cLabel.style.flexGrow = 0;
_addBtn.Add(cLabel);
var scroll = new ScrollView(ScrollViewMode.Horizontal);
scroll.style.flexDirection = FlexDirection.Row;
scroll.style.paddingLeft = 0;
scroll.style.paddingRight = 2;
scroll.style.paddingBottom = 0;
scroll.style.paddingTop = 0;
scroll.horizontalScrollerVisibility = ScrollerVisibility.Hidden;
scroll.verticalScrollerVisibility = ScrollerVisibility.Hidden;
_controllerList = new VisualElement();
_controllerList.style.flexDirection = FlexDirection.Row;
_controllerList.style.flexWrap = Wrap.Wrap;
_controllerList.style.alignItems = Align.FlexStart;
scroll.Add(_controllerList);
scroll.Add(_addBtn);
this.Add(scroll);
Refresh();
}
public void EditorUpdate()
{
// called by manager on EditorApplication.update
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage != _currentPrefabStage)
{
_currentPrefabStage = prefabStage;
Refresh();
return;
}
if (_currentUX != null)
{
int h = ComputeControllersHash(_currentUX);
if (h != _lastControllersHash)
{
_lastControllersHash = h;
Refresh();
}
}
}
private int ComputeControllersHash(UXController ux)
{
if (ux == null) return 0;
unchecked
{
int hash = 17;
var list = ux.Controllers;
if (list != null)
{
foreach (var cd in list)
{
hash = hash * 23 + (cd?.Name?.GetHashCode() ?? 0);
hash = hash * 23 + (cd?.Length.GetHashCode() ?? 0);
hash = hash * 23 + (cd?.CurrentIndex.GetHashCode() ?? 0);
}
}
return hash;
}
}
public void Refresh()
{
_controllerList.Clear();
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
_currentPrefabStage = prefabStage;
_currentUX = null;
if (prefabStage != null && prefabStage.prefabContentsRoot != null)
{
_currentUX = prefabStage.prefabContentsRoot.GetComponent<UXController>();
}
if (PrefabStageUtils.InEmptyStage)
{
_addBtn.style.display = DisplayStyle.None;
return;
}
_addBtn.style.display = DisplayStyle.Flex;
if (_currentUX == null)
{
var empty = new Label("当前 Prefab 未包含 UXController。点击右侧按钮可添加。");
empty.style.unityFontStyleAndWeight = FontStyle.Italic;
empty.style.color = new StyleColor(new Color(0.75f, 0.75f, 0.75f));
empty.style.marginLeft = 6;
empty.style.marginTop = 6;
_controllerList.Add(empty);
_lastControllersHash = 0;
return;
}
var controllers = _currentUX.Controllers;
if (controllers == null) return;
for (int i = 0; i < controllers.Count; i++)
{
var cd = controllers[i];
var card = CreateControllerCard(cd, i);
_controllerList.Add(card);
}
_lastControllersHash = ComputeControllersHash(_currentUX);
}
private VisualElement CreateControllerCard(UXController.ControllerData cd, int index)
{
var card = new VisualElement();
card.style.flexDirection = FlexDirection.Row;
card.style.alignItems = Align.Center;
card.style.paddingLeft = 6;
card.style.paddingRight = 6;
card.style.paddingTop = 4;
card.style.paddingBottom = 4;
card.style.backgroundColor = new StyleColor(new Color(0.13f, 0.13f, 0.13f));
card.style.borderTopLeftRadius = 6;
card.style.borderBottomLeftRadius = 6;
card.style.borderTopRightRadius = 6;
card.style.borderBottomRightRadius = 6;
card.style.minHeight = 28;
card.style.marginRight = 4;
card.style.marginBottom = 6;
// icon (C with underline)
var cBox = new VisualElement();
cBox.style.width = 22;
cBox.style.height = 22;
cBox.style.backgroundColor = new StyleColor(new Color(0.26f, 0.26f, 0.26f));
cBox.style.marginRight = 0;
cBox.style.borderBottomLeftRadius = 4;
cBox.style.borderBottomRightRadius = 4;
cBox.style.borderTopLeftRadius = 4;
cBox.style.borderTopRightRadius = 4;
cBox.style.alignSelf = Align.Center;
cBox.style.justifyContent = Justify.Center;
var cLabel = new Label("C");
cLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
cLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
cLabel.style.color = new StyleColor(Color.white);
cLabel.style.fontSize = 12;
cLabel.style.flexGrow = 0;
cBox.Add(cLabel);
var underline = new VisualElement();
underline.style.position = Position.Absolute;
underline.style.height = 2;
underline.style.width = 14;
underline.style.left = 4;
underline.style.bottom = 3;
underline.style.backgroundColor = new StyleColor(Color.white);
cBox.Add(underline);
card.Add(cBox);
var nameBtn = new Button(() => { UXControllerEditWindow.ShowWindow(_currentUX, index, cd.Name, cd.Length); });
nameBtn.text = cd.Name;
nameBtn.style.unityTextAlign = TextAnchor.MiddleLeft;
nameBtn.style.width = 120;
nameBtn.style.height = 22;
nameBtn.style.marginRight = 6;
card.Add(nameBtn);
var indicesContainer = new VisualElement();
indicesContainer.style.flexDirection = FlexDirection.Row;
indicesContainer.style.alignItems = Align.Center;
int length = Math.Max(1, cd.Length);
for (int idx = 0; idx < length; idx++)
{
int capturedIdx = idx;
bool isSelected = (cd.CurrentIndex == idx);
var idxBtn = new Button(() =>
{
SetControllerIndexViaReflection(_currentUX, index, capturedIdx);
Refresh(); // force update visual state
});
idxBtn.text = idx.ToString();
idxBtn.style.width = 22;
idxBtn.style.height = 18;
idxBtn.style.unityTextAlign = TextAnchor.MiddleCenter;
idxBtn.style.paddingLeft = 0;
idxBtn.style.paddingRight = 0;
idxBtn.style.paddingTop = 0;
idxBtn.style.paddingBottom = 0;
idxBtn.style.fontSize = 11;
idxBtn.style.borderTopLeftRadius = 3;
idxBtn.style.borderTopRightRadius = 3;
idxBtn.style.borderBottomLeftRadius = 3;
idxBtn.style.borderBottomRightRadius = 3;
idxBtn.style.marginRight = 4;
if (isSelected)
{
idxBtn.style.backgroundColor = new StyleColor(new Color(0.9f, 0.55f, 0.1f));
idxBtn.style.color = new StyleColor(Color.white);
}
else
{
idxBtn.style.backgroundColor = new StyleColor(new Color(0.18f, 0.18f, 0.18f));
idxBtn.style.color = new StyleColor(Color.white);
}
indicesContainer.Add(idxBtn);
}
card.Add(indicesContainer);
return card;
}
private static void SetControllerIndexViaReflection(UXController ux, int controllerIndex, int selectedIndex)
{
if (ux == null) return;
var t = ux.GetType();
var mi = t.GetMethod("SetControllerIndex", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (mi != null)
{
mi.Invoke(ux, new object[] { controllerIndex, selectedIndex });
EditorUtility.SetDirty(ux);
}
}
}