Skip to content

Instantly share code, notes, and snippets.

@Rene-Damm
Created April 25, 2025 12:22
Show Gist options
  • Save Rene-Damm/f02ca7d4a7f9a7741b0db3d0a701265f to your computer and use it in GitHub Desktop.
Save Rene-Damm/f02ca7d4a7f9a7741b0db3d0a701265f to your computer and use it in GitHub Desktop.
EnumLinkField with [Flags] support.
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