using System; using System.Collections; using UnityEditor; using UnityEngine; namespace Unity.VisualScripting { public class MetadataDictionaryAdaptor : MetadataCollectionAdaptor { public MetadataDictionaryAdaptor(Metadata metadata, Inspector parentDrawer) : base(metadata, parentDrawer) { this.metadata = metadata; metadata.valueChanged += (previousValue) => { if (!metadata.isDictionary) { throw new InvalidOperationException("Metadata for dictionary adaptor is not a dictionary: " + metadata); } if (metadata.value == null) { metadata.value = ConstructDictionary(); } newKeyMetadata?.Unlink(); newValueMetadata?.Unlink(); newKeyMetadata = metadata.Object("newKey", ConstructKey(), metadata.dictionaryKeyType); newValueMetadata = metadata.Object("newValue", ConstructValue(), metadata.dictionaryValueType); }; } public event Action itemAdded; private Metadata newKeyMetadata; private Metadata newValueMetadata; protected Metadata metadata { get; private set; } protected virtual IDictionary ConstructDictionary() { try { return (IDictionary)Activator.CreateInstance(metadata.dictionaryType); } catch (Exception ex) { throw new InvalidOperationException($"Could not create dictionary instance of type '{metadata.dictionaryType}'.", ex); } } protected virtual object ConstructKey() { if (metadata.dictionaryKeyType.IsValueType) { return Activator.CreateInstance(metadata.dictionaryKeyType); } else { return null; } } protected virtual object ConstructValue() { if (metadata.dictionaryValueType.IsValueType) { return Activator.CreateInstance(metadata.dictionaryValueType); } else { return null; } } private const float spaceBetweenKeyAndValue = 5; private const float itemPadding = 2; #region Manipulation public object this[object key] { get { return ((IDictionary)metadata)[key]; } set { metadata.RecordUndo(); ((IDictionary)metadata)[key] = value; } } public override int Count => metadata.Count + 1; public override void Add() { if (!CanAdd()) { return; } var newKey = newKeyMetadata.value; var newValue = newValueMetadata.value; metadata.RecordUndo(); metadata.Add(newKey, newValue); itemAdded?.Invoke(newKey, newValue); parentInspector.SetHeightDirty(); } public override void Clear() { metadata.RecordUndo(); metadata.Clear(); parentInspector.SetHeightDirty(); } public override void Insert(int index) { } public override void Remove(int index) { metadata.RecordUndo(); metadata.Remove(metadata.KeyMetadata(index).value); parentInspector.SetHeightDirty(); } public override void Move(int sourceIndex, int destinationIndex) { } public override void Duplicate(int index) { } protected bool CanAdd() { var newKey = newKeyMetadata.value; if (newKey == null) { EditorUtility.DisplayDialog("New Dictionary Item", "A dictionary key cannot be null.", "OK"); return false; } if (metadata.Contains(newKeyMetadata.value)) { EditorUtility.DisplayDialog("New Dictionary Item", "An item with the same key already exists.", "OK"); return false; } return true; } public override bool CanDrag(int index) { return metadata.isOrderedDictionary && (index != Count - 1); } public override bool CanRemove(int index) { return index != Count - 1; } #endregion #region Drawing public override float GetItemAdaptiveWidth(int index) { // TODO return 100; } public override float GetItemHeight(float width, int index) { if (index == Count - 1) { return GetNewItemHeight(width); } else { return GetItemHeight(metadata.KeyMetadata(index), metadata.ValueMetadata(index), width); } } public override void DrawItem(Rect position, int index) { if (index == Count - 1) { DrawNewItem(position); } else { OnItemGUI(metadata.KeyMetadata(index), metadata.ValueMetadata(index), position, false); } } private float GetNewItemHeight(float width) { var height = 0f; height += EditorGUIUtility.singleLineHeight; height += GetItemHeight(newKeyMetadata, newValueMetadata, width); return height; } private void DrawNewItem(Rect position) { var newLabelPosition = new Rect ( position.x, position.y, position.width, EditorGUIUtility.singleLineHeight ); var newItemPosition = new Rect ( position.x, newLabelPosition.yMax, position.width, GetItemHeight(newKeyMetadata, newValueMetadata, position.width) ); GUI.Label(newLabelPosition, "New Item: "); OnItemGUI(newKeyMetadata, newValueMetadata, newItemPosition, true); } private float GetKeyHeight(Metadata keyMetadata, float keyWidth) { return LudiqGUI.GetInspectorHeight(parentInspector, keyMetadata, keyWidth, GUIContent.none); } private float GetValueHeight(Metadata valueMetadata, float valueWidth) { return LudiqGUI.GetInspectorHeight(parentInspector, valueMetadata, valueWidth, GUIContent.none); } private float GetKeyWidth(float width) { return (width - spaceBetweenKeyAndValue) / 2; } private float GetValueWidth(float width) { return (width - spaceBetweenKeyAndValue) / 2; } private float GetItemHeight(Metadata keyMetadata, Metadata valueMetadata, float width) { return Mathf.Max(GetKeyHeight(keyMetadata, GetKeyWidth(width)), GetValueHeight(valueMetadata, GetValueWidth(width))) + (itemPadding * 2); } private void OnKeyGUI(Metadata keyMetadata, Rect keyPosition) { LudiqGUI.Inspector(keyMetadata, keyPosition, GUIContent.none); } private void OnValueGUI(Metadata valueMetadata, Rect valuePosition) { LudiqGUI.Inspector(valueMetadata, valuePosition, GUIContent.none); } private void OnItemGUI(Metadata keyMetadata, Metadata valueMetadata, Rect position, bool editableKey) { var keyPosition = new Rect ( position.x + itemPadding, position.y + itemPadding, GetKeyWidth(position.width), GetKeyHeight(keyMetadata, GetKeyWidth(position.width)) ); var valuePosition = new Rect ( keyPosition.xMax + spaceBetweenKeyAndValue, position.y + itemPadding, GetValueWidth(position.width), GetValueHeight(valueMetadata, GetValueWidth(position.width)) ); EditorGUI.BeginDisabledGroup(!editableKey); OnKeyGUI(keyMetadata, keyPosition); EditorGUI.EndDisabledGroup(); OnValueGUI(valueMetadata, valuePosition); } #endregion } }