// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections;
using System.Linq;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
///
/// Default implementation of the inspector used to edit key-value dictionaries.
///
public class DictionaryEditor : CustomEditor
{
///
/// The custom implementation of the dictionary items labels that can be used to remove items or edit keys.
///
///
private class DictionaryItemLabel : PropertyNameLabel
{
private DictionaryEditor _editor;
private object _key;
///
/// Initializes a new instance of the class.
///
/// The editor.
/// The key.
public DictionaryItemLabel(DictionaryEditor editor, object key)
: base(key?.ToString() ?? "")
{
_editor = editor;
_key = key;
SetupContextMenu += OnSetupContextMenu;
}
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
{
if (menu.Items.Any())
menu.AddSeparator();
menu.AddButton("Remove", OnRemoveClicked).Enabled = !_editor._readOnly;
menu.AddButton("Edit", OnEditClicked).Enabled = _editor._canEditKeys;
}
private void OnRemoveClicked(ContextMenuButton button)
{
_editor.Remove(_key);
}
private void OnEditClicked(ContextMenuButton button)
{
var keyType = _editor.Values.Type.GetGenericArguments()[0];
if (keyType == typeof(string) || keyType.IsPrimitive)
{
// Edit as text
var popup = RenamePopup.Show(Parent, Rectangle.Margin(Bounds, Margin), Text, false);
popup.Validate += (renamePopup, value) =>
{
object newKey;
if (keyType.IsPrimitive)
newKey = JsonSerializer.Deserialize(value, keyType);
else
newKey = value;
return !((IDictionary)_editor.Values[0]).Contains(newKey);
};
popup.Renamed += renamePopup =>
{
object newKey;
if (keyType.IsPrimitive)
newKey = JsonSerializer.Deserialize(renamePopup.Text, keyType);
else
newKey = renamePopup.Text;
_editor.ChangeKey(_key, newKey);
_key = newKey;
Text = _key.ToString();
};
}
else if (keyType.IsEnum)
{
// Edit via enum picker
var popup = RenamePopup.Show(Parent, Rectangle.Margin(Bounds, Margin), Text, false);
var picker = new EnumComboBox(keyType)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
Parent = popup,
EnumTypeValue = _key,
};
picker.ValueChanged += () =>
{
popup.Hide();
object newKey = picker.EnumTypeValue;
if (!((IDictionary)_editor.Values[0]).Contains(newKey))
{
_editor.ChangeKey(_key, newKey);
_key = newKey;
Text = _key.ToString();
}
};
}
else
{
// Generic editor
var popup = ContextMenuBase.ShowEmptyMenu(Parent, Rectangle.Margin(Bounds, Margin));
var presenter = new CustomEditorPresenter(null);
presenter.Panel.AnchorPreset = AnchorPresets.StretchAll;
presenter.Panel.IsScrollable = false;
presenter.Panel.Parent = popup;
presenter.Select(_key);
presenter.Modified += () =>
{
popup.Hide();
object newKey = presenter.Selection[0];
_editor.ChangeKey(_key, newKey);
_key = newKey;
Text = _key?.ToString();
};
}
}
///
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _editor._canEditKeys)
{
OnEditClicked(null);
return true;
}
return base.OnMouseDoubleClick(location, button);
}
///
public override void OnDestroy()
{
_editor = null;
_key = null;
base.OnDestroy();
}
}
private IntValueBox _sizeBox;
private Color _background;
private int _elementsCount;
private bool _readOnly;
private bool _notNullItems;
private bool _canEditKeys;
private bool _keyEdited;
private CollectionAttribute.DisplayType _displayType;
///
/// Gets the length of the collection.
///
public int Count => (Values[0] as IDictionary)?.Count ?? 0;
///
public override void Initialize(LayoutElementsContainer layout)
{
// No support for different collections for now
if (HasDifferentValues || HasDifferentTypes)
return;
var type = Values.Type;
var size = Count;
var argTypes = type.GetGenericArguments();
var keyType = argTypes[0];
var valueType = argTypes[1];
_canEditKeys = keyType == typeof(string) || keyType.IsPrimitive || keyType.IsEnum || keyType.IsValueType;
_background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
_readOnly = false;
_notNullItems = false;
_displayType = CollectionAttribute.DisplayType.Header;
// Try get CollectionAttribute for collection editor meta
var attributes = Values.GetAttributes();
Type overrideEditorType = null;
float spacing = 0.0f;
var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
if (collection != null)
{
_canEditKeys &= collection.CanReorderItems;
_readOnly = collection.ReadOnly;
_notNullItems = collection.NotNullItems;
if (collection.BackgroundColor.HasValue)
_background = collection.BackgroundColor.Value;
overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
spacing = collection.Spacing;
_displayType = collection.Display;
}
if (attributes != null && attributes.Any(x => x is ReadOnlyAttribute))
{
_readOnly = true;
_canEditKeys = false;
}
// Size
if (layout.ContainerControl is DropPanel dropPanel)
{
var height = dropPanel.HeaderHeight - dropPanel.HeaderTextMargin.Height;
var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top;
_sizeBox = new IntValueBox(size)
{
MinValue = 0,
MaxValue = _notNullItems ? size : ushort.MaxValue,
AnchorPreset = AnchorPresets.TopRight,
Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height),
Parent = dropPanel,
};
var label = new Label
{
Text = "Size",
AnchorPreset = AnchorPresets.TopRight,
Bounds = new Rectangle(-_sizeBox.Width - 40 - dropPanel.ItemsMargin.Right - 2, y, 40, height),
Parent = dropPanel
};
if (_readOnly || !_canEditKeys)
{
_sizeBox.IsReadOnly = true;
_sizeBox.Enabled = false;
}
else
{
_sizeBox.EditEnd += OnSizeChanged;
}
}
// Elements
if (size > 0)
{
var panel = layout.VerticalPanel();
panel.Panel.BackgroundColor = _background;
var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType