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

View File

@@ -2,11 +2,17 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI; using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Options;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
@@ -134,15 +140,44 @@ namespace FlaxEditor.CustomEditors.Editors
overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type; overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
spacing = collection.Spacing; 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 // Size
if (_readOnly || (NotNullItems && size == 0)) if (_readOnly || (NotNullItems && size == 0))
{ {
layout.Label("Size", size.ToString()); dragArea.Label("Size", size.ToString());
} }
else else
{ {
_size = layout.IntegerValue("Size"); _size = dragArea.IntegerValue("Size");
_size.IntValue.MinValue = 0; _size.IntValue.MinValue = 0;
_size.IntValue.MaxValue = ushort.MaxValue; _size.IntValue.MaxValue = ushort.MaxValue;
_size.IntValue.Value = size; _size.IntValue.Value = size;
@@ -152,7 +187,7 @@ namespace FlaxEditor.CustomEditors.Editors
// Elements // Elements
if (size > 0) if (size > 0)
{ {
var panel = layout.VerticalPanel(); var panel = dragArea.VerticalPanel();
panel.Panel.BackgroundColor = _background; panel.Panel.BackgroundColor = _background;
var elementType = ElementType; var elementType = ElementType;
@@ -212,37 +247,33 @@ namespace FlaxEditor.CustomEditors.Editors
// Add/Remove buttons // Add/Remove buttons
if (!_readOnly) if (!_readOnly)
{ {
var area = layout.Space(20); var panel = dragArea.HorizontalPanel();
var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16) panel.Panel.Size = new Float2(0, 20);
{ panel.Panel.Margin = new Margin(2);
Text = "+",
TooltipText = "Add new item", var removeButton = panel.Button("-", "Remove last item");
AnchorPreset = AnchorPresets.TopRight, removeButton.Button.Size = new Float2(16, 16);
Parent = area.ContainerControl, removeButton.Button.Enabled = size > 0;
Enabled = !NotNullItems || size > 0, removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
}; removeButton.Button.Clicked += () =>
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 += () =>
{ {
if (IsSetBlocked) if (IsSetBlocked)
return; return;
Resize(Count - 1); 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); 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; return;
_isRefreshing = true; _isRefreshing = true;
var slots = _modelInstance.MaterialSlots; 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 defaultMaterial = GPUDevice.Instance.DefaultMaterial;
var value = (ModelInstanceEntry)Values[0]; var value = (ModelInstanceEntry)Values[0];
var prevMaterial = value.Material; var prevMaterial = value.Material;
if (!material) if (!material)
{ {
// Fallback to default material // Fallback to default material
_materialEditor.Picker.SelectedAsset = defaultMaterial; _materialEditor.Picker.Validator.SelectedAsset = defaultMaterial;
value.Material = defaultMaterial; value.Material = defaultMaterial;
} }
else if (material == slots[_entryIndex].Material) else if (material == slots[_entryIndex].Material)

View File

@@ -5,6 +5,7 @@ using System.IO;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.GUI.Drag; using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
@@ -17,189 +18,21 @@ namespace FlaxEditor.GUI
/// <seealso cref="Control" /> /// <seealso cref="Control" />
/// <seealso cref="IContentItemOwner" /> /// <seealso cref="IContentItemOwner" />
[HideInEditor] [HideInEditor]
public class AssetPicker : Control, IContentItemOwner public class AssetPicker : Control
{ {
private const float DefaultIconSize = 64; private const float DefaultIconSize = 64;
private const float ButtonsOffset = 2; private const float ButtonsOffset = 2;
private const float ButtonsSize = 12; private const float ButtonsSize = 12;
private Asset _selected;
private ContentItem _selectedItem;
private ScriptType _type;
private string _fileExtension;
private bool _isMouseDown; private bool _isMouseDown;
private Float2 _mouseDownPos; private Float2 _mouseDownPos;
private Float2 _mousePos; private Float2 _mousePos;
private DragItems _dragOverElement; private DragItems _dragOverElement;
/// <summary> /// <summary>
/// Gets or sets the selected item. /// The asset validator. Used to ensure only appropriate items can be picked.
/// </summary> /// </summary>
public ContentItem SelectedItem public AssetPickerValidator Validator { get; }
{
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> /// <summary>
/// Occurs when selected item gets changed. /// Occurs when selected item gets changed.
@@ -216,38 +49,6 @@ namespace FlaxEditor.GUI
/// </summary> /// </summary>
public bool CanEdit = true; 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> /// <summary>
/// Initializes a new instance of the <see cref="AssetPicker"/> class. /// Initializes a new instance of the <see cref="AssetPicker"/> class.
/// </summary> /// </summary>
@@ -264,7 +65,8 @@ namespace FlaxEditor.GUI
public AssetPicker(ScriptType assetType, Float2 location) public AssetPicker(ScriptType assetType, Float2 location)
: base(location, new Float2(DefaultIconSize + ButtonsOffset + ButtonsSize, DefaultIconSize)) : base(location, new Float2(DefaultIconSize + ButtonsOffset + ButtonsSize, DefaultIconSize))
{ {
_type = assetType; Validator = new AssetPickerValidator(assetType);
Validator.SelectedItemChanged += OnSelectedItemChanged;
_mousePos = Float2.Minimum; _mousePos = Float2.Minimum;
} }
@@ -275,10 +77,10 @@ namespace FlaxEditor.GUI
{ {
// Update tooltip // Update tooltip
string tooltip; string tooltip;
if (_selectedItem is AssetItem assetItem) if (Validator.SelectedItem is AssetItem assetItem)
tooltip = assetItem.NamePath; tooltip = assetItem.NamePath;
else else
tooltip = SelectedPath; tooltip = Validator.SelectedPath;
TooltipText = tooltip; TooltipText = tooltip;
SelectedItemChanged?.Invoke(); SelectedItemChanged?.Invoke();
@@ -289,37 +91,13 @@ namespace FlaxEditor.GUI
// Do the drag drop operation if has selected element // Do the drag drop operation if has selected element
if (new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos)) if (new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos))
{ {
if (_selected != null) if (Validator.SelectedAsset != null)
DoDragDrop(DragAssets.GetDragData(_selected)); DoDragDrop(DragAssets.GetDragData(Validator.SelectedAsset));
else if (_selectedItem != null) else if (Validator.SelectedItem != null)
DoDragDrop(DragItems.GetDragData(_selectedItem)); 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 IconRect => new Rectangle(0, 0, Height, Height);
private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize); private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize);
@@ -341,10 +119,10 @@ namespace FlaxEditor.GUI
if (CanEdit) if (CanEdit)
Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey); Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
if (_selectedItem != null) if (Validator.SelectedItem != null)
{ {
// Draw item preview // Draw item preview
_selectedItem.DrawThumbnail(ref iconRect); Validator.SelectedItem.DrawThumbnail(ref iconRect);
// Draw buttons // Draw buttons
if (CanEdit) if (CanEdit)
@@ -363,7 +141,7 @@ namespace FlaxEditor.GUI
{ {
Render2D.DrawText( Render2D.DrawText(
style.FontSmall, style.FontSmall,
_selectedItem.ShortName, Validator.SelectedItem.ShortName,
new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize), new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize),
style.Foreground, style.Foreground,
TextAlignment.Near, TextAlignment.Near,
@@ -371,7 +149,7 @@ namespace FlaxEditor.GUI
} }
} }
// Check if has no item but has an asset (eg. virtual asset) // Check if has no item but has an asset (eg. virtual asset)
else if (_selected) else if (Validator.SelectedAsset)
{ {
// Draw remove button // Draw remove button
Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey); Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
@@ -380,8 +158,8 @@ namespace FlaxEditor.GUI
float sizeForTextLeft = Width - button1Rect.Right; float sizeForTextLeft = Width - button1Rect.Right;
if (sizeForTextLeft > 30) if (sizeForTextLeft > 30)
{ {
var name = _selected.GetType().Name; var name = Validator.SelectedAsset.GetType().Name;
if (_selected.IsVirtual) if (Validator.SelectedAsset.IsVirtual)
name += " (virtual)"; name += " (virtual)";
Render2D.DrawText( Render2D.DrawText(
style.FontSmall, style.FontSmall,
@@ -407,9 +185,7 @@ namespace FlaxEditor.GUI
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {
_selectedItem?.RemoveReference(this); Validator.OnDestroy();
_selectedItem = null;
_selected = null;
base.OnDestroy(); base.OnDestroy();
} }
@@ -463,57 +239,57 @@ namespace FlaxEditor.GUI
// Buttons logic // Buttons logic
if (!CanEdit) if (!CanEdit)
{ {
if (Button1Rect.Contains(location) && _selectedItem != null) if (Button1Rect.Contains(location) && Validator.SelectedItem != null)
{ {
// Select asset // Select asset
Editor.Instance.Windows.ContentWin.Select(_selectedItem); Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
} }
} }
else if (Button1Rect.Contains(location)) else if (Button1Rect.Contains(location))
{ {
Focus(); Focus();
if (_type != ScriptType.Null) if (Validator.AssetType != ScriptType.Null)
{ {
// Show asset picker popup // 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(); RootWindow.Focus();
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); popup.ScrollToAndHighlightItemByName(selectedAssetName);
} }
} }
else else
{ {
// Show content item picker popup // 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(); RootWindow.Focus();
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 // Select asset
Editor.Instance.Windows.ContentWin.Select(_selectedItem); Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
} }
else if (Button3Rect.Contains(location)) else if (Button3Rect.Contains(location))
{ {
// Deselect asset // Deselect asset
Focus(); Focus();
SelectedItem = null; Validator.SelectedItem = null;
} }
} }
} }
@@ -540,10 +316,10 @@ namespace FlaxEditor.GUI
{ {
Focus(); Focus();
if (_selectedItem != null && IconRect.Contains(location)) if (Validator.SelectedItem != null && IconRect.Contains(location))
{ {
// Open it // Open it
Editor.Instance.ContentEditing.Open(_selectedItem); Editor.Instance.ContentEditing.Open(Validator.SelectedItem);
} }
// Handled // Handled
@@ -557,7 +333,7 @@ namespace FlaxEditor.GUI
// Check if drop asset // Check if drop asset
if (_dragOverElement == null) if (_dragOverElement == null)
_dragOverElement = new DragItems(IsValid); _dragOverElement = new DragItems(Validator.IsValid);
if (CanEdit && _dragOverElement.OnDragEnter(data)) if (CanEdit && _dragOverElement.OnDragEnter(data))
{ {
} }
@@ -590,7 +366,7 @@ namespace FlaxEditor.GUI
if (CanEdit && _dragOverElement.HasValidDrag) if (CanEdit && _dragOverElement.HasValidDrag)
{ {
// Select element // Select element
SelectedItem = _dragOverElement.Objects[0]; Validator.SelectedItem = _dragOverElement.Objects[0];
} }
// Clear cache // Clear cache

View File

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

View File

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

View File

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

View File

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

View File

@@ -290,7 +290,7 @@ namespace FlaxEditor.Tools.Terrain
var patchCoord = Gizmo.SelectedPatchCoord; var patchCoord = Gizmo.SelectedPatchCoord;
var chunkCoord = Gizmo.SelectedChunkCoord; 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(); action.Do();
CarveTab.Editor.Undo.AddAction(action); CarveTab.Editor.Undo.AddAction(action);
} }
@@ -336,12 +336,12 @@ namespace FlaxEditor.Tools.Terrain
_isUpdatingUI = true; _isUpdatingUI = true;
if (terrain.HasPatch(ref patchCoord)) 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; _chunkOverrideMaterial.Enabled = true;
} }
else else
{ {
_chunkOverrideMaterial.SelectedAsset = null; _chunkOverrideMaterial.Validator.SelectedAsset = null;
_chunkOverrideMaterial.Enabled = false; _chunkOverrideMaterial.Enabled = false;
} }
_isUpdatingUI = 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) if (asset != null)
{ {
var path = asset.Path; var path = asset.Path;
picker.SelectedAsset = asset; picker.Validator.SelectedAsset = asset;
Title = System.IO.Path.GetFileNameWithoutExtension(path); Title = System.IO.Path.GetFileNameWithoutExtension(path);
TooltipText = asset.TypeName + '\n' + path; TooltipText = asset.TypeName + '\n' + path;
} }
else else
{ {
picker.SelectedID = AssetId; picker.Validator.SelectedID = AssetId;
var assetItem = picker.SelectedItem as AssetItem; var assetItem = picker.Validator.SelectedItem as AssetItem;
if (assetItem != null) if (assetItem != null)
{ {
Title = assetItem.ShortName; Title = assetItem.ShortName;

View File

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