com.alicizax.unity.ui.exten.../Editor/RecyclerView/RecyclerViewEditor.cs

473 lines
15 KiB
C#
Raw Normal View History

2025-05-30 10:32:08 +08:00
using UnityEditor;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using AlicizaX.UI.RecyclerView;
using UnityEngine.UI;
using Object = UnityEngine.Object;
[CustomEditor(typeof(RecyclerView))]
public class RecyclerViewEditor : Editor
{
// Layout Manager
private SerializedProperty _layoutManagerTypeName;
private SerializedProperty _layoutManager;
private List<string> _layoutTypeNames = new List<string>();
private int _selectedLayoutIndex;
// Scroller
private SerializedProperty scroll;
private SerializedProperty _scroller;
private SerializedProperty _scrollerTypeName;
private List<Type> _scrollerTypes = new List<Type>();
private List<string> _scrollerTypeNames;
private int _selectedScrollerIndex;
// Base Properties
private SerializedProperty direction;
private SerializedProperty alignment;
private SerializedProperty content;
private SerializedProperty spacing;
private SerializedProperty padding;
private SerializedProperty snap;
private SerializedProperty scrollSpeed;
private SerializedProperty wheelSpeed;
private SerializedProperty templates;
private SerializedProperty _showScrollBar;
private SerializedProperty _scrollbar;
private const string NoneOptionName = "None";
private void OnEnable()
{
// Layout Manager
_layoutManagerTypeName = serializedObject.FindProperty("_layoutManagerTypeName");
_layoutManager = serializedObject.FindProperty("_layoutManager");
RefreshLayoutTypes();
// Scroller
scroll = serializedObject.FindProperty("scroll");
_scroller = serializedObject.FindProperty("_scroller");
_scrollerTypeName = serializedObject.FindProperty("_scrollerTypeName");
RefreshScrollerTypes();
SyncExistingScroller();
// Base Properties
direction = serializedObject.FindProperty("direction");
alignment = serializedObject.FindProperty("alignment");
content = serializedObject.FindProperty("content");
spacing = serializedObject.FindProperty("spacing");
padding = serializedObject.FindProperty("padding");
snap = serializedObject.FindProperty("snap");
scrollSpeed = serializedObject.FindProperty("scrollSpeed");
wheelSpeed = serializedObject.FindProperty("wheelSpeed");
templates = serializedObject.FindProperty("templates");
_showScrollBar = serializedObject.FindProperty("_showScrollBar");
_scrollbar = serializedObject.FindProperty("_scrollbar");
}
#region Layout Manager
void RefreshLayoutTypes()
{
_layoutTypeNames.Clear();
_layoutTypeNames.Add(NoneOptionName);
// 获取所有实现ILayoutManager的非Mono类型
var types = AlicizaX.Utility.Assembly.GetRuntimeTypes(typeof(ILayoutManager));
foreach (var type in types)
{
if (!typeof(MonoBehaviour).IsAssignableFrom(type))
{
_layoutTypeNames.Add(type.FullName);
}
}
_selectedLayoutIndex = Mathf.Clamp(
_layoutTypeNames.IndexOf(_layoutManagerTypeName.stringValue),
0,
_layoutTypeNames.Count - 1
);
}
#endregion
#region Scroller
void RefreshScrollerTypes()
{
_scrollerTypes = TypeCache.GetTypesDerivedFrom<IScroller>()
.Where(t => t.IsSubclassOf(typeof(MonoBehaviour)))
.ToList();
_scrollerTypeNames = _scrollerTypes
.Select(t => t.FullName)
.Prepend(NoneOptionName)
.ToList();
}
void SyncExistingScroller()
{
var rv = target as RecyclerView;
if (rv == null) return;
var existing = rv.GetComponent<IScroller>();
if (existing != null)
{
_scrollerTypeName.stringValue = existing.GetType().FullName;
_selectedScrollerIndex = _scrollerTypeNames.IndexOf(_scrollerTypeName.stringValue);
}
else
{
_selectedScrollerIndex = 0;
}
}
#endregion
public override void OnInspectorGUI()
{
serializedObject.Update();
bool isPlaying = Application.isPlaying;
DrawLayoutManagerSection(isPlaying);
DrawBaseSettings(isPlaying);
DrawScrollerSettings(isPlaying);
DrawTemplatesSection();
serializedObject.ApplyModifiedProperties();
}
#region Drawing Methods
void DrawLayoutManagerSection(bool isPlaying)
{
EditorGUILayout.BeginVertical("box");
{
EditorGUILayout.LabelField("Layout Manager", EditorStyles.boldLabel);
using (new EditorGUI.DisabledScope(isPlaying))
{
// 强制允许选择空值
int newIndex = EditorGUILayout.Popup("Layout Type", _selectedLayoutIndex, _layoutTypeNames.ToArray());
if (newIndex != _selectedLayoutIndex)
{
_selectedLayoutIndex = newIndex; // 立即更新索引
UpdateLayoutManager(newIndex);
}
}
// 显示布局属性或警告
if (_layoutManager.managedReferenceValue != null)
{
EditorGUILayout.Space(3);
DrawManagedProperties(_layoutManager);
}
else
{
EditorGUILayout.HelpBox("Need Choose LayoutManager", MessageType.Error);
}
}
EditorGUILayout.EndVertical();
}
void DrawBaseSettings(bool isPlaying)
{
EditorGUILayout.BeginVertical("box");
{
EditorGUILayout.LabelField("Base Settings", EditorStyles.boldLabel);
using (new EditorGUI.DisabledScope(isPlaying))
{
EditorGUILayout.PropertyField(direction);
EditorGUILayout.PropertyField(alignment);
EditorGUILayout.PropertyField(content);
}
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("Spacing", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(spacing);
EditorGUILayout.PropertyField(padding);
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("Scrolling", EditorStyles.boldLabel);
using (new EditorGUI.DisabledScope(isPlaying))
{
bool prevScrollValue = scroll.boolValue;
EditorGUILayout.PropertyField(scroll);
if (scroll.boolValue != prevScrollValue)
{
HandleScrollToggle();
if (!scroll.boolValue)
{
ClearScrollBar();
}
}
}
if (scroll.boolValue)
{
using (new EditorGUI.DisabledScope(isPlaying))
{
bool prevShowScrollBarValue = _showScrollBar.boolValue;
EditorGUILayout.PropertyField(_showScrollBar);
if (_showScrollBar.boolValue != prevShowScrollBarValue)
{
HandleScrollBarToggle();
}
}
}
EditorGUILayout.PropertyField(snap);
}
EditorGUILayout.EndVertical();
}
void DrawScrollerSettings(bool isPlaying)
{
if (!scroll.boolValue) return;
EditorGUILayout.BeginVertical("box");
{
EditorGUILayout.LabelField("Scroller Settings", EditorStyles.boldLabel);
using (new EditorGUI.DisabledScope(isPlaying))
{
int newIndex = EditorGUILayout.Popup("Scroller Type", _selectedScrollerIndex, _scrollerTypeNames.ToArray());
if (newIndex != _selectedScrollerIndex)
{
UpdateScroller(newIndex);
}
}
var rv = target as RecyclerView;
if (rv != null)
{
var scrollerComponent = rv.GetComponent<IScroller>();
if (scrollerComponent != null)
{
DrawComponentProperties(scrollerComponent as MonoBehaviour, "Scroller Properties");
}
else
{
EditorGUILayout.HelpBox("Must choose to use a Scroller.", MessageType.Error);
}
}
EditorGUILayout.Space(3);
EditorGUILayout.PropertyField(scrollSpeed);
EditorGUILayout.PropertyField(wheelSpeed);
}
EditorGUILayout.EndVertical();
}
void DrawComponentProperties(MonoBehaviour component, string header = null)
{
if (component == null) return;
EditorGUILayout.Space(3);
if (!string.IsNullOrEmpty(header))
{
EditorGUILayout.LabelField(header, EditorStyles.boldLabel);
}
SerializedObject so = new SerializedObject(component);
so.Update();
SerializedProperty prop = so.GetIterator();
bool enterChildren = true;
while (prop.NextVisible(enterChildren))
{
enterChildren = false;
if (prop.name == "m_Script") continue;
EditorGUILayout.PropertyField(prop, true);
}
so.ApplyModifiedProperties();
}
void DrawTemplatesSection()
{
EditorGUILayout.Space(5);
EditorGUILayout.PropertyField(templates, new GUIContent("Item Templates"), true);
}
#endregion
#region Update Handlers
void UpdateLayoutManager(int selectedIndex)
{
try
{
// 强制清空逻辑
if (selectedIndex == 0)
{
_layoutManager.managedReferenceValue = null;
_layoutManagerTypeName.stringValue = "";
_selectedLayoutIndex = 0; // 确保索引同步
return;
}
// 有效性检查
if (selectedIndex < 0 || selectedIndex >= _layoutTypeNames.Count)
{
Debug.LogError($"Invalid layout index: {selectedIndex}");
_selectedLayoutIndex = 0;
return;
}
string typeName = _layoutTypeNames[selectedIndex];
Type type = AlicizaX.Utility.Assembly.GetType(typeName);
if (type != null && typeof(ILayoutManager).IsAssignableFrom(type))
{
_layoutManager.managedReferenceValue = Activator.CreateInstance(type);
_layoutManagerTypeName.stringValue = typeName;
_selectedLayoutIndex = selectedIndex;
}
else
{
Debug.LogError($"Invalid layout type: {typeName}");
_selectedLayoutIndex = 0;
}
}
catch (Exception e)
{
Debug.LogError($"Layout Manager Error: {e.Message}");
_layoutManager.managedReferenceValue = null;
_layoutManagerTypeName.stringValue = "";
_selectedLayoutIndex = 0;
}
}
void UpdateScroller(int selectedIndex)
{
try
{
var rv = target as RecyclerView;
if (rv == null) return;
Undo.RecordObjects(new UnityEngine.Object[] { rv, this }, "Update Scroller");
// 移除旧组件
var oldScroller = rv.GetComponent<IScroller>();
if (oldScroller != null)
{
Undo.DestroyObjectImmediate(oldScroller as MonoBehaviour);
}
if (selectedIndex == 0)
{
// 清除序列化引用
_scroller.objectReferenceValue = null;
_scrollerTypeName.stringValue = "";
return;
}
// 添加新组件
Type selectedType = _scrollerTypes[selectedIndex - 1];
var newScroller = Undo.AddComponent(rv.gameObject, selectedType) as IScroller;
// 同步到序列化属性
_scroller.objectReferenceValue = newScroller as MonoBehaviour;
_scrollerTypeName.stringValue = selectedType.FullName;
_selectedScrollerIndex = selectedIndex;
// 立即应用修改
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(rv); // 标记对象需要保存
}
catch (Exception e)
{
Debug.LogError($"Scroller Error: {e}");
_selectedScrollerIndex = 0;
_scrollerTypeName.stringValue = "";
_scroller.objectReferenceValue = null;
}
}
void HandleScrollToggle()
{
var rv = target as RecyclerView;
if (rv == null) return;
if (!scroll.boolValue)
{
// Remove scroller component
var scrollerComponent = rv.GetComponent<IScroller>();
if (scrollerComponent != null)
{
Undo.DestroyObjectImmediate(scrollerComponent as MonoBehaviour);
}
_scrollerTypeName.stringValue = "";
_selectedScrollerIndex = 0;
}
}
void HandleScrollBarToggle()
{
var rv = target as RecyclerView;
if (rv == null) return;
if (_showScrollBar.boolValue)
{
2025-06-03 13:57:23 +08:00
Direction direction = (Direction)this.direction.enumValueIndex;
2025-05-30 10:32:08 +08:00
if (direction == Direction.Vertical)
{
const string path = "Packages/com.alicizax.unity.ui.extension/Editor/RecyclerView/Res/vertical.prefab";
InstantiateScrollBar(path, rv.transform);
}
else if (direction == Direction.Horizontal)
{
const string path = "Packages/com.alicizax.unity.ui.extension/Editor/RecyclerView/Res/horizontal.prefab";
InstantiateScrollBar(path, rv.transform);
}
}
else
{
ClearScrollBar();
}
}
void ClearScrollBar()
{
_showScrollBar.boolValue = false;
if (_scrollbar.objectReferenceValue != null)
{
Scrollbar scrollbar = _scrollbar.objectReferenceValue as Scrollbar;
_scrollbar.objectReferenceValue = null;
Object.DestroyImmediate(scrollbar.gameObject);
}
}
#endregion
void DrawManagedProperties(SerializedProperty property)
{
SerializedProperty iterator = property.Copy();
bool enterChildren = true;
while (iterator.NextVisible(enterChildren))
{
enterChildren = false;
if (iterator.name == "m_Script") continue;
EditorGUILayout.PropertyField(iterator, true);
}
}
void InstantiateScrollBar(string path, Transform parent)
{
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
GameObject instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent);
PrefabUtility.UnpackPrefabInstance(instance, PrefabUnpackMode.Completely, InteractionMode.UserAction);
_scrollbar.objectReferenceValue = instance.GetComponent<Scrollbar>();
}
}