Merge branch 'editor-list-dragging' of https://github.com/MineBill/FlaxEngine into MineBill-editor-list-dragging

This commit is contained in:
Wojtek Figat
2023-11-07 10:21:57 +01:00
12 changed files with 662 additions and 326 deletions

View File

@@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
// Generic file picker
assetType = ScriptType.Null;
Picker.FileExtension = assetReference.TypeName;
Picker.Validator.FileExtension = assetReference.TypeName;
}
else
{
@@ -85,7 +85,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
Picker.AssetType = assetType;
Picker.Validator.AssetType = assetType;
Picker.Height = height;
Picker.SelectedItemChanged += OnSelectedItemChanged;
}
@@ -95,15 +95,15 @@ namespace FlaxEditor.CustomEditors.Editors
if (_isRefreshing)
return;
if (typeof(AssetItem).IsAssignableFrom(_valueType.Type))
SetValue(Picker.SelectedItem);
SetValue(Picker.Validator.SelectedItem);
else if (_valueType.Type == typeof(Guid))
SetValue(Picker.SelectedID);
SetValue(Picker.Validator.SelectedID);
else if (_valueType.Type == typeof(SceneReference))
SetValue(new SceneReference(Picker.SelectedID));
SetValue(new SceneReference(Picker.Validator.SelectedID));
else if (_valueType.Type == typeof(string))
SetValue(Picker.SelectedPath);
SetValue(Picker.Validator.SelectedPath);
else
SetValue(Picker.SelectedAsset);
SetValue(Picker.Validator.SelectedAsset);
}
/// <inheritdoc />
@@ -115,15 +115,15 @@ namespace FlaxEditor.CustomEditors.Editors
{
_isRefreshing = true;
if (Values[0] is AssetItem assetItem)
Picker.SelectedItem = assetItem;
Picker.Validator.SelectedItem = assetItem;
else if (Values[0] is Guid guid)
Picker.SelectedID = guid;
Picker.Validator.SelectedID = guid;
else if (Values[0] is SceneReference sceneAsset)
Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
else if (Values[0] is string path)
Picker.SelectedPath = path;
Picker.Validator.SelectedPath = path;
else
Picker.SelectedAsset = Values[0] as Asset;
Picker.Validator.SelectedAsset = Values[0] as Asset;
_isRefreshing = false;
}
}

View File

