using System; using System.Collections.Generic; using System.Reflection; using Aliciza.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 s_map = new Dictionary(); [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("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("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(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(); } 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); } } }