Add FilePathEditor custom editor for path-based editing asset/url refs with a file picker
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using FlaxEditor.Scripting;
|
||||
@@ -94,30 +96,8 @@ public class AssetPickerValidator : IContentItemOwner
|
||||
/// </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);
|
||||
}
|
||||
}
|
||||
get => Utilities.Utils.ToPathProject(_selectedItem?.Path ?? _selected?.Path);
|
||||
set => SelectedItem = string.IsNullOrEmpty(value) ? null : Editor.Instance.ContentDatabase.Find(Utilities.Utils.ToPathAbsolute(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -242,7 +222,7 @@ public class AssetPickerValidator : IContentItemOwner
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
|
||||
/// </summary>
|
||||
/// <param name="assetType">The assets types that this picker accepts.</param>
|
||||
/// <param name="assetType">The asset types that this picker accepts.</param>
|
||||
public AssetPickerValidator(ScriptType assetType)
|
||||
{
|
||||
_type = assetType;
|
||||
|
||||
@@ -6,6 +6,7 @@ using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
@@ -50,7 +51,6 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
if (HasDifferentTypes)
|
||||
return;
|
||||
Picker = layout.Custom<AssetPicker>().CustomControl;
|
||||
|
||||
var value = Values[0];
|
||||
_valueType = Values.Type.Type != typeof(object) || value == null ? Values.Type : TypeUtils.GetObjectType(value);
|
||||
var assetType = _valueType;
|
||||
@@ -58,37 +58,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
assetType = new ScriptType(typeof(Asset));
|
||||
else if (_valueType.Type != null && _valueType.Type.Name == typeof(JsonAssetReference<>).Name)
|
||||
assetType = new ScriptType(_valueType.Type.GenericTypeArguments[0]);
|
||||
|
||||
float height = 48;
|
||||
var attributes = Values.GetAttributes();
|
||||
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
|
||||
if (assetReference != null)
|
||||
{
|
||||
if (assetReference.UseSmallPicker)
|
||||
height = 32;
|
||||
|
||||
if (string.IsNullOrEmpty(assetReference.TypeName))
|
||||
{
|
||||
}
|
||||
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
|
||||
{
|
||||
// Generic file picker
|
||||
assetType = ScriptType.Null;
|
||||
Picker.Validator.FileExtension = assetReference.TypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var customType = TypeUtils.GetType(assetReference.TypeName);
|
||||
if (customType != ScriptType.Null)
|
||||
assetType = customType;
|
||||
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
|
||||
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
|
||||
else
|
||||
assetType = ScriptType.Void;
|
||||
}
|
||||
}
|
||||
|
||||
Picker.Validator.AssetType = assetType;
|
||||
ApplyAssetReferenceAttribute(Values, out var height, Picker.Validator);
|
||||
Picker.Height = height;
|
||||
Picker.SelectedItemChanged += OnSelectedItemChanged;
|
||||
}
|
||||
@@ -115,6 +86,37 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
SetValue(Picker.Validator.SelectedAsset);
|
||||
}
|
||||
|
||||
internal static void ApplyAssetReferenceAttribute(ValueContainer values, out float height, AssetPickerValidator validator)
|
||||
{
|
||||
height = 48;
|
||||
var attributes = values.GetAttributes();
|
||||
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
|
||||
if (assetReference != null)
|
||||
{
|
||||
if (assetReference.UseSmallPicker)
|
||||
height = 32;
|
||||
if (string.IsNullOrEmpty(assetReference.TypeName))
|
||||
{
|
||||
}
|
||||
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
|
||||
{
|
||||
// Generic file picker
|
||||
validator.AssetType = ScriptType.Null;
|
||||
validator.FileExtension = assetReference.TypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var customType = TypeUtils.GetType(assetReference.TypeName);
|
||||
if (customType != ScriptType.Null)
|
||||
validator.AssetType = customType;
|
||||
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
|
||||
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
|
||||
else
|
||||
validator.AssetType = ScriptType.Void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
@@ -140,4 +142,155 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit reference to the files via path (absolute or relative to the project).
|
||||
/// </summary>
|
||||
/// <remarks>Supports editing reference to the asset via path using various containers: <see cref="Asset"/> or <see cref="AssetItem"/> or <see cref="System.String"/>.</remarks>
|
||||
public class FilePathEditor : CustomEditor
|
||||
{
|
||||
private sealed class TextBoxWithPicker : TextBox
|
||||
{
|
||||
private const float DropdownIconMargin = 3.0f;
|
||||
private const float DropdownIconSize = 12.0f;
|
||||
private Rectangle DropdownRect => new Rectangle(Width - DropdownIconSize - DropdownIconMargin, DropdownIconMargin, DropdownIconSize, DropdownIconSize);
|
||||
|
||||
public Action ShowPicker;
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
var dropdownRect = DropdownRect;
|
||||
Render2D.DrawSprite(style.ArrowDown, dropdownRect, Enabled ? (DropdownRect.Contains(PointFromWindow(RootWindow.MousePosition)) ? style.BorderSelected : style.Foreground) : style.ForegroundDisabled);
|
||||
}
|
||||
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (DropdownRect.Contains(ref location))
|
||||
{
|
||||
Focus();
|
||||
ShowPicker();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
base.OnMouseMove(location);
|
||||
|
||||
if (DropdownRect.Contains(ref location))
|
||||
Cursor = CursorType.Default;
|
||||
else
|
||||
Cursor = CursorType.IBeam;
|
||||
}
|
||||
|
||||
protected override Rectangle TextRectangle
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = base.TextRectangle;
|
||||
result.Size.X -= DropdownIconSize + DropdownIconMargin * 2;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Rectangle TextClipRectangle
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = base.TextClipRectangle;
|
||||
result.Size.X -= DropdownIconSize + DropdownIconMargin * 2;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TextBoxWithPicker _textBox;
|
||||
private AssetPickerValidator _validator;
|
||||
private bool _isRefreshing;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
if (HasDifferentTypes)
|
||||
return;
|
||||
_textBox = layout.Custom<TextBoxWithPicker>().CustomControl;
|
||||
_textBox.ShowPicker = OnShowPicker;
|
||||
_textBox.EditEnd += OnEditEnd;
|
||||
_validator = new AssetPickerValidator(ScriptType.Null);
|
||||
AssetRefEditor.ApplyAssetReferenceAttribute(Values, out _, _validator);
|
||||
}
|
||||
|
||||
private void OnShowPicker()
|
||||
{
|
||||
if (_validator.AssetType != ScriptType.Null)
|
||||
AssetSearchPopup.Show(_textBox, _textBox.BottomLeft, _validator.IsValid, SetPickerPath);
|
||||
else
|
||||
ContentSearchPopup.Show(_textBox, _textBox.BottomLeft, _validator.IsValid, SetPickerPath);
|
||||
}
|
||||
|
||||
private void SetPickerPath(ContentItem item)
|
||||
{
|
||||
var path = Utilities.Utils.ToPathProject(item.Path);
|
||||
SetPath(path);
|
||||
|
||||
_isRefreshing = true;
|
||||
_textBox.Defocus();
|
||||
_textBox.Text = path;
|
||||
_isRefreshing = false;
|
||||
|
||||
_textBox.RootWindow.Focus();
|
||||
_textBox.Focus();
|
||||
}
|
||||
|
||||
private void OnEditEnd()
|
||||
{
|
||||
SetPath(_textBox.Text);
|
||||
}
|
||||
|
||||
private string GetPath()
|
||||
{
|
||||
var value = Values[0];
|
||||
if (value is AssetItem assetItem)
|
||||
return Utilities.Utils.ToPathProject(assetItem.Path);
|
||||
if (value is Asset asset)
|
||||
return Utilities.Utils.ToPathProject(asset.Path);
|
||||
if (value is string str)
|
||||
return str;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void SetPath(string path)
|
||||
{
|
||||
if (_isRefreshing)
|
||||
return;
|
||||
var value = Values[0];
|
||||
if (value is AssetItem)
|
||||
SetValue(Editor.Instance.ContentDatabase.Find(Utilities.Utils.ToPathAbsolute(path)));
|
||||
else if (value is Asset)
|
||||
SetValue(FlaxEngine.Content.LoadAsync(path));
|
||||
else if (value is string)
|
||||
SetValue(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (!HasDifferentValues)
|
||||
{
|
||||
_isRefreshing = true;
|
||||
_textBox.Text = GetPath();
|
||||
_isRefreshing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@ using System.IO;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
namespace FlaxEditor.GUI
|
||||
{
|
||||
|
||||
@@ -1471,5 +1471,27 @@ namespace FlaxEditor.Utilities
|
||||
inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync());
|
||||
inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile);
|
||||
}
|
||||
|
||||
internal static string ToPathProject(string 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;
|
||||
}
|
||||
|
||||
internal static string ToPathAbsolute(string path)
|
||||
{
|
||||
if (path != null)
|
||||
{
|
||||
// Convert into global path to if relative to the project
|
||||
path = StringUtils.IsRelative(path) ? Path.Combine(Globals.ProjectFolder, path) : path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
/// <summary>
|
||||
/// The video clip Url path used as a source of the media. Can be local file (absolute or relative path), or streamed resource ('http://').
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(10), DefaultValue(\"\"), EditorDisplay(\"Video Player\")")
|
||||
API_FIELD(Attributes="EditorOrder(10), DefaultValue(\"\"), EditorDisplay(\"Video Player\"), AssetReference(\".mp4\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.FilePathEditor\")")
|
||||
String Url;
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user