From 5cd6c98ff35260dc9c59161d24f4a1c4a72cf61f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 30 Jun 2026 08:48:24 +0200 Subject: [PATCH] Fix auto-selecting newly imported or created assets in Editor --- Source/Editor/Content/GUI/ContentView.cs | 2 +- Source/Editor/GUI/Tree/Tree.cs | 15 +- .../Editor/Modules/ContentDatabaseModule.cs | 4 - .../Editor/Modules/ContentImportingModule.cs | 4 + .../Windows/ContentWindow.Navigation.cs | 86 ++++------- Source/Editor/Windows/ContentWindow.cs | 134 ++++++++++++------ 6 files changed, 140 insertions(+), 105 deletions(-) diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 4928722b6..5382bd731 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -384,7 +384,7 @@ namespace FlaxEditor.Content.GUI /// Selects the specified item. /// /// The item. - /// If set to true item will be added to the current selection. Otherwise selection will be cleared before. + /// If set to true item will be added to the current selection. Otherwise, selection will be cleared before. public void Select(ContentItem item, bool additive = false) { if (item == null) diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index 3f60572f6..d48b1756e 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -138,7 +138,8 @@ namespace FlaxEditor.GUI.Tree /// Selects single tree node. /// /// Node to select. - public void Select(TreeNode node) + /// If set to true item will be added to the current selection. Otherwise, selection will be cleared before. + public void Select(TreeNode node, bool additive = false) { if (node == null) throw new ArgumentNullException(); @@ -151,8 +152,16 @@ namespace FlaxEditor.GUI.Tree var prev = new List(Selection); // Update selection - Selection.Clear(); - Selection.Add(node); + if (additive) + { + if (!Selection.Contains(node)) + Selection.Add(node); + } + else + { + Selection.Clear(); + Selection.Add(node); + } // Ensure that node can be visible (all it's parents are expanded) node.ExpandAllParents(); diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index 53075fe38..0559e18db 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -1261,7 +1261,6 @@ namespace FlaxEditor.Modules private void OnImportFileDone(string path) { - // Check if already has that element var item = Find(path); if (item is BinaryAssetItem binaryAssetItem) { @@ -1284,9 +1283,6 @@ namespace FlaxEditor.Modules binaryAssetItem.OnReimport(ref assetInfo.ID); } } - - // Refresh content view (not the best design because window could also track this event but it gives better performance) - Editor.Windows.ContentWin?.RefreshView(); } } diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs index 8029c7418..99994ec1f 100644 --- a/Source/Editor/Modules/ContentImportingModule.cs +++ b/Source/Editor/Modules/ContentImportingModule.cs @@ -252,6 +252,8 @@ namespace FlaxEditor.Modules /// Import settings to override. Use null to skip this value. private void Import(string inputPath, string outputPath, bool isInBuilt, bool skipSettingsDialog = false, object settings = null) { + inputPath = StringUtils.NormalizePath(inputPath); + outputPath = StringUtils.NormalizePath(outputPath); lock (_requests) { _requests.Add(new Request @@ -311,7 +313,9 @@ namespace FlaxEditor.Modules } _importBatchDone++; + Profiler.BeginEvent("ImportFileEnd"); ImportFileEnd?.Invoke(entry, failed); + Profiler.EndEvent(); } } else diff --git a/Source/Editor/Windows/ContentWindow.Navigation.cs b/Source/Editor/Windows/ContentWindow.Navigation.cs index 4c7373aa6..27826e195 100644 --- a/Source/Editor/Windows/ContentWindow.Navigation.cs +++ b/Source/Editor/Windows/ContentWindow.Navigation.cs @@ -81,28 +81,10 @@ namespace FlaxEditor.Windows _navigationUndo.Push(source); } - // Show folder contents and select tree node - if (!_showAllContentInTree) - RefreshView(target); - _tree.Select(target); - target.ExpandAllParents(); - // Clear redo list _navigationRedo.Clear(); - // Set valid sizes for stacks - //RedoList.SetSize(32); - //UndoList.SetSize(32); - - // Update search - if (!_showAllContentInTree) - UpdateItemsSearch(); - - // Unlock navigation - _navigationUnlocked = true; - - // Update UI - UpdateUI(); + DoNavigate(target); } } @@ -123,25 +105,7 @@ namespace FlaxEditor.Windows // Add to Redo list _navigationRedo.Push(SelectedNode); - // Select node - if (!_showAllContentInTree) - RefreshView(node); - _tree.Select(node); - node.ExpandAllParents(); - - // Set valid sizes for stacks - //RedoList.SetSize(32); - //UndoList.SetSize(32); - - // Update search - if (!_showAllContentInTree) - UpdateItemsSearch(); - - // Unlock navigation - _navigationUnlocked = true; - - // Update UI - UpdateUI(); + DoNavigate(node); if (!_showAllContentInTree) _view.SelectFirstItem(); } @@ -164,25 +128,7 @@ namespace FlaxEditor.Windows // Add to Undo list _navigationUndo.Push(SelectedNode); - // Select node - if (!_showAllContentInTree) - RefreshView(node); - _tree.Select(node); - node.ExpandAllParents(); - - // Set valid sizes for stacks - //RedoList.SetSize(32); - //UndoList.SetSize(32); - - // Update search - if (!_showAllContentInTree) - UpdateItemsSearch(); - - // Unlock navigation - _navigationUnlocked = true; - - // Update UI - UpdateUI(); + DoNavigate(node); if (!_showAllContentInTree) _view.SelectFirstItem(); } @@ -214,6 +160,32 @@ namespace FlaxEditor.Windows UpdateUI(); } + private void DoNavigate(ContentFolderTreeNode node) + { + // Select node + if (!_showAllContentInTree) + RefreshView(node); + _tree.Select(node); + node.ExpandAllParents(); + + // Set valid sizes for stacks + //RedoList.SetSize(32); + //UndoList.SetSize(32); + + // Update search + if (!_showAllContentInTree) + UpdateItemsSearch(); + + // Unlock navigation + _navigationUnlocked = true; + + UpdateUI(); + + // Clear auto-select cache for new/imported files + _newFilesCache?.Clear(); + _newFilesCacheSize = 0; + } + private void UpdateNavigationBar() { if (_navigationBar == null) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 5a73530bc..2d9daabeb 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -67,6 +67,8 @@ namespace FlaxEditor.Windows private readonly Stack _navigationRedo = new Stack(32); private NewItem _newElement; + private List _newFilesCache; + private int _newFilesCacheSize; /// /// Gets the toolstrip. @@ -598,21 +600,9 @@ namespace FlaxEditor.Windows // Disable scrolling in proper view _renameInTree = _showAllContentInTree; if (_renameInTree) - { - if (_contentTreePanel.VScrollBar != null) - _contentTreePanel.VScrollBar.ThumbEnabled = false; - if (_contentTreePanel.HScrollBar != null) - _contentTreePanel.HScrollBar.ThumbEnabled = false; ScrollingOnTreeView(false); - } else - { - if (_contentViewPanel.VScrollBar != null) - _contentViewPanel.VScrollBar.ThumbEnabled = false; - if (_contentViewPanel.HScrollBar != null) - _contentViewPanel.HScrollBar.ThumbEnabled = false; ScrollingOnContentView(false); - } // Show rename popup RenamePopup popup; @@ -664,21 +654,9 @@ namespace FlaxEditor.Windows { // Restore scrolling in proper view if (_renameInTree) - { - if (_contentTreePanel.VScrollBar != null) - _contentTreePanel.VScrollBar.ThumbEnabled = true; - if (_contentTreePanel.HScrollBar != null) - _contentTreePanel.HScrollBar.ThumbEnabled = true; ScrollingOnTreeView(true); - } else - { - if (_contentViewPanel.VScrollBar != null) - _contentViewPanel.VScrollBar.ThumbEnabled = true; - if (_contentViewPanel.HScrollBar != null) - _contentViewPanel.HScrollBar.ThumbEnabled = true; ScrollingOnContentView(true); - } _renameInTree = false; // Check if was creating new element @@ -704,7 +682,6 @@ namespace FlaxEditor.Windows // Check if can rename this item if (!item.CanRename) { - // Cannot MessageBox.Show("Cannot rename this item.", "Cannot rename", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } @@ -718,7 +695,6 @@ namespace FlaxEditor.Windows // Check if name is valid if (!Editor.ContentEditing.IsValidAssetName(item, newShortName, out string hint)) { - // Invalid name MessageBox.Show("Given asset name is invalid. " + hint, "Invalid name", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } @@ -740,6 +716,7 @@ namespace FlaxEditor.Windows // Note: we create `_newElement` and then rename it to create new asset var itemFolder = item.ParentFolder; Action endEvent = null; + bool lazyCreation = false; if (_newElement == item) { try @@ -750,6 +727,9 @@ namespace FlaxEditor.Windows var proxy = _newElement.Proxy; Editor.Log(string.Format("Creating asset {0} in {1}", proxy.Name, newPath)); proxy.Create(newPath, _newElement.Argument); + + // When creating item with options dialog deffer processing + lazyCreation = !File.Exists(newPath); } catch (Exception ex) { @@ -773,6 +753,16 @@ namespace FlaxEditor.Windows if (_newElement.Proxy is ScriptProxy && Editor.Instance.Options.Options.General.AutoReloadScriptsOnMainWindowFocus) ScriptsBuilder.MarkWorkspaceDirty(); + // Cache new file to be auto-selected after actual creation + _newFilesCache?.Clear(); + _newFilesCacheSize = 0; + if (lazyCreation) + { + _newFilesCache ??= new List(); + _newFilesCache.Add(newPath); + _newFilesCacheSize = 1; + } + // Destroy mock control _newElement.ParentFolder = null; _newElement.Dispose(); @@ -789,7 +779,8 @@ namespace FlaxEditor.Windows var newItem = itemFolder.FindChild(newPath); if (newItem == null) { - Editor.LogWarning("Failed to find the created new item."); + if (!lazyCreation) + Editor.LogWarning("Failed to find the created new item."); return; } @@ -1143,11 +1134,12 @@ namespace FlaxEditor.Windows } /// - /// Selects the specified item in the content view. + /// Selects the specified item in the content view. Does nothing if the current view doesn't show the folder containing that item. /// /// The item to select. /// True of scroll to the item quickly without smoothing. - public void Select(ContentItem item, bool fastScroll = false) + /// True of select item in additive mode with existing selection preservation, otherwise current selection will be cleared. + public void Select(ContentItem item, bool fastScroll = false, bool additive = false) { if (item == null) throw new ArgumentNullException(); @@ -1169,7 +1161,7 @@ namespace FlaxEditor.Windows targetNode.ExpandAllParents(); if (item is ContentFolder) { - _tree.Select(targetNode); + _tree.Select(targetNode, additive); _contentTreePanel.ScrollViewTo(targetNode, fastScroll); targetNode.Focus(); } @@ -1178,13 +1170,13 @@ namespace FlaxEditor.Windows var itemNode = FindTreeItemNode(targetNode, item); if (itemNode != null) { - _tree.Select(itemNode); + _tree.Select(itemNode, additive); _contentTreePanel.ScrollViewTo(itemNode, fastScroll); itemNode.Focus(); } else { - _tree.Select(targetNode); + _tree.Select(targetNode, additive); } } } @@ -1195,7 +1187,7 @@ namespace FlaxEditor.Windows Navigate(parent.Node); // Select and scroll to cover in view - _view.Select(item); + _view.Select(item, additive); _contentViewPanel.ScrollViewTo(item, fastScroll); // Focus @@ -1574,7 +1566,7 @@ namespace FlaxEditor.Windows /// public override void OnInit() { - // Content database events + // Content events Editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true; Editor.ContentDatabase.ItemAdded += OnContentDatabaseItemAdded; Editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved; @@ -1594,6 +1586,9 @@ namespace FlaxEditor.Windows else if (_root != null) ShowRoot(); }; + Editor.ContentImporting.ImportFileBegin += OnImportFileBegin; + Editor.ContentImporting.ImportFileEnd += OnImportFileEnd; + Editor.ContentImporting.ImportingQueueBegin += OnImportingQueueBegin; LoadExpandedFolders(); Refresh(); @@ -1631,6 +1626,64 @@ namespace FlaxEditor.Windows OnFoldersSearchBoxTextChanged(); } + private void OnImportFileBegin(IFileEntryAction entry) + { + // Add to auto-select cache + _newFilesCache ??= new List(); + _newFilesCache.Add(entry.ResultUrl); + _newFilesCacheSize++; + } + + private void OnImportFileEnd(IFileEntryAction entry, bool failed) + { + if (failed) + return; + if (!Platform.IsInMainThread) + { + FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileEnd(entry, false)); + return; + } + + // Refresh view (gives faster response than waiting for filesystem event) + //RefreshView(); // TODO: is this still needed? + + // Auto-select pending items + if (_newFilesCache != null && _newFilesCache.Contains(entry.ResultUrl)) + { + var item = EnsureItem(entry.ResultUrl); + if (item != null) + { + bool additive = _newFilesCache.Count != _newFilesCacheSize; + Select(item, true, additive); + } + _newFilesCache.Remove(entry.ResultUrl); + } + } + + private void OnImportingQueueBegin() + { + // Clear cache to auto-select all imported files + _newFilesCache?.Clear(); + _newFilesCacheSize = 0; + } + + private ContentItem EnsureItem(string path) + { + var item = Editor.ContentDatabase.Find(path); + if (item == null) + { + // Cannot find the item (eg. just created file, content database event not yet handled) so refresh to take effect quickly + var parentPath = Path.GetDirectoryName(path); + var parentItem = Editor.ContentDatabase.Find(parentPath); + if (parentItem != null) + { + Editor.ContentDatabase.RefreshFolder(parentItem, false); + item = Editor.ContentDatabase.Find(path); + } + } + return item; + } + private void Refresh() { // Setup content root node @@ -1774,16 +1827,11 @@ namespace FlaxEditor.Windows return base.OnMouseUp(location, button); } - /// - protected override void PerformLayoutBeforeChildren() - { - base.PerformLayoutBeforeChildren(); - } - /// protected override void PerformLayoutAfterChildren() { base.PerformLayoutAfterChildren(); + UpdateNavigationBarBounds(); } @@ -1846,12 +1894,18 @@ namespace FlaxEditor.Windows _treeHeaderPanel = null; _treeOnlyPanel = null; _contentItemsSearchPanel = null; + _newFilesCache = null; Editor.Options.OptionsChanged -= OnOptionsChanged; ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; if (Editor?.ContentDatabase != null) + { Editor.ContentDatabase.ItemAdded -= OnContentDatabaseItemAdded; + Editor.ContentImporting.ImportFileBegin -= OnImportFileBegin; + Editor.ContentImporting.ImportFileEnd -= OnImportFileEnd; + Editor.ContentImporting.ImportingQueueBegin -= OnImportingQueueBegin; + } base.OnDestroy(); }