Files
FlaxEngine/Source/Editor/GUI/AssetPicker.cs
2023-07-23 15:20:57 -05:00

603 lines
20 KiB
C#

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.IO;
using FlaxEditor.Content;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
namespace FlaxEditor.GUI
{
/// <summary>
/// Assets picking control.
/// </summary>
/// <seealso cref="Control" />
/// <seealso cref="IContentItemOwner" />
[HideInEditor]
public class AssetPicker : Control, IContentItemOwner
{
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.
/// </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>
/// False if changing selected item is disabled.
/// </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>
public AssetPicker()
: this(new ScriptType(typeof(Asset)), Float2.Zero)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetPicker"/> class.
/// </summary>
/// <param name="assetType">The assets types that this picker accepts.</param>
/// <param name="location">The control location.</param>
public AssetPicker(ScriptType assetType, Float2 location)
: base(location, new Float2(DefaultIconSize + ButtonsOffset + ButtonsSize, DefaultIconSize))
{
_type = assetType;
_mousePos = Float2.Minimum;
}
/// <summary>
/// Called when selected item gets changed.
/// </summary>
protected virtual void OnSelectedItemChanged()
{
// Update tooltip
string tooltip;
if (_selectedItem is AssetItem assetItem)
tooltip = assetItem.NamePath;
else
tooltip = SelectedPath;
TooltipText = tooltip;
SelectedItemChanged?.Invoke();
}
private void DoDrag()
{
// 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));
}
}
/// <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);
private Rectangle Button2Rect => new Rectangle(Height + ButtonsOffset, ButtonsSize, ButtonsSize, ButtonsSize);
private Rectangle Button3Rect => new Rectangle(Height + ButtonsOffset, ButtonsSize * 2, ButtonsSize, ButtonsSize);
/// <inheritdoc />
public override void Draw()
{
var style = Style.Current;
var iconRect = IconRect;
var button1Rect = Button1Rect;
var button2Rect = Button2Rect;
var button3Rect = Button3Rect;
// Draw asset picker button
if (CanEdit)
Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
if (_selectedItem != null)
{
// Draw item preview
_selectedItem.DrawThumbnail(ref iconRect);
// Draw buttons
if (CanEdit)
{
Render2D.DrawSprite(style.Search, button2Rect, button2Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
}
else
{
Render2D.DrawSprite(style.Search, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
}
// Draw name
float sizeForTextLeft = Width - button1Rect.Right;
if (sizeForTextLeft > 30)
{
Render2D.DrawText(
style.FontSmall,
_selectedItem.ShortName,
new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize),
style.Foreground,
TextAlignment.Near,
TextAlignment.Center);
}
}
// Check if has no item but has an asset (eg. virtual asset)
else if (_selected)
{
// Draw remove button
Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
// Draw name
float sizeForTextLeft = Width - button1Rect.Right;
if (sizeForTextLeft > 30)
{
var name = _selected.GetType().Name;
if (_selected.IsVirtual)
name += " (virtual)";
Render2D.DrawText(
style.FontSmall,
name,
new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize),
style.Foreground,
TextAlignment.Near,
TextAlignment.Center);
}
}
else
{
// No element selected
Render2D.FillRectangle(iconRect, new Color(0.2f));
Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Wheat, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize);
}
// Check if drag is over
if (IsDragOver && _dragOverElement != null && _dragOverElement.HasValidDrag)
Render2D.FillRectangle(iconRect, style.BackgroundSelected * 0.4f);
}
/// <inheritdoc />
public override void OnDestroy()
{
_selectedItem?.RemoveReference(this);
_selectedItem = null;
_selected = null;
base.OnDestroy();
}
/// <inheritdoc />
public override void OnMouseLeave()
{
_mousePos = Float2.Minimum;
// Check if start drag drop
if (_isMouseDown)
{
_isMouseDown = false;
DoDrag();
}
base.OnMouseLeave();
}
/// <inheritdoc />
public override void OnMouseEnter(Float2 location)
{
_mousePos = location;
_mouseDownPos = Float2.Minimum;
base.OnMouseEnter(location);
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
_mousePos = location;
// Check if start drag drop
if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f && IconRect.Contains(_mouseDownPos))
{
_isMouseDown = false;
DoDrag();
}
base.OnMouseMove(location);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isMouseDown)
{
_isMouseDown = false;
// Buttons logic
if (!CanEdit)
{
if (Button1Rect.Contains(location) && _selectedItem != null)
{
// Select asset
Editor.Instance.Windows.ContentWin.Select(_selectedItem);
}
}
else if (Button1Rect.Contains(location))
{
Focus();
if (_type != ScriptType.Null)
{
// Show asset picker popup
var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
{
SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (_selected != null)
{
var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}
else
{
// Show content item picker popup
var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
{
SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (_selectedItem != null)
{
popup.ScrollToAndHighlightItemByName(_selectedItem.ShortName);
}
}
}
else if (_selected != null || _selectedItem != null)
{
if (Button2Rect.Contains(location) && _selectedItem != null)
{
// Select asset
Editor.Instance.Windows.ContentWin.Select(_selectedItem);
}
else if (Button3Rect.Contains(location))
{
// Deselect asset
Focus();
SelectedItem = null;
}
}
}
// Handled
return true;
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left)
{
_isMouseDown = true;
_mouseDownPos = location;
}
// Handled
return true;
}
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
Focus();
if (_selectedItem != null && IconRect.Contains(location))
{
// Open it
Editor.Instance.ContentEditing.Open(_selectedItem);
}
// Handled
return true;
}
/// <inheritdoc />
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
base.OnDragEnter(ref location, data);
// Check if drop asset
if (_dragOverElement == null)
_dragOverElement = new DragItems(IsValid);
if (CanEdit && _dragOverElement.OnDragEnter(data))
{
}
return _dragOverElement.Effect;
}
/// <inheritdoc />
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
base.OnDragMove(ref location, data);
return _dragOverElement.Effect;
}
/// <inheritdoc />
public override void OnDragLeave()
{
// Clear cache
_dragOverElement.OnDragLeave();
base.OnDragLeave();
}
/// <inheritdoc />
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
base.OnDragDrop(ref location, data);
if (CanEdit && _dragOverElement.HasValidDrag)
{
// Select element
SelectedItem = _dragOverElement.Objects[0];
}
// Clear cache
_dragOverElement.OnDragDrop();
return DragDropEffect.Move;
}
}
}