diff --git a/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs index bd5248e67..e784048af 100644 --- a/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs @@ -77,11 +77,12 @@ namespace FlaxEditor.CustomEditors.Dedicated } prop.Label(string.Format("Progress: {0}% ({1}/{2})", allKeys.Count > 0 ? (int)(((float)validCount / allKeys.Count * 100.0f)) : 0, validCount, allKeys.Count)); prop.Label("Tables:"); + var projectFolder = Globals.ProjectFolder; foreach (var table in e) { var namePath = table.Path; - if (namePath.StartsWith(Globals.ProjectFolder)) - namePath = namePath.Substring(Globals.ProjectFolder.Length + 1); + if (namePath.StartsWith(projectFolder)) + namePath = namePath.Substring(projectFolder.Length + 1); var tableLabel = prop.ClickableLabel(namePath).CustomControl; tableLabel.TextColorHighlighted = Color.Wheat; tableLabel.DoubleClick += delegate { Editor.Instance.Windows.ContentWin.Select(table); }; diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs index f6d8b1671..434d0c558 100644 --- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs @@ -3,7 +3,6 @@ using System; using System.Linq; using FlaxEditor.Content; -using FlaxEditor.CustomEditors.Elements; using FlaxEditor.GUI; using FlaxEditor.Scripting; using FlaxEngine; @@ -33,7 +32,7 @@ namespace FlaxEditor.CustomEditors.Editors [CustomEditor(typeof(Asset)), DefaultEditor] public class AssetRefEditor : CustomEditor { - private CustomElement _element; + private AssetPicker _picker; private ScriptType _valueType; /// @@ -42,46 +41,59 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - if (!HasDifferentTypes) + if (HasDifferentTypes) + return; + _picker = layout.Custom().CustomControl; + + _valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]); + var assetType = _valueType; + if (assetType == typeof(string)) + assetType = new ScriptType(typeof(Asset)); + + float height = 48; + var attributes = Values.GetAttributes(); + var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute); + if (assetReference != null) { - _valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]); - var assetType = _valueType; + if (assetReference.UseSmallPicker) + height = 32; - float height = 48; - var attributes = Values.GetAttributes(); - var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute); - if (assetReference != null) + if (string.IsNullOrEmpty(assetReference.TypeName)) { - if (assetReference.UseSmallPicker) - height = 32; - - if (!string.IsNullOrEmpty(assetReference.TypeName)) - { - var customType = TypeUtils.GetType(assetReference.TypeName); - if (customType != ScriptType.Null) - assetType = customType; - else - Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName)); - } } - - _element = layout.Custom(); - _element.CustomControl.AssetType = assetType; - _element.CustomControl.Height = height; - _element.CustomControl.SelectedItemChanged += OnSelectedItemChanged; + else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.') + { + // Generic file picker + assetType = ScriptType.Null; + _picker.FileExtension = assetReference.TypeName; + } + else + { + var customType = TypeUtils.GetType(assetReference.TypeName); + if (customType != ScriptType.Null) + assetType = customType; + else + Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName)); + } } + + _picker.AssetType = assetType; + _picker.Height = height; + _picker.SelectedItemChanged += OnSelectedItemChanged; } private void OnSelectedItemChanged() { if (typeof(AssetItem).IsAssignableFrom(_valueType.Type)) - SetValue(_element.CustomControl.SelectedItem); + SetValue(_picker.SelectedItem); else if (_valueType.Type == typeof(Guid)) - SetValue(_element.CustomControl.SelectedID); + SetValue(_picker.SelectedID); else if (_valueType.Type == typeof(SceneReference)) - SetValue(new SceneReference(_element.CustomControl.SelectedID)); + SetValue(new SceneReference(_picker.SelectedID)); + else if (_valueType.Type == typeof(string)) + SetValue(_picker.SelectedPath); else - SetValue(_element.CustomControl.SelectedAsset); + SetValue(_picker.SelectedAsset); } /// @@ -92,13 +104,15 @@ namespace FlaxEditor.CustomEditors.Editors if (!HasDifferentValues) { if (Values[0] is AssetItem assetItem) - _element.CustomControl.SelectedItem = assetItem; + _picker.SelectedItem = assetItem; else if (Values[0] is Guid guid) - _element.CustomControl.SelectedID = guid; + _picker.SelectedID = guid; else if (Values[0] is SceneReference sceneAsset) - _element.CustomControl.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID); + _picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID); + else if (Values[0] is string path) + _picker.SelectedPath = path; else - _element.CustomControl.SelectedAsset = Values[0] as Asset; + _picker.SelectedAsset = Values[0] as Asset; } } } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 7656351ce..e5a949f3b 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -510,7 +510,7 @@ namespace FlaxEditor } else if (!_autoSavePopup.Visible && !_autoSavePopup.UserClosed) _autoSavePopup.ShowPopup(); - + if (_autoSavePopup.Visible) _autoSavePopup.UpdateTime(timeToNextSave); } @@ -523,7 +523,7 @@ namespace FlaxEditor Scene.SaveScenes(); if (options.AutoSaveContent) SaveContent(); - + // Hide auto save popup and reset user closed _autoSavePopup.HidePopup(); _autoSavePopup.UserClosed = false; diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index a978409fe..a359c288c 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.IO; using FlaxEditor.Content; using FlaxEditor.GUI.Drag; using FlaxEditor.Scripting; @@ -22,22 +23,25 @@ namespace FlaxEditor.GUI private const float ButtonsSize = 12; private Asset _selected; - private AssetItem _selectedItem; + private ContentItem _selectedItem; private ScriptType _type; + private string _fileExtension; private bool _isMouseDown; private Float2 _mouseDownPos; private Float2 _mousePos; - private DragAssets _dragOverElement; + private DragItems _dragOverElement; /// /// Gets or sets the selected item. /// - public AssetItem SelectedItem + public ContentItem SelectedItem { get => _selectedItem; set { + if (_selectedItem == value) + return; if (value == null) { if (_selected == null && _selectedItem is SceneItem) @@ -46,13 +50,15 @@ namespace FlaxEditor.GUI _selectedItem.RemoveReference(this); _selectedItem = null; _selected = null; - TooltipText = string.Empty; OnSelectedItemChanged(); return; } // Deselect - SelectedAsset = null; + _selectedItem?.RemoveReference(this); + _selectedItem = null; + _selected = null; + OnSelectedItemChanged(); } else if (value is SceneItem item) { @@ -66,15 +72,19 @@ namespace FlaxEditor.GUI _selectedItem = item; _selected = null; _selectedItem?.AddReference(this); - - // Update tooltip - TooltipText = _selectedItem?.NamePath; - OnSelectedItemChanged(); } + else if (value is AssetItem assetItem) + { + SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID); + } else { - SelectedAsset = FlaxEngine.Content.LoadAsync(value.ID); + // Change value + _selectedItem?.RemoveReference(this); + _selectedItem = value; + _selected = null; + OnSelectedItemChanged(); } } } @@ -88,13 +98,44 @@ namespace FlaxEditor.GUI { if (_selected != null) return _selected.ID; - if (_selectedItem != null) - return _selectedItem.ID; + if (_selectedItem is AssetItem assetItem) + return assetItem.ID; return Guid.Empty; } set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value); } + /// + /// Gets or sets the selected content item path. + /// + 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); + } + } + } + /// /// Gets or sets the selected asset object. /// @@ -117,16 +158,12 @@ namespace FlaxEditor.GUI _selectedItem = item; _selected = value; _selectedItem?.AddReference(this); - - // Update tooltip - TooltipText = _selectedItem?.NamePath; - OnSelectedItemChanged(); } } /// - /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). + /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker. /// public ScriptType AssetType { @@ -144,6 +181,25 @@ namespace FlaxEditor.GUI } } + /// + /// Gets or sets the content items extensions filter. Null if unused. + /// + 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; + } + } + } + /// /// Occurs when selected item gets changed. /// @@ -154,24 +210,32 @@ namespace FlaxEditor.GUI /// public bool CanEdit = true; - private bool IsValid(AssetItem item) + private bool IsValid(ContentItem item) { - // Faster path for binary items (in-build) - if (item is BinaryAssetItem binaryItem) - return _type.IsAssignableFrom(new ScriptType(binaryItem.Type)); - - // Type filter - var type = TypeUtils.GetType(item.TypeName); - if (_type.IsAssignableFrom(type)) + if (_fileExtension != null && !item.Path.EndsWith(_fileExtension)) + return false; + if (_type == ScriptType.Null) return true; - // Json assets can contain any type of the object defined by the C# type (data oriented design) - if (item is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset))) - 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)); - // Special case for scene asset references - if (_type.Type == typeof(SceneReference) && item is SceneItem) - return true; + // 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; } @@ -201,15 +265,26 @@ namespace FlaxEditor.GUI /// 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 (_selected != null && new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos)) + if (new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos)) { - DoDragDrop(DragAssets.GetDragData(_selected)); + if (_selected != null) + DoDragDrop(DragAssets.GetDragData(_selected)); + else if (_selectedItem != null) + DoDragDrop(DragItems.GetDragData(_selectedItem)); } } @@ -339,10 +414,7 @@ namespace FlaxEditor.GUI // Check if start drag drop if (_isMouseDown) { - // Clear flag _isMouseDown = false; - - // Do the drag DoDrag(); } @@ -366,7 +438,6 @@ namespace FlaxEditor.GUI // Check if start drag drop if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f && IconRect.Contains(_mouseDownPos)) { - // Do the drag _isMouseDown = false; DoDrag(); } @@ -392,14 +463,27 @@ namespace FlaxEditor.GUI } else if (Button1Rect.Contains(location)) { - // Show asset picker popup Focus(); - AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, assetItem => + if (_type != ScriptType.Null) { - SelectedItem = assetItem; - RootWindow.Focus(); - Focus(); - }); + // Show asset picker popup + AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item => + { + SelectedItem = item; + RootWindow.Focus(); + Focus(); + }); + } + else + { + // Show content item picker popup + ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item => + { + SelectedItem = item; + RootWindow.Focus(); + Focus(); + }); + } } else if (_selected != null || _selectedItem != null) { @@ -437,10 +521,8 @@ namespace FlaxEditor.GUI /// public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { - // Focus Focus(); - // Check if has element selected if (_selectedItem != null && IconRect.Contains(location)) { // Open it @@ -458,7 +540,7 @@ namespace FlaxEditor.GUI // Check if drop asset if (_dragOverElement == null) - _dragOverElement = new DragAssets(IsValid); + _dragOverElement = new DragItems(IsValid); if (CanEdit && _dragOverElement.OnDragEnter(data)) { } diff --git a/Source/Editor/GUI/Popups/AssetSearchPopup.cs b/Source/Editor/GUI/Popups/AssetSearchPopup.cs index f11612e3b..c8b88b7a6 100644 --- a/Source/Editor/GUI/Popups/AssetSearchPopup.cs +++ b/Source/Editor/GUI/Popups/AssetSearchPopup.cs @@ -8,40 +8,38 @@ using FlaxEngine.GUI; namespace FlaxEditor.GUI { /// - /// Popup that shows the list of assets to pick. Supports searching and basic items filtering. + /// Popup that shows the list of content items to pick. Supports searching and basic items filtering. /// /// - public class AssetSearchPopup : ItemsListContextMenu + public class ContentSearchPopup : ItemsListContextMenu { /// - /// The asset item. + /// The content item. /// /// - public class AssetItemView : Item, IContentItemOwner + public class ContentItemView : Item, IContentItemOwner { - private AssetItem _asset; - /// /// The icon size (in pixels). /// public const float IconSize = 28; /// - /// Gets the asset. + /// Gets the item. /// - public AssetItem Asset => _asset; + public ContentItem ContentItem; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The asset. - public AssetItemView(AssetItem asset) + /// The item. + public ContentItemView(ContentItem item) { - _asset = asset; - _asset.AddReference(this); + ContentItem = item; + ContentItem.AddReference(this); - Name = asset.ShortName; - TooltipText = asset.Path; + Name = item.ShortName; + TooltipText = item.Path; Height = IconSize + 4; } @@ -60,16 +58,16 @@ namespace FlaxEditor.GUI // Draw icon var iconRect = new Rectangle(2, 2, IconSize, IconSize); - _asset.DrawThumbnail(ref iconRect); + ContentItem.DrawThumbnail(ref iconRect); } /// public override void OnDestroy() { - if (_asset != null) + if (ContentItem != null) { - _asset.RemoveReference(this); - _asset = null; + ContentItem.RemoveReference(this); + ContentItem = null; } base.OnDestroy(); @@ -84,7 +82,7 @@ namespace FlaxEditor.GUI /// public void OnItemRenamed(ContentItem item) { - Name = _asset.ShortName; + Name = ContentItem.ShortName; } /// @@ -99,12 +97,118 @@ namespace FlaxEditor.GUI } } + /// + /// Validates if the given content item can be used to pick it. + /// + /// The item. + /// True if is valid. + public delegate bool IsValidDelegate(ContentItem item); + + private IsValidDelegate _isValid; + private Action _selected; + + /// + protected ContentSearchPopup() + { + } + + /// + protected ContentSearchPopup(IsValidDelegate isValid, Action selected) + { + _isValid = isValid; + _selected = selected; + + ItemClicked += OnItemClicked; + + // TODO: use async thread to search workspace items + foreach (var project in Editor.Instance.ContentDatabase.Projects) + { + if (project.Content != null) + FindItems(project.Content.Folder); + } + SortItems(); + } + + private void OnItemClicked(Item item) + { + _selected.Invoke(((ContentItemView)item).ContentItem); + } + + private void FindItems(ContentFolder folder) + { + for (int i = 0; i < folder.Children.Count; i++) + { + if (folder.Children[i] is ContentItem item && _isValid(item)) + { + AddItem(new ContentItemView(item)); + } + } + + for (int i = 0; i < folder.Children.Count; i++) + { + if (folder.Children[i] is ContentFolder child) + { + FindItems(child); + } + } + } + + /// + /// Shows the popup. + /// + /// The show target. + /// The show target location. + /// Event called to check if a given content item is valid to be used. + /// Event called on content item pick. + /// The dialog. + public static ContentSearchPopup Show(Control showTarget, Float2 showTargetLocation, IsValidDelegate isValid, Action selected) + { + var popup = new ContentSearchPopup(isValid, selected); + popup.Show(showTarget, showTargetLocation); + return popup; + } + + /// + public override void OnDestroy() + { + _isValid = null; + _selected = null; + + base.OnDestroy(); + } + } + + /// + /// Popup that shows the list of assets to pick. Supports searching and basic items filtering. + /// + public class AssetSearchPopup : ContentSearchPopup + { + /// + /// The asset item. + /// + public class AssetItemView : ContentItemView + { + /// + /// Gets the asset. + /// + public AssetItem AssetItem => (AssetItem)ContentItem; + + /// + /// Initializes a new instance of the class. + /// + /// The asset. + public AssetItemView(AssetItem asset) + : base(asset) + { + } + } + /// /// Validates if the given asset item can be used to pick it. /// /// The asset. /// True if is valid. - public delegate bool IsValidDelegate(AssetItem asset); + public new delegate bool IsValidDelegate(AssetItem asset); private IsValidDelegate _isValid; private Action _selected; @@ -127,7 +231,7 @@ namespace FlaxEditor.GUI private void OnItemClicked(Item item) { - _selected(((AssetItemView)item).Asset); + _selected(((AssetItemView)item).AssetItem); } private void FindAssets(ContentFolder folder) diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 5e5aa2f28..ade084431 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1024,10 +1024,9 @@ namespace FlaxEditor.Utilities /// The processed name path. public static string GetAssetNamePath(string path) { - if (path.StartsWith(Globals.ProjectFolder)) - { - path = path.Substring(Globals.ProjectFolder.Length + 1); - } + var projectFolder = Globals.ProjectFolder; + if (path.StartsWith(projectFolder)) + path = path.Substring(projectFolder.Length + 1); return StringUtils.GetPathWithoutExtension(path); } diff --git a/Source/Editor/Windows/AssetReferencesGraphWindow.cs b/Source/Editor/Windows/AssetReferencesGraphWindow.cs index 78513f037..6e7d3d0ef 100644 --- a/Source/Editor/Windows/AssetReferencesGraphWindow.cs +++ b/Source/Editor/Windows/AssetReferencesGraphWindow.cs @@ -53,7 +53,7 @@ namespace FlaxEditor.Windows else { picker.SelectedID = AssetId; - var assetItem = picker.SelectedItem; + var assetItem = picker.SelectedItem as AssetItem; if (assetItem != null) { Title = assetItem.ShortName; diff --git a/Source/Engine/Scripting/Attributes/Editor/AssetReferenceAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/AssetReferenceAttribute.cs index 15c037f82..62040d224 100644 --- a/Source/Engine/Scripting/Attributes/Editor/AssetReferenceAttribute.cs +++ b/Source/Engine/Scripting/Attributes/Editor/AssetReferenceAttribute.cs @@ -12,7 +12,7 @@ namespace FlaxEngine public class AssetReferenceAttribute : Attribute { /// - /// The full name of the asset type to link. Use null or empty to skip it. + /// The full name of the asset type to link. Use null or empty to skip it. Can be used as file extension filter if starts with a dot and used over string property. /// public string TypeName; @@ -45,7 +45,7 @@ namespace FlaxEngine /// /// Initializes a new instance of the class. /// - /// The full name of the asset type to link. Use null or empty to skip it. + /// The full name of the asset type to link. Use null or empty to skip it. Can be used as file extension filter if starts with a dot and used over string property. /// True if use asset picker with a smaller height (single line), otherwise will use with full icon. public AssetReferenceAttribute(string typeName = null, bool useSmallPicker = false) {