@@ -2,11 +2,17 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Options;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -134,15 +140,44 @@ namespace FlaxEditor.CustomEditors.Editors
overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
spacing = collection.Spacing;
}
var dragArea = layout.CustomContainer<DragAreaControl>();
dragArea.CustomControl.Editor = this;
dragArea.CustomControl.ElementType = ElementType;
// Check for the AssetReferenceAttribute. In JSON assets, it can be used to filter
// which scripts can be dragged over and dropped on this collection editor.
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
if (assetReference != null)
{
if (string.IsNullOrEmpty(assetReference.TypeName))
{
}
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
{
dragArea.CustomControl.ElementType = ScriptType.Null;
dragArea.CustomControl.FileExtension = assetReference.TypeName;
}
else
{
var customType = TypeUtils.GetType(assetReference.TypeName);
if (customType != ScriptType.Null)
dragArea.CustomControl.ElementType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for drag and drop filter.", assetReference.TypeName));
else
dragArea.CustomControl.ElementType = ScriptType.Void;
}
}
// Size
if (_readOnly || (NotNullItems && size == 0))
{
layout.Label("Size", size.ToString());
dragArea.Label("Size", size.ToString());
}
else
{
_size = layout.IntegerValue("Size");
_size = dragArea.IntegerValue("Size");
_size.IntValue.MinValue = 0;
_size.IntValue.MaxValue = ushort.MaxValue;
_size.IntValue.Value = size;
@@ -152,7 +187,7 @@ namespace FlaxEditor.CustomEditors.Editors
// Elements
if (size > 0)
{
var panel = layout.VerticalPanel();
var panel = dragArea.VerticalPanel();
panel.Panel.BackgroundColor = _background;
var elementType = ElementType;
@@ -212,37 +247,33 @@ namespace FlaxEditor.CustomEditors.Editors
// Add/Remove buttons
if (!_readOnly)
{
var area = layout.Space(20);
var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16)
{
Text = "+",
TooltipText = "Add new item",
AnchorPreset = AnchorPresets.TopRight,
Parent = area.ContainerControl,
Enabled = !NotNullItems || size > 0,
};
addButton.Clicked += () =>
{
if (IsSetBlocked)
return;
Resize(Count + 1);
};
var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16)
{
Text = "-",
TooltipText = "Remove last item",
AnchorPreset = AnchorPresets.TopRight,
Parent = area.ContainerControl,
Enabled = size > 0,
};
removeButton.Clicked += () =>
var panel = dragArea.HorizontalPanel();
panel.Panel.Size = new Float2(0, 20);
panel.Panel.Margin = new Margin(2);
var removeButton = panel.Button("-", "Remove last item");
removeButton.Button.Size = new Float2(16, 16);
removeButton.Button.Enabled = size > 0;
removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
removeButton.Button.Clicked += () =>
{
if (IsSetBlocked)
return;
Resize(Count - 1);
};
var addButton = panel.Button("+", "Add new item");
addButton.Button.Size = new Float2(16, 16);
addButton.Button.Enabled = !NotNullItems || size > 0;
addButton.Button.AnchorPreset = AnchorPresets.TopRight;
addButton.Button.Clicked += () =>
{
if (IsSetBlocked)
return;
Resize(Count + 1);
};
}
}
@@ -369,5 +400,241 @@ namespace FlaxEditor.CustomEditors.Editors
}
return base.OnDirty(editor, value, token);
}
private class DragAreaControl : VerticalPanel
{
// private DragHandlers _dragHandlers;
// private DragAssets _dragAssets;
// private DragActors _dragActors;
private DragItems _dragItems;
private DragActors _dragActors;
private DragHandlers _dragHandlers;
private AssetPickerValidator _pickerValidator;
private ScriptType _elementType;
public ScriptType ElementType
{
get => _elementType;
set
{
_pickerValidator = new AssetPickerValidator(value);
_elementType = value;
}
}
public CollectionEditor Editor { get; set; }
public string FileExtension
{
set => _pickerValidator.FileExtension = value;
}
/// <inheritdoc />
public override void Draw()
{
if (_dragHandlers is {HasValidDrag: true})
{
var area = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(area, Color.Orange * 0.5f);
Render2D.DrawRectangle(area, Color.Black);
}
base.Draw();
}
public override void OnDestroy()
{
_pickerValidator.OnDestroy();
}
private bool ValidateActors(ActorNode node)
{
return node.Actor.GetScript(ElementType.Type) || ElementType.Type.IsAssignableTo(typeof(Actor));
}
/// <inheritdoc />
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
var result = base.OnDragEnter(ref location, data);
if (result != DragDropEffect.None)
return result;
if (_dragHandlers == null)
{
_dragItems = new DragItems(_pickerValidator.IsValid);
_dragActors = new DragActors(ValidateActors);
_dragHandlers = new DragHandlers
{
_dragActors,
_dragItems
};
}
return _dragHandlers.OnDragEnter(data);
}
/// <inheritdoc />
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
return result;
return _dragHandlers.Effect;
}
/// <inheritdoc />
public override void OnDragLeave()
{
_dragHandlers.OnDragLeave();
base.OnDragLeave();
}
/// <inheritdoc />
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
var result = base.OnDragDrop(ref location, data);
if (result != DragDropEffect.None)
{
_dragHandlers.OnDragDrop(null);
return result;
}
if (_dragHandlers.HasValidDrag)
{
if (_dragItems.HasValidDrag)
{
var list = Editor.CloneValues();
if (list == null)
{
if (Editor.Values.Type.IsArray)
{
list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
}
else
{
list = Editor.Values.Type.CreateInstance() as IList;
}
}
if (list.IsFixedSize)
{
var oldSize = list.Count;
var newSize = list.Count + _dragItems.Objects.Count;
var type = Editor.Values.Type.GetElementType();
var array = TypeUtils.CreateArrayInstance(type, newSize);
list.CopyTo(array, 0);
for (var i = oldSize; i < newSize; i++)
{
var validator = new AssetPickerValidator
{
FileExtension = _pickerValidator.FileExtension,
AssetType = _pickerValidator.AssetType,
SelectedItem = _dragItems.Objects[i - oldSize],
};
if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
array.SetValue(validator.SelectedItem, i);
else if (ElementType.Type == typeof(Guid))
array.SetValue(validator.SelectedID, i);
else if (ElementType.Type == typeof(SceneReference))
array.SetValue(new SceneReference(validator.SelectedID), i);
else if (ElementType.Type == typeof(string))
array.SetValue(validator.SelectedPath, i);
else
array.SetValue(validator.SelectedAsset, i);
validator.OnDestroy();
}
Editor.SetValue(array);
}
else
{
foreach (var item in _dragItems.Objects)
{
var validator = new AssetPickerValidator
{
FileExtension = _pickerValidator.FileExtension,
AssetType = _pickerValidator.AssetType,
SelectedItem = item,
};
if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
list.Add(validator.SelectedItem);
else if (ElementType.Type == typeof(Guid))
list.Add(validator.SelectedID);
else if (ElementType.Type == typeof(SceneReference))
list.Add(new SceneReference(validator.SelectedID));
else if (ElementType.Type == typeof(string))
list.Add(validator.SelectedPath);
else
list.Add(validator.SelectedAsset);
validator.OnDestroy();
}
Editor.SetValue(list);
}
}
else if (_dragActors.HasValidDrag)
{
var list = Editor.CloneValues();
if (list == null)
{
if (Editor.Values.Type.IsArray)
{
list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
}
else
{
list = Editor.Values.Type.CreateInstance() as IList;
}
}
if (list.IsFixedSize)
{
var oldSize = list.Count;
var newSize = list.Count + _dragActors.Objects.Count;
var type = Editor.Values.Type.GetElementType();
var array = TypeUtils.CreateArrayInstance(type, newSize);
list.CopyTo(array, 0);
for (var i = oldSize; i < newSize; i++)
{
var actor = _dragActors.Objects[i - oldSize].Actor;
if (ElementType.Type.IsAssignableTo(typeof(Actor)))
{
array.SetValue(actor, i);
}
else
{
array.SetValue(actor.GetScript(ElementType.Type), i);
}
}
Editor.SetValue(array);
}
else
{
foreach (var actorNode in _dragActors.Objects)
{
if (ElementType.Type.IsAssignableTo(typeof(Actor)))
{
list.Add(actorNode.Actor);
}
else
{
list.Add(actorNode.Actor.GetScript(ElementType.Type));
}
}
Editor.SetValue(list);
}
}
_dragHandlers.OnDragDrop(null);
}
return result;
}
}
}
}

