AlicizaX/Client/Packages/com.alicizax.uxtool/Editor/UXGUI/SceneView/ContextMenu/SceneViewContextMenu.cs

988 lines
37 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 UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
using System.Linq;
using UnityEngine.UI;
namespace Aliciza.UXTool
{
public static class SceneViewContextMenu
{
// 右键状态和拖动状态
private static bool rightMouseDown = false;
private static bool hasDrag = false;
// 右键菜单的委托,允许外部插件扩展菜单
public delegate void AddContextMenuFunc();
public static AddContextMenuFunc addContextMenuFunc;
// 用于复制/粘贴RectTransform数据的缓存
private static RectTransformCache clipboard = null;
[InitializeOnLoadMethod]
public static void Init()
{
// 在SceneView中注册事件回调
if (UXDesinUtil.InDesign)
{
SceneView.duringSceneGui += OnSceneGUI;
}
else
{
SceneView.duringSceneGui -= OnSceneGUI;
}
}
static void OnSceneGUI(SceneView sceneView)
{
if (PrefabStageUtils.InEmptyStage) return;
// 如果不是右键点击,直接返回
if (Event.current == null || Event.current.button != 1)
{
return;
}
// 右键按下,初始化状态
if (Event.current.type == EventType.MouseDown)
{
hasDrag = false;
rightMouseDown = true;
return;
}
// 拖动过程中标记状态
if (Event.current.type == EventType.MouseDrag && rightMouseDown)
{
hasDrag = true;
return;
}
// 右键松开时弹出菜单
if (Event.current.type == EventType.MouseUp && rightMouseDown && !hasDrag)
{
HandleRightClick();
Event.current.Use(); // 防止事件进一步传播
}
}
// 右键菜单生成和显示的核心方法
private static void HandleRightClick()
{
// 生成右键菜单项
GenerateRectTransformMenu();
GenerateSelectObjectMenu();
// 调用外部扩展菜单
addContextMenuFunc?.Invoke();
// 显示菜单
ContextMenuUtils.ShowContextMenu(); // 调用修改后的 ShowContextMenu
// 重置状态
rightMouseDown = false;
}
#region RectTransform
private static void GenerateRectTransformMenu()
{
if (UXSelectionUtil.gameObjects == null || UXSelectionUtil.gameObjects.Length == 0) return;
if (!(UXSelectionUtil.gameObjects[0].transform is RectTransform))
{
return;
}
GameObject[] selected = UXSelectionUtil.gameObjects;
// 添加分隔符
ContextMenuUtils.BuildSeparator();
// 添加通用菜单项
AddCommonItem();
// 布局与对齐
ContextMenuUtils.BuildSeparator();
ContextMenuUtils.BuildContextMenu("对齐/Left", false, () => AlignSelected(AlignType.Left));
ContextMenuUtils.BuildContextMenu("对齐/Center (水平)", false, () => AlignSelected(AlignType.CenterHorizontal));
ContextMenuUtils.BuildContextMenu("对齐/Right", false, () => AlignSelected(AlignType.Right));
ContextMenuUtils.BuildContextMenu("对齐/Top", false, () => AlignSelected(AlignType.Top));
ContextMenuUtils.BuildContextMenu("对齐/Middle (垂直)", false, () => AlignSelected(AlignType.CenterVertical));
ContextMenuUtils.BuildContextMenu("对齐/Bottom", false, () => AlignSelected(AlignType.Bottom));
ContextMenuUtils.BuildSeparator();
ContextMenuUtils.BuildContextMenu("分发/水平分发", false, () => DistributeSelected(DistributeAxis.Horizontal));
ContextMenuUtils.BuildContextMenu("分发/垂直分发", false, () => DistributeSelected(DistributeAxis.Vertical));
ContextMenuUtils.BuildSeparator();
ContextMenuUtils.BuildContextMenu("大小/匹配第一个尺寸", false, () => MatchSizeToFirst());
ContextMenuUtils.BuildContextMenu("像素对齐/位置取整", false, () => PixelSnapSelected());
ContextMenuUtils.BuildContextMenu("重置/位置与旋转与缩放", false, () => ResetTransformSelected());
ContextMenuUtils.BuildSeparator();
ContextMenuUtils.BuildContextMenu("锚点/设为四角(保持位置)", false, () => SetAnchorsToCornersKeepPos());
ContextMenuUtils.BuildContextMenu("锚点/居中锚点(保持位置)", false, () => SetAnchorsToCenterKeepPos());
ContextMenuUtils.BuildSeparator();
ContextMenuUtils.BuildContextMenu("临时操作/复制 RectTransform", false, () => CopyRectTransform());
ContextMenuUtils.BuildContextMenu("临时操作/粘贴 RectTransform", clipboard == null, () => PasteRectTransform());
ContextMenuUtils.BuildSeparator();
ContextMenuUtils.BuildContextMenu("对象/向前移动(置为最后Sibling)", false, () => BringToFront());
ContextMenuUtils.BuildContextMenu("对象/发送到最底层(置为第0)", false, () => SendToBack());
ContextMenuUtils.BuildContextMenu("对象/创建容器并包裹", false, () => CreateContainerAroundSelection());
ContextMenuUtils.BuildContextMenu("对象/复制(场景内)", false, () => DuplicateSelection());
ContextMenuUtils.BuildContextMenu("对象/重命名选中", false, () => RenameSelection());
ContextMenuUtils.BuildContextMenu("对象/选择父对象", false, () => SelectParent());
ContextMenuUtils.BuildContextMenu("对象/聚焦(场景视图)", false, () => FrameSelection());
ContextMenuUtils.BuildSeparator();
ContextMenuUtils.BuildContextMenu("Graphic/切换 RaycastTarget", false, () => ToggleRaycastTarget());
ContextMenuUtils.BuildContextMenu("排序/按名称排序同级子项", false, () => SortSiblingsByName());
// 如果可以组合RectTransform则添加组合项
ContextMenuUtils.BuildSeparator();
if (CombineWidgetLogic.CanCombine(UXSelectionUtil.gameObjects))
{
ContextMenuUtils.BuildContextMenu("组合", false, () => CombineWidgetLogic.GenCombineRootRect(UXSelectionUtil.gameObjects));
}
else
{
ContextMenuUtils.BuildContextMenu("组合", true, null); // Disabled item
}
}
private static void SetAnchors(GameObject[] selectedObjects)
{
// 这里是设置锚点的逻辑,示例函数
Debug.Log("设置锚点的功能...");
}
#endregion
private static void AddCommonItem()
{
}
#region UI
private static void GenerateSelectObjectMenu()
{
var prefabStage = PrefabStageUtils.GetCurrentPrefabStage();
List<RectTransform> inSceneObjs = GetAllUIObjects(prefabStage);
// 获取鼠标位置并转换为屏幕坐标GUI坐标调整
Camera camera = SceneView.currentDrawingSceneView.camera;
Vector2 mousePosGui = Event.current.mousePosition;
Vector2 mousePos = new Vector2(mousePosGui.x, camera.pixelHeight - mousePosGui.y);
// 排序UI对象
SortedList<string, RectTransform> sortedUIObjects = SortUIObjects(inSceneObjs);
// 将UI对象添加到菜单中
foreach (string key in sortedUIObjects.Keys)
{
RectTransform obj = sortedUIObjects[key];
if (RectTransformUtility.RectangleContainsScreenPoint(obj, mousePos, camera))
{
ContextMenuUtils.BuildContextMenu(obj.name, false, () => UXSelectionUtil.activeGameObject = obj.gameObject);
}
}
}
// 获取场景中的所有UI对象
private static List<RectTransform> GetAllUIObjects(PrefabStage prefabStage)
{
List<RectTransform> uiObjects = new List<RectTransform>();
if (prefabStage != null)
{
RectTransform[] allObjects = prefabStage.prefabContentsRoot.GetComponentsInChildren<RectTransform>(true);
foreach (RectTransform obj in allObjects)
{
if (FindContainerLogic.ObjectFit(obj.gameObject))
{
uiObjects.Add(obj);
}
}
}
else
{
Scene scene = SceneManager.GetActiveScene();
GameObject[] rootObjects = scene.GetRootGameObjects();
foreach (GameObject rootObj in rootObjects)
{
RectTransform[] childRects = rootObj.GetComponentsInChildren<RectTransform>(true);
foreach (RectTransform rect in childRects)
{
if (FindContainerLogic.ObjectFit(rect.gameObject))
{
uiObjects.Add(rect);
}
}
}
}
return uiObjects;
}
// 排序UI对象
private static SortedList<string, RectTransform> SortUIObjects(List<RectTransform> uiObjects)
{
List<string> uiNames = new List<string>();
foreach (var rect in uiObjects)
{
uiNames.Add(GetTransformHierarchyString("", rect));
}
// 按照名称排序
SortedList<string, RectTransform> sortedList = new SortedList<string, RectTransform>(new DuplicateKeyComparer());
for (int i = 0; i < uiObjects.Count; i++)
{
// 确保 key 唯一:包含索引
string key = uiNames[i] + "_" + i;
sortedList.Add(key, uiObjects[i]);
}
return sortedList;
}
// 获取UI对象的层级字符串
private static string GetTransformHierarchyString(string prefix, Transform trans)
{
string str = string.IsNullOrEmpty(prefix) ? trans.GetSiblingIndex().ToString() : $"{trans.GetSiblingIndex()}.{prefix}";
if (trans.parent != null)
{
return GetTransformHierarchyString(str, trans.parent);
}
return str;
}
#endregion
#region
// 自定义比较器用于排序UI元素
private class DuplicateKeyComparer : IComparer<string>
{
public int Compare(string x, string y)
{
return -string.Compare(x, y); // 逆序排序
}
}
private enum AlignType
{
Left,
CenterHorizontal,
Right,
Top,
CenterVertical,
Bottom
}
private enum DistributeAxis
{
Horizontal,
Vertical
}
private class RectTransformCache
{
public Vector2 anchoredPosition;
public Vector2 sizeDelta;
public Vector2 anchorMin;
public Vector2 anchorMax;
public Vector2 pivot;
public Quaternion rotation;
public Vector3 localScale;
}
private static void CopyRectTransform()
{
if (UXSelectionUtil.gameObjects == null || UXSelectionUtil.gameObjects.Length == 0) return;
var rt = UXSelectionUtil.gameObjects[0].GetComponent<RectTransform>();
if (rt == null) return;
clipboard = new RectTransformCache()
{
anchoredPosition = rt.anchoredPosition,
sizeDelta = rt.sizeDelta,
anchorMin = rt.anchorMin,
anchorMax = rt.anchorMax,
pivot = rt.pivot,
rotation = rt.localRotation,
localScale = rt.localScale
};
Debug.Log("已复制 RectTransform 数据到剪贴板");
}
private static void PasteRectTransform()
{
if (clipboard == null || UXSelectionUtil.gameObjects == null) return;
foreach (var go in UXSelectionUtil.gameObjects)
{
var rt = go.GetComponent<RectTransform>();
if (rt == null) continue;
Undo.RecordObject(rt, "Paste RectTransform");
rt.anchorMin = clipboard.anchorMin;
rt.anchorMax = clipboard.anchorMax;
rt.pivot = clipboard.pivot;
rt.sizeDelta = clipboard.sizeDelta;
rt.localRotation = clipboard.rotation;
rt.localScale = clipboard.localScale;
// 保持世界位置:计算当前 world pos然后应用 anchoredPosition 使其看起来相同
Vector3 worldPos = rt.position;
rt.anchoredPosition = clipboard.anchoredPosition;
rt.position = worldPos;
EditorUtility.SetDirty(rt);
}
}
private static void AlignSelected(AlignType align)
{
var gos = UXSelectionUtil.gameObjects?.Where(g => g != null).ToArray();
if (gos == null || gos.Length <= 1) return;
// 统一以第一个为目标
RectTransform[] rts = gos.Select(g => g.GetComponent<RectTransform>()).Where(r => r != null).ToArray();
if (rts.Length <= 1) return;
// 计算所有对象在世界空间的边界
switch (align)
{
case AlignType.Left:
{
float target = rts.Min(rt => GetWorldLeft(rt));
foreach (var rt in rts) MoveWorldLeftTo(rt, target);
}
break;
case AlignType.Right:
{
float target = rts.Max(rt => GetWorldRight(rt));
foreach (var rt in rts) MoveWorldRightTo(rt, target);
}
break;
case AlignType.CenterHorizontal:
{
float minX = rts.Min(rt => GetWorldLeft(rt));
float maxX = rts.Max(rt => GetWorldRight(rt));
float centerX = (minX + maxX) / 2f;
foreach (var rt in rts) MoveWorldCenterXTo(rt, centerX);
}
break;
case AlignType.Top:
{
float target = rts.Max(rt => GetWorldTop(rt));
foreach (var rt in rts) MoveWorldTopTo(rt, target);
}
break;
case AlignType.Bottom:
{
float target = rts.Min(rt => GetWorldBottom(rt));
foreach (var rt in rts) MoveWorldBottomTo(rt, target);
}
break;
case AlignType.CenterVertical:
{
float minY = rts.Min(rt => GetWorldBottom(rt));
float maxY = rts.Max(rt => GetWorldTop(rt));
float centerY = (minY + maxY) / 2f;
foreach (var rt in rts) MoveWorldCenterYTo(rt, centerY);
}
break;
}
}
private static float GetWorldLeft(RectTransform rt)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
return corners[0].x; // bottom-left.x
}
private static float GetWorldRight(RectTransform rt)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
return corners[2].x; // top-right.x
}
private static float GetWorldTop(RectTransform rt)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
return corners[1].y; // top-left.y
}
private static float GetWorldBottom(RectTransform rt)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
return corners[0].y; // bottom-left.y
}
// 将世界空间的位移转换为父空间的局部位移,并应用到 rectTransform.localPosition
private static void ApplyWorldDeltaToRectTransform(RectTransform rt, Vector3 worldDelta, string undoName)
{
if (rt == null || rt.parent == null) return;
Undo.RecordObject(rt, undoName);
// 将世界向量转换为父对象的局部向量(旋转会被考虑,但位置不重要因为这是增量)
Vector3 localDelta = rt.parent.InverseTransformVector(worldDelta);
// 应用到 localPosition而不是直接修改 world position这样对于 UI 更稳定
rt.localPosition += localDelta;
EditorUtility.SetDirty(rt);
}
private static void MoveWorldLeftTo(RectTransform rt, float worldX)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
float curLeft = corners[0].x;
float delta = worldX - curLeft;
if (Mathf.Approximately(delta, 0f)) return;
ApplyWorldDeltaToRectTransform(rt, new Vector3(delta, 0, 0), "Align Left");
}
private static void MoveWorldRightTo(RectTransform rt, float worldX)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
float curRight = corners[2].x;
float delta = worldX - curRight;
if (Mathf.Approximately(delta, 0f)) return;
ApplyWorldDeltaToRectTransform(rt, new Vector3(delta, 0, 0), "Align Right");
}
private static void MoveWorldTopTo(RectTransform rt, float worldY)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
float curTop = corners[1].y;
float delta = worldY - curTop;
if (Mathf.Approximately(delta, 0f)) return;
ApplyWorldDeltaToRectTransform(rt, new Vector3(0, delta, 0), "Align Top");
}
private static void MoveWorldBottomTo(RectTransform rt, float worldY)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
float curBottom = corners[0].y;
float delta = worldY - curBottom;
if (Mathf.Approximately(delta, 0f)) return;
ApplyWorldDeltaToRectTransform(rt, new Vector3(0, delta, 0), "Align Bottom");
}
private static void MoveWorldCenterXTo(RectTransform rt, float worldX)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
float curCenterX = (corners[0].x + corners[2].x) / 2f;
float delta = worldX - curCenterX;
if (Mathf.Approximately(delta, 0f)) return;
ApplyWorldDeltaToRectTransform(rt, new Vector3(delta, 0, 0), "Align Center X");
}
private static void MoveWorldCenterYTo(RectTransform rt, float worldY)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
float curCenterY = (corners[0].y + corners[2].y) / 2f;
float delta = worldY - curCenterY;
if (Mathf.Approximately(delta, 0f)) return;
ApplyWorldDeltaToRectTransform(rt, new Vector3(0, delta, 0), "Align Center Y");
}
private static void DistributeSelected(DistributeAxis axis)
{
var gos = UXSelectionUtil.gameObjects?.Where(g => g != null).ToArray();
if (gos == null || gos.Length <= 2) return; // 两个对象没必要
RectTransform[] rts = gos.Select(g => g.GetComponent<RectTransform>()).Where(r => r != null).ToArray();
if (rts.Length <= 2) return;
// 按中心排序
if (axis == DistributeAxis.Horizontal)
{
var ordered = rts.OrderBy(rt => GetWorldCenter(rt).x).ToArray();
float left = GetWorldLeft(ordered.First());
float right = GetWorldRight(ordered.Last());
float totalWidth = ordered.Sum(rt => GetWidthWorld(rt));
float space = (right - left - totalWidth) / (ordered.Length - 1);
float curX = left;
for (int i = 0; i < ordered.Length; i++)
{
float w = GetWidthWorld(ordered[i]);
float targetCenter = curX + w / 2f;
MoveWorldCenterXTo(ordered[i], targetCenter);
curX += w + space;
}
}
else
{
var ordered = rts.OrderBy(rt => GetWorldCenter(rt).y).ToArray();
float bottom = GetWorldBottom(ordered.First());
float top = GetWorldTop(ordered.Last());
float totalHeight = ordered.Sum(rt => GetHeightWorld(rt));
float space = (top - bottom - totalHeight) / (ordered.Length - 1);
float curY = bottom;
for (int i = 0; i < ordered.Length; i++)
{
float h = GetHeightWorld(ordered[i]);
float targetCenter = curY + h / 2f;
MoveWorldCenterYTo(ordered[i], targetCenter);
curY += h + space;
}
}
}
private static Vector2 GetWorldCenter(RectTransform rt)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
return (corners[0] + corners[2]) / 2f;
}
private static float GetWidthWorld(RectTransform rt)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
return Mathf.Abs(corners[2].x - corners[0].x);
}
private static float GetHeightWorld(RectTransform rt)
{
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
return Mathf.Abs(corners[1].y - corners[0].y);
}
private static void MatchSizeToFirst()
{
var gos = UXSelectionUtil.gameObjects;
if (gos == null || gos.Length <= 1) return;
var first = gos[0].GetComponent<RectTransform>();
if (first == null) return;
for (int i = 1; i < gos.Length; i++)
{
var rt = gos[i].GetComponent<RectTransform>();
if (rt == null) continue;
Undo.RecordObject(rt, "Match Size");
rt.sizeDelta = first.sizeDelta;
EditorUtility.SetDirty(rt);
}
}
private static void PixelSnapSelected()
{
foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0])
{
var rt = go.GetComponent<RectTransform>();
if (rt == null) continue;
Undo.RecordObject(rt, "Pixel Snap");
Vector2 ap = rt.anchoredPosition;
ap.x = Mathf.Round(ap.x);
ap.y = Mathf.Round(ap.y);
rt.anchoredPosition = ap;
EditorUtility.SetDirty(rt);
}
}
private static void ResetTransformSelected()
{
foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0])
{
var rt = go.GetComponent<RectTransform>();
if (rt == null) continue;
Undo.RecordObject(rt, "Reset Transform");
rt.anchoredPosition = Vector2.zero;
rt.localRotation = Quaternion.identity;
rt.localScale = Vector3.one;
EditorUtility.SetDirty(rt);
}
}
private static void SetAnchorsToCornersKeepPos()
{
foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0])
{
var rt = go.GetComponent<RectTransform>();
if (rt == null) continue;
Undo.RecordObject(rt, "Set Anchors to Corners Keep Position");
Transform parent = rt.parent;
if (parent == null) continue;
RectTransform parentRT = parent as RectTransform;
if (parentRT == null) continue;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
Vector3[] parentCorners = new Vector3[4];
parentRT.GetWorldCorners(parentCorners);
Vector2 newAnchorMin = new Vector2(
Mathf.InverseLerp(parentCorners[0].x, parentCorners[2].x, corners[0].x),
Mathf.InverseLerp(parentCorners[0].y, parentCorners[2].y, corners[0].y));
Vector2 newAnchorMax = new Vector2(
Mathf.InverseLerp(parentCorners[0].x, parentCorners[2].x, corners[2].x),
Mathf.InverseLerp(parentCorners[0].y, parentCorners[2].y, corners[2].y));
Vector2 oldAnchored = rt.anchoredPosition;
rt.anchorMin = newAnchorMin;
rt.anchorMax = newAnchorMax;
rt.anchoredPosition = oldAnchored; // 尝试保留视觉位置
EditorUtility.SetDirty(rt);
}
}
private static void SetAnchorsToCenterKeepPos()
{
foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0])
{
var rt = go.GetComponent<RectTransform>();
if (rt == null) continue;
Undo.RecordObject(rt, "Set Anchors to Center Keep Position");
Transform parent = rt.parent;
if (parent == null) continue;
RectTransform parentRT = parent as RectTransform;
if (parentRT == null) continue;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
Vector3[] parentCorners = new Vector3[4];
parentRT.GetWorldCorners(parentCorners);
float centerX = (corners[0].x + corners[2].x) / 2f;
float centerY = (corners[0].y + corners[2].y) / 2f;
Vector2 anchor = new Vector2(
Mathf.InverseLerp(parentCorners[0].x, parentCorners[2].x, centerX),
Mathf.InverseLerp(parentCorners[0].y, parentCorners[2].y, centerY));
Vector2 oldAnchored = rt.anchoredPosition;
rt.anchorMin = anchor;
rt.anchorMax = anchor;
rt.anchoredPosition = oldAnchored;
EditorUtility.SetDirty(rt);
}
}
private static void BringToFront()
{
foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0])
{
Transform parent = go.transform.parent;
Undo.RecordObject(go.transform, "Bring To Front");
if (parent != null)
{
go.transform.SetSiblingIndex(parent.childCount - 1);
}
else
{
go.transform.SetSiblingIndex(go.transform.GetSiblingIndex());
}
EditorUtility.SetDirty(go);
}
}
private static void SendToBack()
{
foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0])
{
Transform parent = go.transform.parent;
Undo.RecordObject(go.transform, "Send To Back");
go.transform.SetSiblingIndex(0);
EditorUtility.SetDirty(go);
}
}
private static void CreateContainerAroundSelection()
{
var gos = UXSelectionUtil.gameObjects?.Where(g => g != null).ToArray();
if (gos == null || gos.Length == 0) return;
// 只允许相同父级
Transform parent = gos[0].transform.parent;
if (gos.Any(g => g.transform.parent != parent))
{
Debug.LogWarning("创建容器要求所有对象在同一父级下");
return;
}
// 计算包围盒(世界坐标)
var rts = gos.Select(g => g.GetComponent<RectTransform>()).Where(r => r != null).ToArray();
if (rts.Length == 0) return;
Vector3 worldMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
Vector3 worldMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
foreach (var rt in rts)
{
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
worldMin = Vector3.Min(worldMin, corners[0]);
worldMax = Vector3.Max(worldMax, corners[2]);
}
// 创建容器
GameObject root = UIBuilderUtil.CreateUIObj("Container");
Undo.RegisterCreatedObjectUndo(root, "Create Container");
var rootRT = root.GetComponent<RectTransform>();
root.transform.SetParent(parent);
root.transform.SetSiblingIndex(rts.Min(r => r.GetSiblingIndex()));
rootRT.localScale = Vector3.one;
// 将世界包围盒转换为父本地坐标
RectTransform parentRT = parent as RectTransform;
if (parentRT == null)
{
Debug.LogWarning("父对象不是 RectTransform无法创建容器");
return;
}
Vector3 localMin = parentRT.InverseTransformPoint(worldMin);
Vector3 localMax = parentRT.InverseTransformPoint(worldMax);
Vector2 size = new Vector2(localMax.x - localMin.x, localMax.y - localMin.y);
Vector2 center = (localMin + localMax) / 2f;
rootRT.sizeDelta = size;
rootRT.anchoredPosition = center;
// 将选中对象设置为容器子对象并保持位置
foreach (var g in gos)
{
Undo.SetTransformParent(g.transform, root.transform, "Wrap In Container");
}
Selection.activeGameObject = root;
EditorUtility.SetDirty(rootRT);
}
private static void DuplicateSelection()
{
var gos = UXSelectionUtil.gameObjects?.Where(g => g != null).ToArray();
if (gos == null || gos.Length == 0) return;
List<GameObject> newOnes = new List<GameObject>();
foreach (var g in gos)
{
GameObject dup = Object.Instantiate(g, g.transform.parent);
dup.name = g.name + " Copy";
Undo.RegisterCreatedObjectUndo(dup, "Duplicate UI Element");
dup.transform.SetSiblingIndex(g.transform.GetSiblingIndex() + 1);
newOnes.Add(dup);
}
Selection.objects = newOnes.ToArray();
}
private static void RenameSelection()
{
if (UXSelectionUtil.gameObjects == null || UXSelectionUtil.gameObjects.Length == 0) return;
Selection.activeGameObject = UXSelectionUtil.gameObjects[0];
// 调用编辑器的重命名命令
EditorApplication.ExecuteMenuItem("Edit/Rename");
}
private static void SelectParent()
{
if (UXSelectionUtil.gameObjects == null || UXSelectionUtil.gameObjects.Length == 0) return;
var go = UXSelectionUtil.gameObjects[0];
if (go.transform.parent != null)
{
Selection.activeGameObject = go.transform.parent.gameObject;
}
}
private static void FrameSelection()
{
SceneView.lastActiveSceneView?.FrameSelected();
}
private static void ToggleRaycastTarget()
{
foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0])
{
var g = go.GetComponent<Graphic>();
if (g == null) continue;
Undo.RecordObject(g, "Toggle RaycastTarget");
g.raycastTarget = !g.raycastTarget;
EditorUtility.SetDirty(g);
}
}
private static void SortSiblingsByName()
{
var gos = UXSelectionUtil.gameObjects?.Where(g => g != null).ToArray();
if (gos == null || gos.Length == 0) return;
Transform parent = gos[0].transform.parent;
if (parent == null) return;
var children = parent.Cast<Transform>().Select(t => t.gameObject).OrderBy(o => o.name).ToArray();
for (int i = 0; i < children.Length; i++)
{
Undo.RecordObject(children[i].transform, "Sort By Name");
children[i].transform.SetSiblingIndex(i);
}
}
#endregion
}
// CombineWidgetLogic 放在同一文件以便完整交付
public static class CombineWidgetLogic
{
/// <summary>
/// 组合节点
/// </summary>
/// <param name="rects">需要组合的对象 需要所有对象在同一层级</param>
/// <returns>组合之后的父节点</returns>
public static GameObject GenCombineRootRect(List<RectTransform> rects)
{
if (rects == null || rects.Count == 0) return null;
// 确保所有在同一父级
Transform parent = rects[0].transform.parent;
if (rects.Any(r => r.transform.parent != parent))
{
Debug.LogWarning("所有对象必须在同一父级下才能组合");
return null;
}
// 计算世界包围盒
Vector3 worldMin = Vector3.positiveInfinity;
Vector3 worldMax = Vector3.negativeInfinity;
foreach (var rt in rects)
{
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
worldMin = Vector3.Min(worldMin, corners[0]);
worldMax = Vector3.Max(worldMax, corners[2]);
}
// 创建容器
GameObject root = UIBuilderUtil.CreateUIObj("CombineRoot");
RectTransform rootRT = root.GetComponent<RectTransform>();
root.transform.SetParent(parent);
rootRT.localScale = Vector3.one;
// 将世界包围盒转换为父本地坐标
RectTransform parentRT = parent as RectTransform;
if (parentRT == null)
{
Debug.LogWarning("父对象不是 RectTransform无法创建组合容器");
return null;
}
Vector3 localMin = parentRT.InverseTransformPoint(worldMin);
Vector3 localMax = parentRT.InverseTransformPoint(worldMax);
Vector2 size = new Vector2(localMax.x - localMin.x, localMax.y - localMin.y);
Vector2 center = (localMin + localMax) / 2f;
rootRT.sizeDelta = size;
rootRT.anchoredPosition = center;
// 按原顺序设置为子对象
rects.Sort((a, b) => a.GetSiblingIndex() - b.GetSiblingIndex());
foreach (var rt in rects)
{
Undo.SetTransformParent(rt.transform, root.transform, "Combine Widgets");
}
Selection.activeGameObject = root;
return root;
}
/// <summary>
/// 组合节点
/// </summary>
/// <param name="rects"></param>
/// <returns></returns>
public static GameObject GenCombineRootRect(GameObject[] objs)
{
List<RectTransform> rects = objs.ToList().Select(a => a.GetComponent<RectTransform>()).Where(r => r != null).ToList();
return GenCombineRootRect(rects);
}
/// <summary>
///
/// </summary>
/// <param name="rects"></param>
/// <returns></returns>
private static bool AllHaveSameParent(GameObject[] objs)
{
if (objs.Length == 1)
{
return true;
}
Transform parent = objs[0].transform.parent;
for (int i = 1; i < objs.Length; i++)
{
if (objs[i].transform.parent != parent)
{
return false;
}
}
return true;
}
private static bool AllHaveRectTransform(GameObject[] objs)
{
for (int i = 0; i < objs.Length; i++)
{
if (objs[i].GetComponent<RectTransform>() == null)
{
return false;
}
}
return true;
}
public static bool CanCombine(GameObject[] objs)
{
if (objs == null || objs.Length <= 1)
{
return false;
}
bool SameLevelUI = false;
if (AllHaveSameParent(objs))
{
SameLevelUI = true;
}
bool UIInPrefab = false;
foreach (GameObject go in objs)
{
string assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go);
if (!string.IsNullOrEmpty(assetPath))
{
UIInPrefab = true;
}
}
if (SameLevelUI && !UIInPrefab)
{
return true;
}
return false;
}
}
}
#endif