Created
April 25, 2025 12:22
-
-
Save Rene-Damm/f02ca7d4a7f9a7741b0db3d0a701265f to your computer and use it in GitHub Desktop.
EnumLinkField with [Flags] support.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using Unity.Behavior.GraphFramework; | |
using Unity.AppUI.UI; | |
using UnityEngine.UIElements; | |
namespace Unity.Behavior | |
{ | |
/// <summary> | |
/// A link field class specialized for enum types. | |
/// </summary> | |
/// <typeparam name="TValueType">The enum type represented by the link field.</typeparam> | |
public class EnumLinkField<TValueType> : BaseLinkField, INotifyValueChanged<TValueType> where TValueType : Enum | |
{ | |
private readonly Dropdown m_Field; | |
private bool m_IsFlagsEnum; | |
private TValueType[] m_EnumValues; | |
private BehaviorGraphNodeModel BehaviorGraphNode => Model as BehaviorGraphNodeModel; | |
/// <inheritdoc cref="SetValueWithoutNotify"/> | |
public void SetValueWithoutNotify(TValueType newValue) | |
{ | |
if (m_IsFlagsEnum) | |
{ | |
var newValueAsInt = Convert.ToInt32(newValue); | |
var values = new List<int>(); | |
var index = 0; | |
foreach (var value in m_EnumValues) | |
{ | |
var v = Convert.ToInt32(value); | |
if ((newValueAsInt & v) != 0) | |
values.Add(index); | |
++index; | |
} | |
m_Field.SetValueWithoutNotify(values); | |
} | |
else | |
{ | |
for (var i = 0; i < m_EnumValues.Length; ++i) | |
if (m_EnumValues[i].Equals(newValue)) | |
{ | |
m_Field.SetValueWithoutNotify(new[] { i }); | |
break; | |
} | |
} | |
} | |
internal Dropdown Field => m_Field; | |
/// <inheritdoc cref="value"/> | |
public TValueType value | |
{ | |
get | |
{ | |
if (m_IsFlagsEnum) | |
{ | |
var indices = m_Field.value; | |
var finalValue = 0; | |
foreach (var i in indices) | |
finalValue |= Convert.ToInt32(m_EnumValues[i]); | |
return (TValueType)Enum.ToObject(typeof(TValueType), finalValue); | |
} | |
var index = m_Field.value.FirstOrDefault(); | |
return m_EnumValues[index]; | |
} | |
set | |
{ | |
var indices = new List<int>(); | |
if (m_IsFlagsEnum) | |
{ | |
var mask = Convert.ToInt32(value); | |
for (var i = 1; i < m_EnumValues.Length; ++i) | |
{ | |
if ((Convert.ToInt32(m_EnumValues[i]) & mask) != 0) | |
indices.Add(i); | |
} | |
} | |
else | |
{ | |
var index = 0; | |
for (var i = 0; i < m_EnumValues.Length; ++i) | |
if (m_EnumValues[i].Equals(value)) | |
{ | |
index = i; | |
break; | |
} | |
indices.Add(index); | |
} | |
m_Field.SetValueWithoutNotify(indices); | |
using var changeEvent = LinkFieldValueChangeEvent.GetPooled(this, value); | |
SendEvent(changeEvent); | |
} | |
} | |
/// <summary> | |
/// The default constructor for EnumLinkField, using its generic type for initialization. | |
/// </summary> | |
public EnumLinkField() : this(typeof(TValueType)) | |
{ | |
} | |
/// <summary> | |
/// A custom constructor taking any type for initialization. | |
/// </summary> | |
/// <param name="runtimeType">The enum type represented by the link field.</param> | |
public EnumLinkField(Type runtimeType) | |
{ | |
m_IsFlagsEnum = runtimeType.GetCustomAttribute<FlagsAttribute>() != null; | |
Array enumValues = Enum.GetValues(runtimeType); | |
if (!m_IsFlagsEnum) | |
{ | |
m_EnumValues = new TValueType[enumValues.Length]; | |
for (var i = 0; i < enumValues.Length; ++i) | |
m_EnumValues[i] = (TValueType)enumValues.GetValue(i); | |
} | |
else | |
{ | |
var list = new List<TValueType>(); | |
for (var i = 0; i < enumValues.Length; ++i) | |
{ | |
var value = (TValueType)enumValues.GetValue(i); | |
list.Add(value); | |
} | |
m_EnumValues = list.ToArray(); | |
} | |
LinkVariableType = runtimeType; | |
m_Field = new Dropdown { name = "InputField" }; | |
FieldContainer.Clear(); | |
FieldContainer.Add(m_Field); | |
if (m_IsFlagsEnum) | |
m_Field.selectionType = PickerSelectionType.Multiple; | |
m_Field.size = Size.S; | |
m_Field.bindItem = (item, i) => | |
{ | |
var str = Enum.GetName(runtimeType, m_EnumValues[i]); | |
if (string.IsNullOrEmpty(str)) | |
str = "<None>"; | |
item.label = str; | |
}; | |
m_Field.sourceItems = enumValues; | |
SetFieldIcon(runtimeType); | |
m_Field.RegisterValueChangedCallback(OnValueChanged); | |
VisualElement linkFieldSpacer = new VisualElement(); | |
linkFieldSpacer.AddToClassList("LinkButtonSpacer"); | |
linkFieldSpacer.style.position = Position.Relative; | |
linkFieldSpacer.style.visibility = Visibility.Hidden; | |
m_Field.Q<VisualElement>("appui-picker__trailingcontainer").Add(linkFieldSpacer); | |
} | |
private void OnValueChanged(ChangeEvent<IEnumerable<int>> evt) | |
{ | |
using LinkFieldValueChangeEvent changeEvent = LinkFieldValueChangeEvent.GetPooled(this, value); | |
SendEvent(changeEvent); | |
} | |
internal override void UpdateValue(IVariableLink field) | |
{ | |
if (field.Value == null) | |
{ | |
SetValueWithoutNotify(default); | |
} | |
else | |
{ | |
SetValueWithoutNotify((TValueType)field.Value); | |
} | |
base.UpdateValue(field); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment