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

591 lines
18 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.

using UnityEditor;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using AlicizaX.UI;
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);
serializedObject.ApplyModifiedProperties();
}
}
// 显示布局属性或警告
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);
serializedObject.ApplyModifiedProperties();
}
}
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.LabelField("Item Templates", EditorStyles.boldLabel);
DrawTemplatesList();
DrawDragAndDropArea();
}
void DrawTemplatesList()
{
if (templates == null || !templates.isArray) return;
serializedObject.Update();
for (int i = 0; i < templates.arraySize; i++)
{
DrawTemplateItem(i);
}
}
void DrawTemplateItem(int index)
{
if (index < 0 || index >= templates.arraySize) return;
SerializedProperty item = templates.GetArrayElementAtIndex(index);
EditorGUILayout.BeginHorizontal();
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(item, GUIContent.none, true);
EditorGUI.EndDisabledGroup();
if (GUILayout.Button("×", GUILayout.Width(20), GUILayout.Height(EditorGUIUtility.singleLineHeight)))
{
RemoveTemplateItem(index);
return;
}
}
EditorGUILayout.EndHorizontal();
}
void RemoveTemplateItem(int index)
{
if (templates == null || index < 0 || index >= templates.arraySize) return;
templates.DeleteArrayElementAtIndex(index);
serializedObject.ApplyModifiedProperties();
GUIUtility.ExitGUI();
}
void DrawDragAndDropArea()
{
Rect dropArea = GUILayoutUtility.GetRect(0f, 50f, GUILayout.ExpandWidth(true));
GUI.Box(dropArea, "Drag Template to here", EditorStyles.helpBox);
// 处理拖拽事件
HandleDragAndDrop(dropArea);
}
void HandleDragAndDrop(Rect dropArea)
{
Event evt = Event.current;
switch (evt.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
if (!dropArea.Contains(evt.mousePosition))
return;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
AddTemplatesFromDrag();
evt.Use();
}
break;
}
}
void AddTemplatesFromDrag()
{
foreach (UnityEngine.Object draggedObject in DragAndDrop.objectReferences)
{
if (draggedObject is GameObject gameObject && gameObject.GetComponent<ViewHolder>() != null)
{
AddTemplate(gameObject);
}
else
{
Debug.LogWarning("模板必须为GameObject资源 同时挂载继承ViewHolder脚本!");
}
}
}
void AddTemplate(GameObject templatePrefab)
{
if (templates == null) return;
for (int i = 0; i < templates.arraySize; i++)
{
SerializedProperty existingItem = templates.GetArrayElementAtIndex(i);
var refrence = (ViewHolder)existingItem.objectReferenceValue;
if (refrence.GetType() != templatePrefab.GetType())
{
Debug.LogWarning("列表元素重复项,禁止再次添加!");
return;
}
}
templates.arraySize++;
SerializedProperty newItem = templates.GetArrayElementAtIndex(templates.arraySize - 1);
newItem.objectReferenceValue = templatePrefab;
serializedObject.ApplyModifiedProperties();
}
#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)
{
Direction direction = (Direction)this.direction.enumValueIndex;
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>();
}
}