Skip to content

Commit

Permalink
Merge pull request #44 from AnnulusGames/add-collection-callbacks
Browse files Browse the repository at this point in the history
Add: OnListViewChangedAttribute
  • Loading branch information
AnnulusGames authored Feb 21, 2024
2 parents d874e72 + 5f62a11 commit 02849c2
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 26 deletions.
41 changes: 17 additions & 24 deletions Alchemy/Assets/Alchemy/Editor/Elements/PropertyListView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,26 @@ public PropertyListView(SerializedProperty property, int depth)
{
Assert.IsTrue(property.isArray);

var settings = property.GetAttribute<ListViewSettingsAttribute>(true);
var parentObj = property.GetDeclaredObject();
var events = property.GetAttribute<OnListViewChangedAttribute>(true);

var listView = new ListView
var listView = GUIHelper.CreateListViewFromFieldInfo(parentObj, property.GetFieldInfo());
listView.headerTitle = ObjectNames.NicifyVariableName(property.displayName);
listView.bindItem = (element, index) =>
{
reorderable = settings == null ? true : settings.Reorderable,
reorderMode = settings == null ? ListViewReorderMode.Animated : settings.ReorderMode,
showBorder = settings == null ? true : settings.ShowBorder,
showFoldoutHeader = settings == null ? true : settings.ShowFoldoutHeader,
showBoundCollectionSize = settings == null ? true : (settings.ShowFoldoutHeader && settings.ShowBoundCollectionSize),
selectionType = settings == null ? SelectionType.Multiple : settings.SelectionType,
headerTitle = ObjectNames.NicifyVariableName(property.displayName),
showAddRemoveFooter = settings == null ? true : settings.ShowAddRemoveFooter,
fixedItemHeight = 20f,
virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight,
showAlternatingRowBackgrounds = settings == null ? AlternatingRowBackground.None : settings.ShowAlternatingRowBackgrounds,
bindItem = (element, index) =>
var arrayElement = property.GetArrayElementAtIndex(index);
var e = new AlchemyPropertyField(arrayElement, property.GetPropertyType(true), depth + 1, true);
element.Add(e);
element.Bind(arrayElement.serializedObject);
e.TrackPropertyValue(arrayElement, x =>
{
var arrayElement = property.GetArrayElementAtIndex(index);
var e = new AlchemyPropertyField(arrayElement, property.GetPropertyType(true), depth + 1, true);
element.Add(e);
element.Bind(arrayElement.serializedObject);
},
unbindItem = (element, index) =>
{
element.Clear();
element.Unbind();
}
ReflectionHelper.Invoke(parentObj, events.OnItemChanged, new object[] { index, x.GetValue<object>() });
});
};
listView.unbindItem = (element, index) =>
{
element.Clear();
element.Unbind();
};

var label = listView.Q<Label>();
Expand Down
63 changes: 62 additions & 1 deletion Alchemy/Assets/Alchemy/Editor/Internal/GUIHelper.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System.Reflection;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEngine.Assertions;
using Alchemy.Inspector;
using System;

namespace Alchemy.Editor
{
Expand Down Expand Up @@ -51,6 +52,66 @@ public static ListView CreateDefaultListView(string label)
};
}

public static ListView CreateListViewFromFieldInfo(object target, FieldInfo fieldInfo)
{
var settings = fieldInfo.GetCustomAttribute<ListViewSettingsAttribute>();
var listView = new ListView
{
reorderable = settings == null ? true : settings.Reorderable,
reorderMode = settings == null ? ListViewReorderMode.Animated : settings.ReorderMode,
showBorder = settings == null ? true : settings.ShowBorder,
showFoldoutHeader = settings == null ? true : settings.ShowFoldoutHeader,
showBoundCollectionSize = settings == null ? true : (settings.ShowFoldoutHeader && settings.ShowBoundCollectionSize),
selectionType = settings == null ? SelectionType.Multiple : settings.SelectionType,
showAddRemoveFooter = settings == null ? true : settings.ShowAddRemoveFooter,
fixedItemHeight = 20f,
virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight,
showAlternatingRowBackgrounds = settings == null ? AlternatingRowBackground.None : settings.ShowAlternatingRowBackgrounds,
};

var events = fieldInfo.GetCustomAttribute<OnListViewChangedAttribute>();
if (events != null)
{
listView.itemsAdded += indices =>
{
if (events.OnItemsAdded == null) return;
ReflectionHelper.Invoke(target, events.OnItemsAdded, new object[] { indices });
};
listView.itemsRemoved += indices =>
{
if (events.OnItemsRemoved == null) return;
ReflectionHelper.Invoke(target, events.OnItemsRemoved, new object[] { indices });
};
listView.itemsChosen += items =>
{
if (events.OnItemsChosen == null) return;
ReflectionHelper.Invoke(target, events.OnItemsChosen, new object[] { items });
};
listView.itemIndexChanged += (before, after) =>
{
if (events.OnItemIndexChanged == null) return;
ReflectionHelper.Invoke(target, events.OnItemIndexChanged, new object[] { before, after });
};
listView.selectionChanged += items =>
{
if (events.OnSelectionChanged == null) return;
ReflectionHelper.Invoke(target, events.OnSelectionChanged, new object[] { items });
};
listView.selectedIndicesChanged += indices =>
{
if (events.OnSelectedIndicesChanged== null) return;
ReflectionHelper.Invoke(target, events.OnSelectedIndicesChanged, new object[] { indices });
};
listView.itemsSourceChanged += () =>
{
if (events.OnItemsSourceChanged == null) return;
ReflectionHelper.Invoke(target, events.OnItemsSourceChanged, null);
};
}

return listView;
}

