// Copyright (c) Wojciech Figat. All rights reserved. using System.Collections.Generic; using FlaxEditor.Content; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Modules; using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.Windows.Search { /// /// The content finder popup. Allows to search for project items and quickly navigate to. /// /// [HideInEditor] internal class ContentFinder : ContextMenuBase { private Panel _resultPanel; private TextBox _searchBox; private SearchItem _selectedItem; private List _matchedItems = new List(); /// /// Gets or sets the height per item. /// public float ItemHeight { get; set; } = 20; /// /// Gets or sets the number of item to show. /// public int VisibleItemCount { get; set; } = 12; /// /// Gets or sets the selected item. /// internal SearchItem SelectedItem { get => _selectedItem; set { if (value == _selectedItem || (value != null && !_matchedItems.Contains(value))) return; // Restore the previous selected item to the non-selected color if (_selectedItem != null) { _selectedItem.BackgroundColor = Color.Transparent; } _selectedItem = value; if (_selectedItem != null) { _selectedItem.BackgroundColor = Style.Current.BackgroundSelected; if (_matchedItems.Count > VisibleItemCount) { _selectedItem.Focus(); _resultPanel.ScrollViewTo(_selectedItem, true); } } } } /// /// Gets actual matched item list. /// internal bool Hand; /// /// Initializes a new instance of the class. /// /// The finder width. public ContentFinder(float width = 440.0f) { Size = new Float2(width, TextBox.DefaultHeight + 2.0f); _searchBox = new TextBox { X = 1, Y = 1, Width = width - 2.0f, WatermarkText = "Type to search...", Parent = this }; _searchBox.TextChanged += OnTextChanged; _resultPanel = new Panel { Location = new Float2(1, _searchBox.Height + 1), Size = new Float2(width - 2.0f, Height - (_searchBox.Height + 1 + 1)), Parent = this }; } private void OnTextChanged() { _matchedItems.Clear(); SelectedItem = null; var results = Editor.Instance.ContentFinding.Search(_searchBox.Text); BuildList(results); } private void BuildList(List items) { _resultPanel.DisposeChildren(); LockChildrenRecursive(); var dpiScale = DpiScale; var window = RootWindow.Window; if (items.Count == 0) { Height = _searchBox.Height + 1; _resultPanel.ScrollBars = ScrollBars.None; window.ClientSize = new Float2(window.ClientSize.X, Height * dpiScale); UnlockChildrenRecursive(); PerformLayout(); return; } // Setup items container if (items.Count <= VisibleItemCount) { Height = 1 + _searchBox.Height + 1 + ItemHeight * items.Count; _resultPanel.ScrollBars = ScrollBars.None; } else { Height = 1 + _searchBox.Height + 1 + ItemHeight * VisibleItemCount; _resultPanel.ScrollBars = ScrollBars.Vertical; } _resultPanel.Height = ItemHeight * VisibleItemCount; var itemsWidth = _resultPanel.GetClientArea().Width; var itemHeight = ItemHeight; // Spawn items for (var i = 0; i < items.Count; i++) { var item = items[i]; SearchItem searchItem; if (item.Item is ContentItem contentItem) searchItem = new ContentSearchItem(item.Name, item.Type, contentItem, this, itemsWidth, itemHeight); else searchItem = new SearchItem(item.Name, item.Type, item.Item, this, itemsWidth, itemHeight); searchItem.Y = i * itemHeight; searchItem.Parent = _resultPanel; _matchedItems.Add(searchItem); } window.ClientSize = new Float2(window.ClientSize.X, Height * dpiScale); UnlockChildrenRecursive(); PerformLayout(); } /// public override void Show(Control parent, Float2 location, ContextMenuDirection? direction = null) { base.Show(parent, location, direction); // Setup _resultPanel.ScrollViewTo(Float2.Zero); // Select the text in the search bar so that the user can just start typing _searchBox.SelectAll(); _searchBox.Focus(); } /// public override void Update(float delta) { Hand = false; base.Update(delta); } /// public override bool OnKeyDown(KeyboardKeys key) { switch (key) { case KeyboardKeys.ArrowDown: case KeyboardKeys.ArrowUp: { if (_matchedItems.Count == 0) return true; var focusedIndex = _matchedItems.IndexOf(_selectedItem); int delta = key == KeyboardKeys.ArrowDown ? -1 : 1; int nextIndex = Mathf.Wrap(focusedIndex - delta, 0, _matchedItems.Count - 1); var nextItem = _matchedItems[nextIndex]; SelectedItem = nextItem; return true; } case KeyboardKeys.Return: { if (_selectedItem != null) { Hide(); Editor.Instance.ContentFinding.Open(_selectedItem.Item); } else if (_selectedItem == null && _searchBox.TextLength != 0 && _matchedItems.Count != 0) { Hide(); Editor.Instance.ContentFinding.Open(_matchedItems[0].Item); } return true; } case KeyboardKeys.Escape: { Hide(); return true; } case KeyboardKeys.Backspace: { // Alow the user to quickly focus the searchbar if (_searchBox != null && !_searchBox.IsFocused) { _searchBox.Focus(); _searchBox.SelectAll(); return true; } break; } } return base.OnKeyDown(key); } } }