// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.Text.RegularExpressions; using FlaxEditor.Content; using FlaxEditor.GUI.Docking; using FlaxEditor.SceneGraph; using FlaxEditor.Windows.Search; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Windows.Search; namespace FlaxEditor.Modules { internal struct QuickAction { public string Name; public Action Action; } /// /// The search result. /// [HideInEditor] public struct SearchResult { /// /// The name. /// public string Name; /// /// The type name. /// public string Type; /// /// The item. /// public object Item; } /// /// The content finding module. /// public class ContentFindingModule : EditorModule { private List _quickActions; private ContentFinder _finder; private ContentSearchWindow _searchWin; /// /// Initializes a new instance of the class. /// /// The editor. internal ContentFindingModule(Editor editor) : base(editor) { } /// public override void OnExit() { if (_quickActions != null) { _quickActions.Clear(); _quickActions = null; } if (_finder != null) { _finder.Dispose(); _finder = null; } if (_searchWin != null) { _searchWin.Dispose(); _searchWin = null; } base.OnExit(); } /// /// Shows the content search window. /// public void ShowSearch() { // Try to find the currently focused editor window that might call this DockWindow window = null; foreach (var editorWindow in Editor.Windows.Windows) { if (editorWindow.Visible && editorWindow.ContainsFocus) { window = editorWindow; break; } } ShowSearch(window); } /// /// Shows the content search window. /// /// The target control to show search for it. /// The initial query for the search. public void ShowSearch(Control control, string query = null) { // Try to find the owning window DockWindow window = null; while (control != null && window == null) { window = control as DockWindow; control = control.Parent; } ShowSearch(window, query); } /// /// Shows the content search window. /// /// The target window to show search next to it. /// The initial query for the search. public void ShowSearch(DockWindow window, string query = null) { if (_searchWin == null) _searchWin = new ContentSearchWindow(Editor); _searchWin.TargetWindow = window; if (!_searchWin.IsHidden) { // Focus _searchWin.SelectTab(); _searchWin.Focus(); } else if (window != null) { // Show docked to the target window _searchWin.Show(DockState.DockBottom, window); _searchWin.SearchLocation = ContentSearchWindow.SearchLocations.CurrentAsset; } else { // Show floating _searchWin.ShowFloating(); _searchWin.SearchLocation = ContentSearchWindow.SearchLocations.AllAssets; } if (window is ISearchWindow searchWindow) { _searchWin.SearchAssets = searchWindow.AssetType; } if (query != null) { _searchWin.Query = query; _searchWin.Search(); } } /// /// Shows the content finder popup. /// /// The target control to show finder over it. public void ShowFinder(Control control) { var finder = _finder ?? (_finder = new ContentFinder()); if (control == null) control = Editor.Instance.Windows.MainWindow.GUI; var position = (control.Size - new Float2(finder.Width, 300.0f)) * 0.5f; finder.Show(control, position); } /// /// Adds to quick action list. /// /// The action's name. /// The actual action callback. public void AddQuickAction(string name, Action action) { if (_quickActions == null) _quickActions = new List(); _quickActions.Add(new QuickAction { Name = name, Action = action, }); } /// /// Removes a quick action by name. /// /// The action's name. /// True when it succeed, false if there is no Quick Action with this name. public bool RemoveQuickAction(string name) { if (_quickActions == null) return false; foreach (var action in _quickActions) { if (action.Name.Equals(name)) { _quickActions.Remove(action); return true; } } return false; } /// /// Searches any assets/scripts/quick actions that match the provided type and name. /// /// Two pattern can be used, the first one will just take a string without ':' and will only match names. /// The second looks like this "name:type", it will match name and type. Experimental : You can use regular expressions, might break if you are using ':' character. /// The results list. public List Search(string charsToFind) { // Special case if searching by object id if (charsToFind.Length == 32) { FlaxEngine.Json.JsonSerializer.ParseID(charsToFind, out var id); var item = Editor.Instance.ContentDatabase.Find(id); if (item is AssetItem assetItem) { return new List { new SearchResult { Name = item.ShortName, Type = assetItem.TypeName, Item = item } }; } var actor = FlaxEngine.Object.Find(ref id); if (actor != null) { return new List { new SearchResult { Name = actor.Name, Type = actor.TypeName, Item = actor } }; } } Profiler.BeginEvent("ContentFinding.Search"); string type = ".*"; string name = charsToFind.Trim(); if (charsToFind.Contains(':')) { var args = charsToFind.Split(':'); type = ".*" + args[1].Trim() + ".*"; name = ".*" + args[0].Trim() + ".*"; } if (name.Equals(string.Empty)) name = ".*"; var typeRegex = new Regex(type, RegexOptions.IgnoreCase); var nameRegex = new Regex(name, RegexOptions.IgnoreCase); var matches = new List(); foreach (var project in Editor.Instance.ContentDatabase.Projects) { Profiler.BeginEvent(project.Project.Name); ProcessItems(nameRegex, typeRegex, project.Folder.Children, matches); Profiler.EndEvent(); } //ProcessSceneNodes(nameRegex, typeRegex, Editor.Instance.Scene.Root, matches); { Profiler.BeginEvent("Actors"); ProcessActors(nameRegex, typeRegex, Editor.Instance.Scene.Root, matches); Profiler.EndEvent(); } if (_quickActions != null) { Profiler.BeginEvent("QuickActions"); foreach (var action in _quickActions) { if (nameRegex.Match(action.Name).Success && typeRegex.Match("Quick Action").Success) matches.Add(new SearchResult { Name = action.Name, Type = "Quick Action", Item = action }); } Profiler.EndEvent(); } Profiler.EndEvent(); return matches; } private void ProcessSceneNodes(Regex nameRegex, Regex typeRegex, SceneGraphNode root, List matches) { root.ChildNodes.ForEach(node => { var type = node.GetType(); if (typeRegex.Match(type.Name).Success && nameRegex.Match(node.Name).Success) { string finalName = type.Name; if (Aliases.TryGetValue(finalName, out var alias)) { finalName = alias; } matches.Add(new SearchResult { Name = node.Name, Type = finalName, Item = node }); } if (node.ChildNodes.Count != 0) { node.ChildNodes.ForEach(n => { ProcessSceneNodes(nameRegex, typeRegex, n, matches); }); } }); } private void ProcessActors(Regex nameRegex, Regex typeRegex, ActorNode node, List matches) { if (node.Actor != null) { var type = node.Actor.GetType(); if (typeRegex.Match(type.Name).Success && nameRegex.Match(node.Name).Success) { string finalName = type.Name; if (Aliases.TryGetValue(finalName, out var alias)) { finalName = alias; } matches.Add(new SearchResult { Name = node.Name, Type = finalName, Item = node }); } } for (var i = 0; i < node.ChildNodes.Count; i++) { if (node.ChildNodes[i] is ActorNode child) { ProcessActors(nameRegex, typeRegex, child, matches); } } } private void ProcessItems(Regex nameRegex, Regex typeRegex, List items, List matches) { foreach (var contentItem in items) { if (contentItem.IsAsset) { if (nameRegex.Match(contentItem.ShortName).Success) { var asset = contentItem as AssetItem; if (asset == null || !typeRegex.Match(asset.TypeName).Success) continue; if (Aliases.TryGetValue(asset.TypeName, out var finalName)) { } else { var splits = asset.TypeName.Split('.'); finalName = splits[splits.Length - 1]; } matches.Add(new SearchResult { Name = asset.ShortName, Type = finalName, Item = asset }); } } else if (contentItem.IsFolder) { ProcessItems(nameRegex, typeRegex, ((ContentFolder)contentItem).Children, matches); } else if (contentItem.GetType().Name.Equals("FileItem")) { } else { if (nameRegex.Match(contentItem.ShortName).Success && typeRegex.Match(contentItem.GetType().Name).Success) { string finalName = contentItem.GetType().Name.Replace("Item", ""); matches.Add(new SearchResult { Name = contentItem.ShortName, Type = finalName, Item = contentItem }); } } } } internal void Open(object o) { switch (o) { case ContentItem contentItem: Editor.Instance.ContentEditing.Open(contentItem); break; case QuickAction quickAction: quickAction.Action(); break; case ActorNode actorNode: Editor.Instance.SceneEditing.Select(actorNode.Actor); Editor.Instance.Windows.EditWin.Viewport.FocusSelection(); break; case Actor actor: Editor.Instance.SceneEditing.Select(actor); Editor.Instance.Windows.EditWin.Viewport.FocusSelection(); break; } } /// /// The aliases to match the given type to its name. /// public static readonly Dictionary Aliases = new Dictionary { // Assets { "FlaxEditor.Content.Settings.AudioSettings", "Settings" }, { "FlaxEditor.Content.Settings.BuildSettings", "Settings" }, { "FlaxEditor.Content.Settings.GameSettings", "Settings" }, { "FlaxEditor.Content.Settings.GraphicsSettings", "Settings" }, { "FlaxEditor.Content.Settings.NetworkSettings", "Settings" }, { "FlaxEditor.Content.Settings.InputSettings", "Settings" }, { "FlaxEditor.Content.Settings.LayersAndTagsSettings", "Settings" }, { "FlaxEditor.Content.Settings.NavigationSettings", "Settings" }, { "FlaxEditor.Content.Settings.LocalizationSettings", "Settings" }, { "FlaxEditor.Content.Settings.PhysicsSettings", "Settings" }, { "FlaxEditor.Content.Settings.TimeSettings", "Settings" }, { "FlaxEditor.Content.Settings.UWPPlatformSettings", "Settings" }, { "FlaxEditor.Content.Settings.WindowsPlatformSettings", "Settings" }, }; } }