271 lines
9.4 KiB
C#
271 lines
9.4 KiB
C#
|
|
using System.Collections.Generic;
|
|||
|
|
using UnityEditorInternal;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UnityEngine.UI;
|
|||
|
|
using UnityEditor;
|
|||
|
|
|
|||
|
|
namespace UnityEditor.UI
|
|||
|
|
{
|
|||
|
|
[CustomEditor(typeof(UXGroup))]
|
|||
|
|
public class UXGroupInspector : Editor
|
|||
|
|
{
|
|||
|
|
private SerializedProperty m_Toggles;
|
|||
|
|
private SerializedProperty m_AllowSwitchOff;
|
|||
|
|
private SerializedProperty m_DefaultToggle;
|
|||
|
|
private UXGroup _target;
|
|||
|
|
private ReorderableList _reorderableList;
|
|||
|
|
|
|||
|
|
private void OnEnable()
|
|||
|
|
{
|
|||
|
|
_target = (UXGroup)target;
|
|||
|
|
m_Toggles = serializedObject.FindProperty("m_Toggles");
|
|||
|
|
m_AllowSwitchOff = serializedObject.FindProperty("m_AllowSwitchOff");
|
|||
|
|
m_DefaultToggle = serializedObject.FindProperty("m_DefaultToggle");
|
|||
|
|
|
|||
|
|
_reorderableList = new ReorderableList(serializedObject, m_Toggles, true, true, true, true)
|
|||
|
|
{
|
|||
|
|
drawHeaderCallback = DrawHeader,
|
|||
|
|
drawElementCallback = DrawElement,
|
|||
|
|
onRemoveCallback = OnRemoveList,
|
|||
|
|
onChangedCallback = OnChanged,
|
|||
|
|
displayAdd = false,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override void OnInspectorGUI()
|
|||
|
|
{
|
|||
|
|
serializedObject.Update();
|
|||
|
|
|
|||
|
|
EditorGUILayout.PropertyField(m_AllowSwitchOff);
|
|||
|
|
|
|||
|
|
// Default selector: only show toggles that are currently in the group's m_Toggles list
|
|||
|
|
DrawDefaultToggleSelector();
|
|||
|
|
|
|||
|
|
bool isPlaying = Application.isPlaying || EditorApplication.isPlaying;
|
|||
|
|
|
|||
|
|
_reorderableList.draggable = !isPlaying;
|
|||
|
|
_reorderableList.displayAdd = !isPlaying;
|
|||
|
|
_reorderableList.displayRemove = !isPlaying;
|
|||
|
|
|
|||
|
|
bool prevEnabled = GUI.enabled;
|
|||
|
|
if (isPlaying) GUI.enabled = false;
|
|||
|
|
|
|||
|
|
_reorderableList.DoLayoutList();
|
|||
|
|
|
|||
|
|
GUI.enabled = prevEnabled;
|
|||
|
|
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
|
|||
|
|
// 在编辑器下尽量实时同步状态
|
|||
|
|
if (!Application.isPlaying)
|
|||
|
|
{
|
|||
|
|
// 保证序列化数据写入后同步 group 状态
|
|||
|
|
_target.EnsureValidState();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void DrawDefaultToggleSelector()
|
|||
|
|
{
|
|||
|
|
// Build a list of current toggles (non-null)
|
|||
|
|
List<UXToggle> toggles = new List<UXToggle>();
|
|||
|
|
for (int i = 0; i < m_Toggles.arraySize; i++)
|
|||
|
|
{
|
|||
|
|
var elem = m_Toggles.GetArrayElementAtIndex(i);
|
|||
|
|
var t = elem.objectReferenceValue as UXToggle;
|
|||
|
|
if (t != null)
|
|||
|
|
toggles.Add(t);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Prepare options array with a "None" entry
|
|||
|
|
string[] options = new string[toggles.Count + 1];
|
|||
|
|
options[0] = "<None>";
|
|||
|
|
for (int i = 0; i < toggles.Count; i++)
|
|||
|
|
{
|
|||
|
|
options[i + 1] = string.Format("[{0}] {1}", i, toggles[i] != null ? toggles[i].name : "Null");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Determine current index
|
|||
|
|
UXToggle currentDefault = m_DefaultToggle.objectReferenceValue as UXToggle;
|
|||
|
|
int currentIndex = 0;
|
|||
|
|
if (currentDefault != null)
|
|||
|
|
{
|
|||
|
|
int found = toggles.IndexOf(currentDefault);
|
|||
|
|
if (found >= 0)
|
|||
|
|
currentIndex = found + 1; // +1 because 0 is <None>
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// Current default is not in the list -> clear it
|
|||
|
|
m_DefaultToggle.objectReferenceValue = null;
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
currentIndex = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUI.BeginChangeCheck();
|
|||
|
|
int newIndex = EditorGUILayout.Popup("Default Toggle", currentIndex, options);
|
|||
|
|
if (EditorGUI.EndChangeCheck())
|
|||
|
|
{
|
|||
|
|
UXToggle newDefault = null;
|
|||
|
|
if (newIndex > 0)
|
|||
|
|
newDefault = toggles[newIndex - 1];
|
|||
|
|
|
|||
|
|
m_DefaultToggle.objectReferenceValue = newDefault;
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
|
|||
|
|
// 如果选择了一个非空默认项,则在 allowSwitchOff == false 时将其设为选中状态(并通知组)
|
|||
|
|
if (newDefault != null)
|
|||
|
|
{
|
|||
|
|
// 确保该 toggle 在 group 中(理论上应该如此)
|
|||
|
|
if (!_target.ContainsToggle(newDefault))
|
|||
|
|
{
|
|||
|
|
_target.RegisterToggle(newDefault);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!_target.allowSwitchOff)
|
|||
|
|
{
|
|||
|
|
// 通过 SerializedObject 修改 UXToggle 的 m_IsOn,避免触发不必要的回调
|
|||
|
|
SerializedObject so = new SerializedObject(newDefault);
|
|||
|
|
var isOnProp = so.FindProperty("m_IsOn");
|
|||
|
|
isOnProp.boolValue = true;
|
|||
|
|
so.ApplyModifiedProperties();
|
|||
|
|
|
|||
|
|
// 通知组同步其余项
|
|||
|
|
_target.NotifyToggleOn(newDefault);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// 选择 None:不自动切换状态。但如果组不允许 all-off,则会在 EnsureValidState 中被处理
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void DrawHeader(Rect rect)
|
|||
|
|
{
|
|||
|
|
EditorGUI.LabelField(rect, "Toggles", EditorStyles.boldLabel);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 记录旧的引用用于侦测变化
|
|||
|
|
private UXToggle previousRef;
|
|||
|
|
|
|||
|
|
private void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
|
|||
|
|
{
|
|||
|
|
SerializedProperty element = m_Toggles.GetArrayElementAtIndex(index);
|
|||
|
|
|
|||
|
|
rect.y += 2;
|
|||
|
|
Rect fieldRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
|
|||
|
|
|
|||
|
|
UXToggle oldButton = element.objectReferenceValue as UXToggle;
|
|||
|
|
|
|||
|
|
string label = $"[{index}] {(oldButton != null ? oldButton.name : "Null")}";
|
|||
|
|
|
|||
|
|
EditorGUI.BeginChangeCheck();
|
|||
|
|
|
|||
|
|
var newRef = EditorGUI.ObjectField(fieldRect, label, oldButton, typeof(UXToggle), true) as UXToggle;
|
|||
|
|
|
|||
|
|
if (EditorGUI.EndChangeCheck())
|
|||
|
|
{
|
|||
|
|
// 先处理 Remove(旧值存在且不同)
|
|||
|
|
if (oldButton != null && oldButton != newRef)
|
|||
|
|
{
|
|||
|
|
OnRemove(oldButton);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 再处理 Add(新值非空)
|
|||
|
|
if (newRef != null && oldButton != newRef)
|
|||
|
|
{
|
|||
|
|
OnAdd(newRef);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 最后把引用写回去
|
|||
|
|
element.objectReferenceValue = newRef;
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnAddList(ReorderableList list)
|
|||
|
|
{
|
|||
|
|
int newIndex = m_Toggles.arraySize;
|
|||
|
|
m_Toggles.arraySize++;
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
|
|||
|
|
var newElem = m_Toggles.GetArrayElementAtIndex(newIndex);
|
|||
|
|
newElem.objectReferenceValue = null;
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnRemoveList(ReorderableList list)
|
|||
|
|
{
|
|||
|
|
if (list.index < 0 || list.index >= m_Toggles.arraySize)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var oldButton = m_Toggles.GetArrayElementAtIndex(list.index).objectReferenceValue as UXToggle;
|
|||
|
|
if (oldButton)
|
|||
|
|
{
|
|||
|
|
OnRemove(oldButton);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
m_Toggles.DeleteArrayElementAtIndex(list.index);
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnChanged(ReorderableList list)
|
|||
|
|
{
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
|
|||
|
|
// 编辑器变动后同步 group 状态和默认项
|
|||
|
|
if (!Application.isPlaying)
|
|||
|
|
_target.EnsureValidState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ========================
|
|||
|
|
// 自动调用的新增方法
|
|||
|
|
// ========================
|
|||
|
|
|
|||
|
|
private void OnAdd(UXToggle toggle)
|
|||
|
|
{
|
|||
|
|
if (toggle == null)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
SerializedObject so = new SerializedObject(toggle);
|
|||
|
|
var groupProp = so.FindProperty("m_Group");
|
|||
|
|
groupProp.objectReferenceValue = target;
|
|||
|
|
so.ApplyModifiedProperties();
|
|||
|
|
|
|||
|
|
UXGroup group = (UXGroup)target;
|
|||
|
|
group.RegisterToggle(toggle);
|
|||
|
|
|
|||
|
|
// 添加后尽量同步组状态(处理默认项或冲突)
|
|||
|
|
if (!Application.isPlaying)
|
|||
|
|
group.EnsureValidState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnRemove(UXToggle toggle)
|
|||
|
|
{
|
|||
|
|
if (toggle == null)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
SerializedObject so = new SerializedObject(toggle);
|
|||
|
|
var groupProp = so.FindProperty("m_Group");
|
|||
|
|
|
|||
|
|
UXGroup group = groupProp.objectReferenceValue as UXGroup;
|
|||
|
|
if (group != null)
|
|||
|
|
group.UnregisterToggle(toggle);
|
|||
|
|
|
|||
|
|
groupProp.objectReferenceValue = null;
|
|||
|
|
|
|||
|
|
so.ApplyModifiedProperties();
|
|||
|
|
|
|||
|
|
// 如果移除的正好是默认项,则清空默认选项
|
|||
|
|
if (m_DefaultToggle != null && m_DefaultToggle.objectReferenceValue == toggle)
|
|||
|
|
{
|
|||
|
|
m_DefaultToggle.objectReferenceValue = null;
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!Application.isPlaying && _target != null)
|
|||
|
|
_target.EnsureValidState();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|