View File

@@ -72,14 +72,14 @@ namespace FlaxEditor.CustomEditors.Editors
return;
_isRefreshing = true;
var slots = _modelInstance.MaterialSlots;
var material = _materialEditor.Picker.SelectedAsset as MaterialBase;
var material = _materialEditor.Picker.Validator.SelectedAsset as MaterialBase;
var defaultMaterial = GPUDevice.Instance.DefaultMaterial;
var value = (ModelInstanceEntry)Values[0];
var prevMaterial = value.Material;
if (!material)
{
// Fallback to default material
_materialEditor.Picker.SelectedAsset = defaultMaterial;
_materialEditor.Picker.Validator.SelectedAsset = defaultMaterial;
value.Material = defaultMaterial;
}
else if (material == slots[_entryIndex].Material)

View File

@@ -5,6 +5,7 @@ using System.IO;
using FlaxEditor.Content;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -17,189 +18,21 @@ namespace FlaxEditor.GUI
/// <seealso cref="Control" />
/// <seealso cref="IContentItemOwner" />
[HideInEditor]
public class AssetPicker : Control, IContentItemOwner
public class AssetPicker : Control
{
private const float DefaultIconSize = 64;
private const float ButtonsOffset = 2;
private const float ButtonsSize = 12;
private Asset _selected;
private ContentItem _selectedItem;
private ScriptType _type;
private string _fileExtension;
private bool _isMouseDown;
private Float2 _mouseDownPos;
private Float2 _mousePos;
private DragItems _dragOverElement;
/// <summary>
/// Gets or sets the selected item.
/// The asset validator. Used to ensure only appropriate items can be picked.
/// </summary>
public ContentItem SelectedItem
{
get => _selectedItem;
set
{
if (_selectedItem == value)
return;
if (value == null)
{
if (_selected == null && _selectedItem is SceneItem)
{
// Deselect scene reference
_selectedItem.RemoveReference(this);
_selectedItem = null;
_selected = null;
OnSelectedItemChanged();
return;
}
// Deselect
_selectedItem?.RemoveReference(this);
_selectedItem = null;
_selected = null;
OnSelectedItemChanged();
}
else if (value is SceneItem item)
{
if (_selectedItem == item)
return;
if (!IsValid(item))
item = null;
// Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
_selectedItem?.RemoveReference(this);
_selectedItem = item;
_selected = null;
_selectedItem?.AddReference(this);
OnSelectedItemChanged();
}
else if (value is AssetItem assetItem)
{
SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
}
else
{
// Change value
_selectedItem?.RemoveReference(this);
_selectedItem = value;
_selected = null;
OnSelectedItemChanged();
}
}
}
/// <summary>
/// Gets or sets the selected asset identifier.
/// </summary>
public Guid SelectedID
{
get
{
if (_selected != null)
return _selected.ID;
if (_selectedItem is AssetItem assetItem)
return assetItem.ID;
return Guid.Empty;
}
set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
}
/// <summary>
/// Gets or sets the selected content item path.
/// </summary>
public string SelectedPath
{
get
{
string path = _selectedItem?.Path ?? _selected?.Path;
if (path != null)
{
// Convert into path relative to the project (cross-platform)
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
}
return path;
}
set
{
if (string.IsNullOrEmpty(value))
{
SelectedItem = null;
}
else
{
var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
SelectedItem = Editor.Instance.ContentDatabase.Find(path);
}
}
}
/// <summary>
/// Gets or sets the selected asset object.
/// </summary>
public Asset SelectedAsset
{
get => _selected;
set
{
// Check if value won't change
if (value == _selected)
return;
// Find item from content database and check it
var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
if (item != null && !IsValid(item))
item = null;
// Change value
_selectedItem?.RemoveReference(this);
_selectedItem = item;
_selected = value;
_selectedItem?.AddReference(this);
OnSelectedItemChanged();
}
}
/// <summary>
/// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use <see cref="ScriptType.Null"/> for generic file picker.
/// </summary>
public ScriptType AssetType
{
get => _type;
set
{
if (_type != value)
{
_type = value;
// Auto deselect if the current value is invalid
if (_selectedItem != null && !IsValid(_selectedItem))
SelectedItem = null;
}
}
}
/// <summary>
/// Gets or sets the content items extensions filter. Null if unused.
/// </summary>
public string FileExtension
{
get => _fileExtension;
set
{
if (_fileExtension != value)
{
_fileExtension = value;
// Auto deselect if the current value is invalid
if (_selectedItem != null && !IsValid(_selectedItem))
SelectedItem = null;
}
}
}
public AssetPickerValidator Validator { get; }
/// <summary>
/// Occurs when selected item gets changed.
@@ -216,38 +49,6 @@ namespace FlaxEditor.GUI
/// </summary>
public bool CanEdit = true;
private bool IsValid(ContentItem item)
{
if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
return false;
if (CheckValid != null && !CheckValid(item))
return false;
if (_type == ScriptType.Null)
return true;
if (item is AssetItem assetItem)
{
// Faster path for binary items (in-built)
if (assetItem is BinaryAssetItem binaryItem)
return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
// Type filter
var type = TypeUtils.GetType(assetItem.TypeName);
if (_type.IsAssignableFrom(type))
return true;
// Json assets can contain any type of the object defined by the C# type (data oriented design)
if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
return true;
// Special case for scene asset references
if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
return true;
}
return false;
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetPicker"/> class.
/// </summary>
@@ -264,7 +65,8 @@ namespace FlaxEditor.GUI
public AssetPicker(ScriptType assetType, Float2 location)
: base(location, new Float2(DefaultIconSize + ButtonsOffset + ButtonsSize, DefaultIconSize))
{
_type = assetType;
Validator = new AssetPickerValidator(assetType);
Validator.SelectedItemChanged += OnSelectedItemChanged;
_mousePos = Float2.Minimum;
}
@@ -275,10 +77,10 @@ namespace FlaxEditor.GUI
{
// Update tooltip
string tooltip;
if (_selectedItem is AssetItem assetItem)
if (Validator.SelectedItem is AssetItem assetItem)
tooltip = assetItem.NamePath;
else
tooltip = SelectedPath;
tooltip = Validator.SelectedPath;
TooltipText = tooltip;
SelectedItemChanged?.Invoke();
@@ -289,37 +91,13 @@ namespace FlaxEditor.GUI
// Do the drag drop operation if has selected element
if (new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos))
{
if (_selected != null)
DoDragDrop(DragAssets.GetDragData(_selected));
else if (_selectedItem != null)
DoDragDrop(DragItems.GetDragData(_selectedItem));
if (Validator.SelectedAsset != null)
DoDragDrop(DragAssets.GetDragData(Validator.SelectedAsset));
else if (Validator.SelectedItem != null)
DoDragDrop(DragItems.GetDragData(Validator.SelectedItem));
}
}
/// <inheritdoc />
public void OnItemDeleted(ContentItem item)
{
// Deselect item
SelectedItem = null;
}
/// <inheritdoc />
public void OnItemRenamed(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemReimported(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemDispose(ContentItem item)
{
// Deselect item
SelectedItem = null;
}
private Rectangle IconRect => new Rectangle(0, 0, Height, Height);
private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize);
@@ -341,10 +119,10 @@ namespace FlaxEditor.GUI
if (CanEdit)
Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
if (_selectedItem != null)
if (Validator.SelectedItem != null)
{
// Draw item preview
_selectedItem.DrawThumbnail(ref iconRect);
Validator.SelectedItem.DrawThumbnail(ref iconRect);
// Draw buttons
if (CanEdit)
@@ -363,7 +141,7 @@ namespace FlaxEditor.GUI
{
Render2D.DrawText(
style.FontSmall,
_selectedItem.ShortName,
Validator.SelectedItem.ShortName,
new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize),
style.Foreground,
TextAlignment.Near,
@@ -371,7 +149,7 @@ namespace FlaxEditor.GUI
}
}
// Check if has no item but has an asset (eg. virtual asset)
else if (_selected)
else if (Validator.SelectedAsset)
{
// Draw remove button
Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
@@ -380,8 +158,8 @@ namespace FlaxEditor.GUI
float sizeForTextLeft = Width - button1Rect.Right;
if (sizeForTextLeft > 30)
{
var name = _selected.GetType().Name;
if (_selected.IsVirtual)
var name = Validator.SelectedAsset.GetType().Name;
if (Validator.SelectedAsset.IsVirtual)
name += " (virtual)";
Render2D.DrawText(
style.FontSmall,
@@ -407,9 +185,7 @@ namespace FlaxEditor.GUI
/// <inheritdoc />
public override void OnDestroy()
{
_selectedItem?.RemoveReference(this);
_selectedItem = null;
_selected = null;
Validator.OnDestroy();
base.OnDestroy();
}
@@ -463,57 +239,57 @@ namespace FlaxEditor.GUI
// Buttons logic
if (!CanEdit)
{
if (Button1Rect.Contains(location) && _selectedItem != null)
if (Button1Rect.Contains(location) && Validator.SelectedItem != null)
{
// Select asset
Editor.Instance.Windows.ContentWin.Select(_selectedItem);
Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
}
}
else if (Button1Rect.Contains(location))
{
Focus();
if (_type != ScriptType.Null)
if (Validator.AssetType != ScriptType.Null)
{
// Show asset picker popup
var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
SelectedItem = item;
Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (_selected != null)
if (Validator.SelectedAsset != null)
{
var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}
else
{
// Show content item picker popup
var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
SelectedItem = item;
Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (_selectedItem != null)
if (Validator.SelectedItem != null)
{
popup.ScrollToAndHighlightItemByName(_selectedItem.ShortName);
popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName);
}
}
}
else if (_selected != null || _selectedItem != null)
else if (Validator.SelectedAsset != null || Validator.SelectedItem != null)
{
if (Button2Rect.Contains(location) && _selectedItem != null)
if (Button2Rect.Contains(location) && Validator.SelectedItem != null)
{
// Select asset
Editor.Instance.Windows.ContentWin.Select(_selectedItem);
Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
}
else if (Button3Rect.Contains(location))
{
// Deselect asset
Focus();
SelectedItem = null;
Validator.SelectedItem = null;
}
}
}
@@ -540,10 +316,10 @@ namespace FlaxEditor.GUI
{
Focus();
if (_selectedItem != null && IconRect.Contains(location))
if (Validator.SelectedItem != null && IconRect.Contains(location))
{
// Open it
Editor.Instance.ContentEditing.Open(_selectedItem);
Editor.Instance.ContentEditing.Open(Validator.SelectedItem);
}
// Handled
@@ -557,7 +333,7 @@ namespace FlaxEditor.GUI
// Check if drop asset
if (_dragOverElement == null)
_dragOverElement = new DragItems(IsValid);
_dragOverElement = new DragItems(Validator.IsValid);
if (CanEdit && _dragOverElement.OnDragEnter(data))
{
}
@@ -590,7 +366,7 @@ namespace FlaxEditor.GUI
if (CanEdit && _dragOverElement.HasValidDrag)
{
// Select element
SelectedItem = _dragOverElement.Objects[0];
Validator.SelectedItem = _dragOverElement.Objects[0];
}
// Clear cache

View File

@@ -39,7 +39,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (AssetID == value?.ID)
return;
AssetID = value?.ID ?? Guid.Empty;
_picker.SelectedAsset = value;
_picker.Validator.SelectedAsset = value;
OnAssetChanged();
Timeline?.MarkAsEdited();
}
@@ -63,10 +63,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnPickerSelectedItemChanged()
{
if (Asset == (TAsset)_picker.SelectedAsset)
if (Asset == (TAsset)_picker.Validator.SelectedAsset)
return;
using (new TrackUndoBlock(this))
Asset = (TAsset)_picker.SelectedAsset;
Asset = (TAsset)_picker.Validator.SelectedAsset;
}
/// <summary>

View File

@@ -465,7 +465,7 @@ namespace FlaxEditor.Surface.Archetypes
if (selectedIndex != -1)
{
var index = 5 + selectedIndex * 2;
SetValue(index, _animationPicker.SelectedID);
SetValue(index, _animationPicker.Validator.SelectedID);
}
}
@@ -495,7 +495,7 @@ namespace FlaxEditor.Surface.Archetypes
{
if (isValid)
{
_animationPicker.SelectedID = data1;
_animationPicker.Validator.SelectedID = data1;
_animationSpeed.Value = data0.W;
var path = string.Empty;
@@ -505,7 +505,7 @@ namespace FlaxEditor.Surface.Archetypes
}
else
{
_animationPicker.SelectedID = Guid.Empty;
_animationPicker.Validator.SelectedID = Guid.Empty;
_animationSpeed.Value = 1.0f;
}
_animationPicker.Enabled = isValid;

View File

@@ -95,7 +95,7 @@ namespace FlaxEditor.Surface.Archetypes
private void OnAssetPickerSelectedItemChanged()
{
SetValue(0, _assetPicker.SelectedID);
SetValue(0, _assetPicker.Validator.SelectedID);
}
private void TryRestoreConnections(Box box, Box[] prevBoxes, ref NodeElementArchetype arch)
@@ -133,7 +133,7 @@ namespace FlaxEditor.Surface.Archetypes
var prevOutputs = _outputs;
// Extract function signature parameters (inputs and outputs packed)
_asset = LoadSignature(_assetPicker.SelectedID, out var typeNames, out var names);
_asset = LoadSignature(_assetPicker.Validator.SelectedID, out var typeNames, out var names);
if (typeNames != null && names != null)
{
var types = new Type[typeNames.Length];
@@ -174,7 +174,7 @@ namespace FlaxEditor.Surface.Archetypes
_outputs[i] = box;
}
Title = _assetPicker.SelectedItem.ShortName;
Title = _assetPicker.Validator.SelectedItem.ShortName;
}
else
{

View File

@@ -38,13 +38,13 @@ namespace FlaxEditor.Surface.Elements
private void OnNodeValuesChanged()
{
SelectedID = (Guid)ParentNode.Values[Archetype.ValueIndex];
Validator.SelectedID = (Guid)ParentNode.Values[Archetype.ValueIndex];
}
/// <inheritdoc />
protected override void OnSelectedItemChanged()
{
var selectedId = SelectedID;
var selectedId = Validator.SelectedID;
if (ParentNode != null && (Guid)ParentNode.Values[Archetype.ValueIndex] != selectedId)
{
ParentNode.SetValue(Archetype.ValueIndex, selectedId);

View File

@@ -290,7 +290,7 @@ namespace FlaxEditor.Tools.Terrain
var patchCoord = Gizmo.SelectedPatchCoord;
var chunkCoord = Gizmo.SelectedChunkCoord;
var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.SelectedAsset as MaterialBase);
var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.Validator.SelectedAsset as MaterialBase);
action.Do();
CarveTab.Editor.Undo.AddAction(action);
}
@@ -336,12 +336,12 @@ namespace FlaxEditor.Tools.Terrain
_isUpdatingUI = true;
if (terrain.HasPatch(ref patchCoord))
{
_chunkOverrideMaterial.SelectedAsset = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord);
_chunkOverrideMaterial.Validator.SelectedAsset = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord);
_chunkOverrideMaterial.Enabled = true;
}
else
{
_chunkOverrideMaterial.SelectedAsset = null;
_chunkOverrideMaterial.Validator.SelectedAsset = null;
_chunkOverrideMaterial.Enabled = false;
}
_isUpdatingUI = false;

View File

@@ -0,0 +1,293 @@
using System;
using System.IO;
using FlaxEditor.Content;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.Utilities;
namespace FlaxEditor.Utilities;
/// <summary>
/// Manages and converts the selected content item to the appropriate types. Useful for drag operations.
/// </summary>
public class AssetPickerValidator: IContentItemOwner
{
private Asset _selected;
private ContentItem _selectedItem;
private ScriptType _type;
private string _fileExtension;
/// <summary>
/// Gets or sets the selected item.
/// </summary>
public ContentItem SelectedItem
{
get => _selectedItem;
set
{
if (_selectedItem == value)
return;
if (value == null)
{
if (_selected == null && _selectedItem is SceneItem)
{
// Deselect scene reference
_selectedItem.RemoveReference(this);
_selectedItem = null;
_selected = null;
OnSelectedItemChanged();
return;
}
// Deselect
_selectedItem?.RemoveReference(this);
_selectedItem = null;
_selected = null;
OnSelectedItemChanged();
}
else if (value is SceneItem item)
{
if (_selectedItem == item)
return;
if (!IsValid(item))
item = null;
// Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
_selectedItem?.RemoveReference(this);
_selectedItem = item;
_selected = null;
_selectedItem?.AddReference(this);
OnSelectedItemChanged();
}
else if (value is AssetItem assetItem)
{
SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
}
else
{
// Change value
_selectedItem?.RemoveReference(this);
_selectedItem = value;
_selected = null;
OnSelectedItemChanged();
}
}
}
/// <summary>
/// Gets or sets the selected asset identifier.
/// </summary>
public Guid SelectedID
{
get
{
if (_selected != null)
return _selected.ID;
if (_selectedItem is AssetItem assetItem)
return assetItem.ID;
return Guid.Empty;
}
set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
}
/// <summary>
/// Gets or sets the selected content item path.
/// </summary>
public string SelectedPath
{
get
{
string path = _selectedItem?.Path ?? _selected?.Path;
if (path != null)
{
// Convert into path relative to the project (cross-platform)
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
}
return path;
}
set
{
if (string.IsNullOrEmpty(value))
{
SelectedItem = null;
}
else
{
var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
SelectedItem = Editor.Instance.ContentDatabase.Find(path);
}
}
}
/// <summary>
/// Gets or sets the selected asset object.
/// </summary>
public Asset SelectedAsset
{
get => _selected;
set
{
// Check if value won't change
if (value == _selected)
return;
// Find item from content database and check it
var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
if (item != null && !IsValid(item))
item = null;
// Change value
_selectedItem?.RemoveReference(this);
_selectedItem = item;
_selected = value;
_selectedItem?.AddReference(this);
OnSelectedItemChanged();
}
}
/// <summary>
/// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use <see cref="ScriptType.Null"/> for generic file picker.
/// </summary>
public ScriptType AssetType
{
get => _type;
set
{
if (_type != value)
{
_type = value;
// Auto deselect if the current value is invalid
if (_selectedItem != null && !IsValid(_selectedItem))
SelectedItem = null;
}
}
}
/// <summary>
/// Gets or sets the content items extensions filter. Null if unused.
/// </summary>
public string FileExtension
{
get => _fileExtension;
set
{
if (_fileExtension != value)
{
_fileExtension = value;
// Auto deselect if the current value is invalid
if (_selectedItem != null && !IsValid(_selectedItem))
SelectedItem = null;
}
}
}
/// <summary>
/// Occurs when selected item gets changed.
/// </summary>
public event Action SelectedItemChanged;
/// <summary>
/// The custom callback for assets validation. Cane be used to implement a rule for assets to pick.
/// </summary>
public Func<ContentItem, bool> CheckValid;
/// <summary>
/// Returns whether item is valid.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool IsValid(ContentItem item)
{
if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
return false;
if (CheckValid != null && !CheckValid(item))
return false;
if (_type == ScriptType.Null)
return true;
if (item is AssetItem assetItem)
{
// Faster path for binary items (in-built)
if (assetItem is BinaryAssetItem binaryItem)
return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
// Type filter
var type = TypeUtils.GetType(assetItem.TypeName);
if (_type.IsAssignableFrom(type))
return true;
// Json assets can contain any type of the object defined by the C# type (data oriented design)
if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
return true;
// Special case for scene asset references
if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
return true;
}
return false;
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
/// </summary>
public AssetPickerValidator()
: this(new ScriptType(typeof(Asset)))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
/// </summary>
/// <param name="assetType">The assets types that this picker accepts.</param>
public AssetPickerValidator(ScriptType assetType)
{
_type = assetType;
}
/// <summary>
/// Called when selected item gets changed.
/// </summary>
protected virtual void OnSelectedItemChanged()
{
SelectedItemChanged?.Invoke();
}
/// <inheritdoc />
public void OnItemDeleted(ContentItem item)
{
// Deselect item
SelectedItem = null;
}
/// <inheritdoc />
public void OnItemRenamed(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemReimported(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemDispose(ContentItem item)
{
// Deselect item
SelectedItem = null;
}
/// <summary>
/// Call to remove reference from the selected item.
/// </summary>
public void OnDestroy()
{
_selectedItem?.RemoveReference(this);
_selectedItem = null;
_selected = null;
}
}

View File

@@ -46,14 +46,14 @@ namespace FlaxEditor.Windows
if (asset != null)
{
var path = asset.Path;
picker.SelectedAsset = asset;
picker.Validator.SelectedAsset = asset;
Title = System.IO.Path.GetFileNameWithoutExtension(path);
TooltipText = asset.TypeName + '\n' + path;
}
else
{
picker.SelectedID = AssetId;
var assetItem = picker.SelectedItem as AssetItem;
picker.Validator.SelectedID = AssetId;
var assetItem = picker.Validator.SelectedItem as AssetItem;
if (assetItem != null)
{
Title = assetItem.ShortName;

View File

@@ -837,7 +837,7 @@ namespace FlaxEditor.Windows.Assets
sourceAssetPicker.CheckValid = CheckSourceAssetValid;
sourceAssetPicker.SelectedItemChanged += () =>
{
proxy.Setups.Add(sourceAssetPicker.SelectedAsset, new SetupProxy());
proxy.Setups.Add(sourceAssetPicker.Validator.SelectedAsset, new SetupProxy());
proxy.Window.MarkAsEdited();
RebuildLayout();
};
@@ -856,7 +856,7 @@ namespace FlaxEditor.Windows.Assets
// Source asset picker
var sourceAssetPicker = setupGroup.AddPropertyItem("Source Asset").Custom<AssetPicker>().CustomControl;
sourceAssetPicker.SelectedAsset = sourceAsset;
sourceAssetPicker.Validator.SelectedAsset = sourceAsset;
sourceAssetPicker.CanEdit = false;
sourceAssetPicker.Height = 48;
@@ -916,12 +916,12 @@ namespace FlaxEditor.Windows.Assets
{
// Show skeleton asset picker
var sourceSkeletonPicker = setupGroup.AddPropertyItem("Skeleton", "Skinned model that contains a skeleton for this animation retargeting.").Custom<AssetPicker>().CustomControl;
sourceSkeletonPicker.AssetType = new ScriptType(typeof(SkinnedModel));
sourceSkeletonPicker.SelectedAsset = setup.Value.Skeleton;
sourceSkeletonPicker.Validator.AssetType = new ScriptType(typeof(SkinnedModel));
sourceSkeletonPicker.Validator.SelectedAsset = setup.Value.Skeleton;
sourceSkeletonPicker.Height = 48;
sourceSkeletonPicker.SelectedItemChanged += () =>
{
setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.SelectedAsset;
setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.Validator.SelectedAsset;
proxy.Window.MarkAsEdited();
};
}