Files
FlaxEngine/Source/Editor/GUI/AssetPicker.cs
Luke Schneider b7b8213179 Some additional fixes to light theme support
Fixed some issues with light theme support:

1) Icons in content tree nodes (Folder icons) now use the foreground color.  I did not find a case where the content tree is used for other icons.

2) The asset picker now uses the Background Normal color (instead of a very transparent dark gray) for the background, and Orange for the text.  Did not seem like it warranted adding a new color, and Orange works in both dark and light styles.

3) The platform selector icons are now hard-coded instead of based on the style.  This may sound odd, but the icons are colored, so they should always use White as the fully active color.  Previously they worked with a dark theme because the Foreground was set to white.

4) Fixed the CollectionBackgroundColor in the light theme being dark gray instead of light gray like it should be.  This fixes certain lists of things having a dark background in the light theme.
2023-09-29 07:43:59 -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, Style.Current.BackgroundNormal);
Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Orange, 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;
}
}
}