public static PropertyField CreateObjectPropertyField(SerializedProperty property, Type type)
{
Assert.IsTrue(property.propertyType == SerializedPropertyType.ObjectReference);
Expand Down
2 changes: 1 addition & 1 deletion Alchemy/Assets/Alchemy/Editor/Internal/ReflectionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public static object Invoke(object target, string name, params object[] paramete
{
if (target == null) return false;
Type type = target.GetType();
BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;

while (type != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,31 @@ public static object GetParentObject(this SerializedProperty property)
return obj;
}


public static object GetDeclaredObject(this SerializedProperty property)
{
if (property == null) return null;

var path = property.propertyPath.Replace(".Array.data[", "[");
object obj = property.serializedObject.targetObject;
var elements = path.Split('.');
for (int i = 0; i < elements.Length - 1; i++)
{
var element = elements[i];
if (element.Contains("["))
{
var elementName = element[..element.IndexOf("[")];
var index = Convert.ToInt32(element[element.IndexOf("[")..].Replace("[", "").Replace("]", ""));
obj = ReflectionHelper.GetValue(obj, elementName, index);
}
else
{
obj = ReflectionHelper.GetValue(obj, element);
}
}
return obj;
}

static T GetFieldOrPropertyValue<T>(string fieldName, object obj, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
var field = obj.GetType().GetField(fieldName, bindings);
Expand Down
13 changes: 13 additions & 0 deletions Alchemy/Assets/Alchemy/Runtime/Inspector/InspectorAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,17 @@ public sealed class ListViewSettingsAttribute : Attribute
public bool Reorderable { get; set; } = true;
public ListViewReorderMode ReorderMode { get; set; } = ListViewReorderMode.Animated;
}

[AttributeUsage(AttributeTargets.Field)]
public sealed class OnListViewChangedAttribute : Attribute
{
public string OnItemChanged { get; set; }
public string OnItemIndexChanged { get; set; }
public string OnItemsAdded { get; set; }
public string OnItemsRemoved { get; set; }
public string OnItemsChosen { get; set; }
public string OnItemsSourceChanged { get; set; }
public string OnSelectionChanged { get; set; }
public string OnSelectedIndicesChanged { get; set; }
}
}
40 changes: 40 additions & 0 deletions Alchemy/Assets/Tests/OnListViewChangedTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using UnityEngine;
using Alchemy.Inspector;

public class OnListViewChangedTest : MonoBehaviour
{
[OnListViewChanged(
OnItemChanged = nameof(OnItemChanged),
OnItemsAdded = nameof(OnItemsAdded),
OnItemsRemoved = nameof(OnItemsRemoved),
OnSelectedIndicesChanged = nameof(OnSelectedIndicesChanged),
OnItemIndexChanged = nameof(OnItemIndexChanged))
]
public int[] array;

void OnItemChanged(int index, int item)
{
Debug.Log($"Changed: [{index}] -> {item}");
}

void OnItemsAdded(IEnumerable<int> indices)
{
Debug.Log($"Added: [{string.Join(',', indices)}]");
}

void OnItemsRemoved(IEnumerable<int> indices)
{
Debug.Log($"Removed: [{string.Join(',', indices)}]");
}

void OnSelectedIndicesChanged(IEnumerable<int> indices)
{
Debug.Log($"Selected: [{string.Join(',', indices)}]");
}

void OnItemIndexChanged(int before, int after)
{
Debug.Log($"Index Changed: [{before} -> {after}]");
}
}
11 changes: 11 additions & 0 deletions Alchemy/Assets/Tests/OnListViewChangedTest.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions docs/articles/en/attributes/on-list-view-changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# On List View Changed Attribute

Detects changes in collections and invokes methods accordingly. Refer to Unity's [ListView documentation](https://docs.unity3d.com/ScriptReference/UIElements.ListView.html) for details on each event.

> [!WARNING]
> Ensure that the types of arguments for each event exactly match the arguments listed below (or the ListView event arguments). Failure to match will result in errors and the method won't execute.
```cs
[OnListViewChanged(
OnItemChanged = nameof(OnItemChanged),
OnItemsAdded = nameof(OnItemsAdded),
OnItemsRemoved = nameof(OnItemsRemoved),
OnSelectedIndicesChanged = nameof(OnSelectedIndicesChanged),
OnItemIndexChanged = nameof(OnItemIndexChanged))
]
public float[] array;

void OnItemChanged(int index, float item)
{
Debug.Log($"Changed: [{index}] -> {item}");
}

void OnItemsAdded(IEnumerable<int> indices)
{
Debug.Log($"Added: [{string.Join(',', indices)}]");
}

void OnItemsRemoved(IEnumerable<int> indices)
{
Debug.Log($"Removed: [{string.Join(',', indices)}]");
}

void OnSelectedIndicesChanged(IEnumerable<int> indices)
{
Debug.Log($"Selected: [{string.Join(',', indices)}]");
}

void OnItemIndexChanged(int before, int after)
{
Debug.Log($"Index Changed: [{before} -> {after}]");
}
```

| Parameter | Description |
| - | - |
| OnItemChanged | Name of the method called when an item's value changes `(int index, T value)` |
| OnItemIndexChanged | Name of the method called when an item's index changes `(int before, int after)` |
| OnItemsAdded | Name of the method called when items are added `(IEnumerable<int> indices)` |
| OnItemsRemoved | Name of the method called when items are removed `(IEnumerable<int> indices)` |
| OnItemsChosen | Name of the method called when items are chosen by pressing Enter or double-clicking `(IEnumerable<object> items)` |
| OnItemsSourceChanged | Name of the method called when the original collection changes, such as its count `(no arguments)` |
| OnSelectionChanged | Name of the method called when the selected items change `(IEnumerable<object> items)` |
| OnSelectedIndicesChanged | Name of the method called when the selected indices change `(IEnumerable<int> indices)` |
2 changes: 2 additions & 0 deletions docs/articles/en/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
href: attributes/on-inspector-disable.md
- name: On Inspector Enable
href: attributes/on-inspector-enable.md
- name: On List View Changed
href: attributes/on-list-view-changed.md
- name: On Value Changed
href: attributes/on-value-changed.md

Expand Down
53 changes: 53 additions & 0 deletions docs/articles/ja/attributes/on-list-view-changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# On List View Changed Attribute

コレクションの変更を検知してメソッドを呼び出します。各イベントの詳細はUnityの[ListViewのドキュメント](https://docs.unity3d.com/ScriptReference/UIElements.ListView.html)を参照してください。

> [!WARNING]
> 各イベントの引数の型が、下の表に示した引数(またはListViewのイベントの引数)と完全に一致していることを確認してください。一致しない場合、メソッドを実行できずエラーが発生します。
```cs
[OnListViewChanged(
OnItemChanged = nameof(OnItemChanged),
OnItemsAdded = nameof(OnItemsAdded),
OnItemsRemoved = nameof(OnItemsRemoved),
OnSelectedIndicesChanged = nameof(OnSelectedIndicesChanged),
OnItemIndexChanged = nameof(OnItemIndexChanged))
]
public float[] array;

void OnItemChanged(int index, float item)
{
Debug.Log($"Changed: [{index}] -> {item}");
}

void OnItemsAdded(IEnumerable<int> indices)
{
Debug.Log($"Added: [{string.Join(',', indices)}]");
}

void OnItemsRemoved(IEnumerable<int> indices)
{
Debug.Log($"Removed: [{string.Join(',', indices)}]");
}

void OnSelectedIndicesChanged(IEnumerable<int> indices)
{
Debug.Log($"Selected: [{string.Join(',', indices)}]");
}

void OnItemIndexChanged(int before, int after)
{
Debug.Log($"Index Changed: [{before} -> {after}]");
}
```

| パラメータ | 説明 |
| - | - |
| OnItemChanged | 要素の値を変更した際に呼ばれるメソッドの名前 `(int index, T value)` |
| OnItemIndexChanged | 要素のindexが変更された際に呼ばれるメソッドの名前 `(int before, int after)` |
| OnItemsAdded | 要素が追加された際に呼ばれるメソッドの名前 `(IEnumerable<int> indices)` |
| OnItemsRemoved | 要素が削除された際に呼ばれるメソッドの名前 `(IEnumerable<int> indices)` |
| OnItemsChosen | 要素がEnterキーやダブルクリックで選択された際に呼ばれるメソッドの名前 `(IEnumerable<object> items)` |
| OnItemsSourceChanged | 要素の個数など、元のコレクションが変更された際に呼ばれるメソッドの名前 `(引数なし)` |
| OnSelectionChanged | 選択中の要素が変更された際に呼ばれるメソッドの名前 `(IEnumerable<object> items)` |
| OnSelectedIndicesChanged | 選択中のindexが変更された際に呼ばれるメソッドの名前 `(IEnumerable<int> indices)` |
2 changes: 2 additions & 0 deletions docs/articles/ja/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
href: attributes/on-inspector-disable.md
- name: On Inspector Enable
href: attributes/on-inspector-enable.md
- name: On List View Changed
href: attributes/on-list-view-changed.md
- name: On Value Changed
href: attributes/on-value-changed.md

Expand Down

0 comments on commit 02849c2

Please sign in to comment.