diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 919da4301..526c91d20 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -3,9 +3,9 @@ using System; using System.Collections; using System.Linq; -using FlaxEditor.Content; -using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; +using FlaxEditor.GUI.Input; +using FlaxEditor.Content; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Drag; using FlaxEditor.SceneGraph; @@ -54,8 +54,13 @@ namespace FlaxEditor.CustomEditors.Editors private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor) { + menu.ItemsContainer.RemoveChildren(); + + menu.AddButton("Copy", linkedEditor.Copy); + var paste = menu.AddButton("Paste", linkedEditor.Paste); + paste.Enabled = linkedEditor.CanPaste; + menu.AddSeparator(); - var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked); moveUpButton.Enabled = Index > 0; @@ -65,17 +70,100 @@ namespace FlaxEditor.CustomEditors.Editors menu.AddButton("Remove", OnRemoveClicked); } - private void OnMoveUpClicked(ContextMenuButton button) + private void OnMoveUpClicked() { Editor.Move(Index, Index - 1); } - private void OnMoveDownClicked(ContextMenuButton button) + private void OnMoveDownClicked() { Editor.Move(Index, Index + 1); } - private void OnRemoveClicked(ContextMenuButton button) + private void OnRemoveClicked() + { + Editor.Remove(Index); + } + } + + private class CollectionDropPanel : DropPanel + { + /// + /// The collection editor. + /// + public CollectionEditor Editor; + + /// + /// The index of the item (zero-based). + /// + public int Index { get; private set; } + + /// + /// The linked editor. + /// + public CustomEditor LinkedEditor; + + private bool _canReorder = true; + + public void Setup(CollectionEditor editor, int index, bool canReorder = true) + { + HeaderHeight = 18; + _canReorder = canReorder; + EnableDropDownIcon = true; + var icons = FlaxEditor.Editor.Instance.Icons; + ArrowImageClosed = new SpriteBrush(icons.ArrowRight12); + ArrowImageOpened = new SpriteBrush(icons.ArrowDown12); + HeaderText = $"Element {index}"; + IsClosed = false; + Editor = editor; + Index = index; + Offsets = new Margin(7, 7, 0, 0); + + MouseButtonRightClicked += OnMouseButtonRightClicked; + if (_canReorder) + { + // TODO: Drag drop + } + } + + private void OnMouseButtonRightClicked(DropPanel panel, Float2 location) + { + if (LinkedEditor == null) + return; + var linkedEditor = LinkedEditor; + var menu = new ContextMenu(); + + menu.AddButton("Copy", linkedEditor.Copy); + var paste = menu.AddButton("Paste", linkedEditor.Paste); + paste.Enabled = linkedEditor.CanPaste; + + if (_canReorder) + { + menu.AddSeparator(); + + var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked); + moveUpButton.Enabled = Index > 0; + + var moveDownButton = menu.AddButton("Move down", OnMoveDownClicked); + moveDownButton.Enabled = Index + 1 < Editor.Count; + } + + menu.AddButton("Remove", OnRemoveClicked); + + menu.Show(panel, location); + } + + private void OnMoveUpClicked() + { + Editor.Move(Index, Index - 1); + } + + private void OnMoveDownClicked() + { + Editor.Move(Index, Index + 1); + } + + private void OnRemoveClicked() { Editor.Remove(Index); } @@ -85,13 +173,12 @@ namespace FlaxEditor.CustomEditors.Editors /// Determines if value of collection can be null. /// protected bool NotNullItems; - - private IntegerValueElement _size; - private PropertyNameLabel _sizeLabel; + private IntValueBox _sizeBox; private Color _background; private int _elementsCount; private bool _readOnly; private bool _canReorderItems; + private CollectionAttribute.DisplayType _displayType; /// /// Gets the length of the collection. @@ -124,12 +211,13 @@ namespace FlaxEditor.CustomEditors.Editors _readOnly = false; _canReorderItems = true; _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor; + _displayType = CollectionAttribute.DisplayType.Header; NotNullItems = false; // Try get CollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); Type overrideEditorType = null; - float spacing = 10.0f; + float spacing = 1.0f; var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { @@ -140,6 +228,7 @@ namespace FlaxEditor.CustomEditors.Editors _background = collection.BackgroundColor.Value; overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type; spacing = collection.Spacing; + _displayType = collection.Display; } var dragArea = layout.CustomContainer(); @@ -172,76 +261,77 @@ namespace FlaxEditor.CustomEditors.Editors } // Size - if (_readOnly || (NotNullItems && size == 0)) + if (layout.ContainerControl is DropPanel dropPanel) { - dragArea.Label("Size", size.ToString()); - } - else - { - var sizeProperty = dragArea.AddPropertyItem("Size"); - _sizeLabel = sizeProperty.Labels.Last(); - _size = sizeProperty.IntegerValue(); - _size.IntValue.MinValue = 0; - _size.IntValue.MaxValue = ushort.MaxValue; - _size.IntValue.Value = size; - _size.IntValue.EditEnd += OnSizeChanged; + var height = dropPanel.HeaderHeight - dropPanel.HeaderTextMargin.Height; + var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top; + _sizeBox = new IntValueBox(size) + { + MinValue = 0, + MaxValue = 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 || (NotNullItems && size == 0)) + { + _sizeBox.IsReadOnly = true; + _sizeBox.Enabled = false; + } + else + { + _sizeBox.EditEnd += OnSizeChanged; + } } // Elements if (size > 0) { var panel = dragArea.VerticalPanel(); + panel.Panel.Offsets = new Margin(7, 7, 0, 0); panel.Panel.BackgroundColor = _background; var elementType = ElementType; + bool single = elementType.IsPrimitive || + elementType.Equals(new ScriptType(typeof(string))) || + elementType.IsEnum || + (elementType.GetFields().Length == 1 && elementType.GetProperties().Length == 0) || + (elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) || + elementType.Equals(new ScriptType(typeof(JsonAsset))) || + elementType.Equals(new ScriptType(typeof(SettingsBase))); - // Use separate layout cells for each collection items to improve layout updates for them in separation - var useSharedLayout = elementType.IsPrimitive || elementType.IsEnum; - - if (_canReorderItems) + for (int i = 0; i < size; i++) { - for (int i = 0; i < size; i++) - { - if (i != 0 && spacing > 0f) - { - if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement) - { - if (propertiesListElement.Labels.Count > 0) - { - var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1]; - var margin = label.Margin; - margin.Bottom += spacing; - label.Margin = margin; - } - propertiesListElement.Space(spacing); - } - else - { - panel.Space(spacing); - } - } + // Apply spacing + if (i > 0 && i < size && spacing > 0 && !single) + panel.Space(spacing); - var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - var property = panel.AddPropertyItem(new CollectionItemLabel(this, i)); - var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); - itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); + var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; + if (_displayType == CollectionAttribute.DisplayType.Inline || (collection == null && single) || (_displayType == CollectionAttribute.DisplayType.Default && single)) + { + PropertyNameLabel itemLabel; + if (_canReorderItems) + itemLabel = new CollectionItemLabel(this, i); + else + itemLabel = new PropertyNameLabel("Element " + i); + var property = panel.AddPropertyItem(itemLabel); + var itemLayout = (LayoutElementsContainer)property; + itemLabel.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); } - } - else - { - for (int i = 0; i < size; i++) + else if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single)) { - if (i != 0 && spacing > 0f) - { - if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement) - propertiesListElement.Space(spacing); - else - panel.Space(spacing); - } - - var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - var property = panel.AddPropertyItem("Element " + i); - var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); - itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); + var cdp = panel.CustomContainer(); + cdp.CustomControl.Setup(this, i, _canReorderItems); + var itemLayout = cdp.VerticalPanel(); + cdp.CustomControl.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); } } } @@ -283,8 +373,7 @@ namespace FlaxEditor.CustomEditors.Editors /// protected override void Deinitialize() { - _size = null; - _sizeLabel = null; + _sizeBox = null; base.Deinitialize(); } @@ -311,7 +400,8 @@ namespace FlaxEditor.CustomEditors.Editors { if (IsSetBlocked) return; - Resize(_size.IntValue.Value); + + Resize(_sizeBox.Value); } /// @@ -384,14 +474,14 @@ namespace FlaxEditor.CustomEditors.Editors return; // Update reference/default value indicator - if (_sizeLabel != null) + if (_sizeBox != null) { var color = Color.Transparent; if (Values.HasReferenceValue && Values.ReferenceValue is IList referenceValue && referenceValue.Count != Count) color = FlaxEngine.GUI.Style.Current.BackgroundSelected; else if (Values.HasDefaultValue && Values.DefaultValue is IList defaultValue && defaultValue.Count != Count) color = Color.Yellow * 0.8f; - _sizeLabel.HighlightStripColor = color; + _sizeBox.BorderColor = color; } // Check if collection has been resized (by UI or from external source) diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index 73089ff04..774f9b6b3 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -7,6 +7,7 @@ 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; @@ -149,13 +150,14 @@ namespace FlaxEditor.CustomEditors.Editors } } - private IntegerValueElement _size; + 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. @@ -178,6 +180,7 @@ namespace FlaxEditor.CustomEditors.Editors _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(); @@ -192,20 +195,40 @@ namespace FlaxEditor.CustomEditors.Editors _background = collection.BackgroundColor.Value; overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type; spacing = collection.Spacing; + _displayType = collection.Display; } // Size - if (_readOnly || !_canEditKeys) + if (layout.ContainerControl is DropPanel dropPanel) { - layout.Label("Size", size.ToString()); - } - else - { - _size = layout.IntegerValue("Size"); - _size.IntValue.MinValue = 0; - _size.IntValue.MaxValue = _notNullItems ? size : ushort.MaxValue; - _size.IntValue.Value = size; - _size.IntValue.EditEnd += OnSizeChanged; + 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 @@ -216,29 +239,23 @@ namespace FlaxEditor.CustomEditors.Editors var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType(); var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray(); var valuesType = new ScriptType(valueType); + + bool single = valuesType.IsPrimitive || + valuesType.Equals(new ScriptType(typeof(string))) || + valuesType.IsEnum || + (valuesType.GetFields().Length == 1 && valuesType.GetProperties().Length == 0) || + (valuesType.GetProperties().Length == 1 && valuesType.GetFields().Length == 0) || + valuesType.Equals(new ScriptType(typeof(JsonAsset))) || + valuesType.Equals(new ScriptType(typeof(SettingsBase))); // Use separate layout cells for each collection items to improve layout updates for them in separation var useSharedLayout = valueType.IsPrimitive || valueType.IsEnum; for (int i = 0; i < size; i++) { - if (i != 0 && spacing > 0f) + if (i > 0 && i < size && spacing > 0) { - if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement) - { - if (propertiesListElement.Labels.Count > 0) - { - var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1]; - var margin = label.Margin; - margin.Bottom += spacing; - label.Margin = margin; - } - propertiesListElement.Space(spacing); - } - else - { - panel.Space(spacing); - } + panel.Space(spacing); } var key = keys.ElementAt(i); @@ -310,7 +327,7 @@ namespace FlaxEditor.CustomEditors.Editors if (IsSetBlocked) return; - Resize(_size.IntValue.Value); + Resize(_sizeBox.Value); } /// diff --git a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs index f901b20d9..568a57794 100644 --- a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs @@ -4,6 +4,7 @@ using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.Scripting; using FlaxEngine; +using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.Editors { @@ -13,7 +14,7 @@ namespace FlaxEditor.CustomEditors.Editors [CustomEditor(typeof(ModelInstanceEntry)), DefaultEditor] public sealed class ModelInstanceEntryEditor : GenericEditor { - private GroupElement _group; + private DropPanel _mainPanel; private bool _updateName; private int _entryIndex; private bool _isRefreshing; @@ -25,8 +26,11 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { _updateName = true; - var group = layout.Group("Entry"); - _group = group; + if (layout.ContainerControl.Parent is DropPanel panel) + { + _mainPanel = panel; + _mainPanel.HeaderText = "Entry"; + } if (ParentEditor == null || HasDifferentTypes) return; @@ -60,14 +64,14 @@ namespace FlaxEditor.CustomEditors.Editors var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase); for (var i = 1; i < parentEditorValues.Count; i++) materialValue.Add(_material); - var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue); + var materialEditor = (AssetRefEditor)layout.Property(materialLabel, materialValue); materialEditor.Values.SetDefaultValue(defaultValue); materialEditor.RefreshDefaultValue(); materialEditor.Picker.SelectedItemChanged += OnSelectedMaterialChanged; _materialEditor = materialEditor; } - base.Initialize(group); + base.Initialize(layout); } private void OnSelectedMaterialChanged() @@ -120,7 +124,7 @@ namespace FlaxEditor.CustomEditors.Editors { // Update panel title to match material slot name if (_updateName && - _group != null && + _mainPanel != null && ParentEditor?.ParentEditor != null && ParentEditor.ParentEditor.Values.Count > 0) { @@ -131,7 +135,7 @@ namespace FlaxEditor.CustomEditors.Editors if (slots != null && slots.Length > entryIndex) { _updateName = false; - _group.Panel.HeaderText = "Entry " + slots[entryIndex].Name; + _mainPanel.HeaderText = "Entry " + slots[entryIndex].Name; } } } diff --git a/Source/Engine/Core/Config/LayersAndTagsSettings.cs b/Source/Engine/Core/Config/LayersAndTagsSettings.cs index 008f70504..d5ebbe19b 100644 --- a/Source/Engine/Core/Config/LayersAndTagsSettings.cs +++ b/Source/Engine/Core/Config/LayersAndTagsSettings.cs @@ -18,7 +18,7 @@ namespace FlaxEditor.Content.Settings /// /// The layers names. /// - [EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true)] + [EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true, Display = CollectionAttribute.DisplayType.Inline)] public string[] Layers = new string[32]; /// diff --git a/Source/Engine/Localization/LocalizationSettings.h b/Source/Engine/Localization/LocalizationSettings.h index e2c546c8b..22a4f03e4 100644 --- a/Source/Engine/Localization/LocalizationSettings.h +++ b/Source/Engine/Localization/LocalizationSettings.h @@ -16,7 +16,7 @@ public: /// /// The list of the string localization tables used by the game. /// - API_FIELD() + API_FIELD(Attributes="Collection(Display = CollectionAttribute.DisplayType.Inline)") Array> LocalizedStringTables; /// diff --git a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs index ca8f45d39..cd2e962e6 100644 --- a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs +++ b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs @@ -10,6 +10,32 @@ namespace FlaxEngine [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class)] public sealed class CollectionAttribute : Attribute { + /// + /// The display type for collections. + /// + public enum DisplayType + { + /// + /// Displays the default display type. + /// + Default, + + /// + /// Displays a header. + /// + Header, + + /// + /// Displays inline. + /// + Inline, + } + + /// + /// Gets or sets the display type. + /// + public DisplayType Display; + /// /// Gets or sets whether this collection is read-only. If true, applications using this collection should not allow to add or remove items. ///