473 lines
15 KiB
C#
473 lines
15 KiB
C#
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)
|
|
{
|
|
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>();
|
|
}
|
|
}
|