Add debug commands search popup to Output Log
This commit is contained in:
@@ -98,6 +98,16 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
/// </summary>
|
||||
public List<Window> ExternalPopups = new List<Window>();
|
||||
|
||||
/// <summary>
|
||||
/// Optional flag that can disable popup visibility based on window focus and use external control via Hide.
|
||||
/// </summary>
|
||||
public bool UseVisibilityControl = true;
|
||||
|
||||
/// <summary>
|
||||
/// Optional flag that can disable popup input capturing. Useful for transparent or visual-only popups.
|
||||
/// </summary>
|
||||
public bool UseInput = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContextMenuBase"/> class.
|
||||
/// </summary>
|
||||
@@ -230,8 +240,8 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
desc.HasBorder = false;
|
||||
desc.SupportsTransparency = false;
|
||||
desc.ShowInTaskbar = false;
|
||||
desc.ActivateWhenFirstShown = true;
|
||||
desc.AllowInput = true;
|
||||
desc.ActivateWhenFirstShown = UseInput;
|
||||
desc.AllowInput = UseInput;
|
||||
desc.AllowMinimize = false;
|
||||
desc.AllowMaximize = false;
|
||||
desc.AllowDragAndDrop = false;
|
||||
@@ -240,8 +250,11 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
desc.HasSizingFrame = false;
|
||||
OnWindowCreating(ref desc);
|
||||
_window = Platform.CreateWindow(ref desc);
|
||||
_window.GotFocus += OnWindowGotFocus;
|
||||
_window.LostFocus += OnWindowLostFocus;
|
||||
if (UseVisibilityControl)
|
||||
{
|
||||
_window.GotFocus += OnWindowGotFocus;
|
||||
_window.LostFocus += OnWindowLostFocus;
|
||||
}
|
||||
|
||||
// Attach to the window
|
||||
_parentCM = parent as ContextMenuBase;
|
||||
@@ -253,9 +266,12 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
return;
|
||||
_window.Show();
|
||||
PerformLayout();
|
||||
_previouslyFocused = parentWin.FocusedControl;
|
||||
Focus();
|
||||
OnShow();
|
||||
if (UseVisibilityControl)
|
||||
{
|
||||
_previouslyFocused = parentWin.FocusedControl;
|
||||
Focus();
|
||||
OnShow();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -508,7 +524,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
base.Update(deltaTime);
|
||||
|
||||
// Let root context menu to check if none of the popup windows
|
||||
if (_parentCM == null && !IsForeground)
|
||||
if (_parentCM == null && UseVisibilityControl && !IsForeground)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ namespace FlaxEditor.GUI
|
||||
/// </summary>
|
||||
public event Action<Item> Clicked;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when items gets focused.
|
||||
/// </summary>
|
||||
public event Action<Item> Focused;
|
||||
|
||||
/// <summary>
|
||||
/// The tint color of the text.
|
||||
/// </summary>
|
||||
@@ -141,6 +146,10 @@ namespace FlaxEditor.GUI
|
||||
protected virtual void GetTextRect(out Rectangle rect)
|
||||
{
|
||||
rect = new Rectangle(2, 0, Width - 4, Height);
|
||||
|
||||
// Indent for drop panel items is handled by drop panel margin
|
||||
if (Parent is not DropPanel)
|
||||
rect.Location += new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -155,10 +164,6 @@ namespace FlaxEditor.GUI
|
||||
if (IsMouseOver || IsFocused)
|
||||
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundHighlighted);
|
||||
|
||||
// Indent for drop panel items is handled by drop panel margin
|
||||
if (Parent is not DropPanel)
|
||||
textRect.Location += new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0);
|
||||
|
||||
// Draw all highlights
|
||||
if (_highlights != null)
|
||||
{
|
||||
@@ -207,6 +212,14 @@ namespace FlaxEditor.GUI
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnGotFocus()
|
||||
{
|
||||
base.OnGotFocus();
|
||||
|
||||
Focused?.Invoke(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Compare(Control other)
|
||||
{
|
||||
@@ -227,6 +240,7 @@ namespace FlaxEditor.GUI
|
||||
private readonly Panel _scrollPanel;
|
||||
private List<DropPanel> _categoryPanels;
|
||||
private bool _waitingForInput;
|
||||
private string _customSearch;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when any item in this popup menu gets clicked.
|
||||
@@ -290,12 +304,13 @@ namespace FlaxEditor.GUI
|
||||
|
||||
LockChildrenRecursive();
|
||||
|
||||
var searchText = _searchBox?.Text ?? _customSearch;
|
||||
var items = ItemsPanel.Children;
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
if (items[i] is Item item)
|
||||
{
|
||||
item.UpdateFilter(_searchBox.Text);
|
||||
item.UpdateFilter(searchText);
|
||||
item.UpdateScore();
|
||||
}
|
||||
}
|
||||
@@ -309,13 +324,13 @@ namespace FlaxEditor.GUI
|
||||
{
|
||||
if (category.Children[j] is Item item2)
|
||||
{
|
||||
item2.UpdateFilter(_searchBox.Text);
|
||||
item2.UpdateFilter(searchText);
|
||||
item2.UpdateScore();
|
||||
anyVisible |= item2.Visible;
|
||||
}
|
||||
}
|
||||
category.Visible = anyVisible;
|
||||
if (string.IsNullOrEmpty(_searchBox.Text))
|
||||
if (string.IsNullOrEmpty(searchText))
|
||||
category.Close(false);
|
||||
else
|
||||
category.Open(false);
|
||||
@@ -326,8 +341,8 @@ namespace FlaxEditor.GUI
|
||||
|
||||
UnlockChildrenRecursive();
|
||||
PerformLayout(true);
|
||||
_searchBox.Focus();
|
||||
TextChanged?.Invoke(_searchBox.Text);
|
||||
_searchBox?.Focus();
|
||||
TextChanged?.Invoke(searchText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -359,6 +374,14 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all added items.
|
||||
/// </summary>
|
||||
public void ClearItems()
|
||||
{
|
||||
ItemsPanel.DisposeChildren();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the items list (by item name by default).
|
||||
/// </summary>
|
||||
@@ -372,6 +395,34 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Focuses and scroll to the given item to be selected.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to select.</param>
|
||||
public void SelectItem(Item item)
|
||||
{
|
||||
item.Focus();
|
||||
ScrollViewTo(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies custom search text query on the items list. Works even if search field is disabled
|
||||
/// </summary>
|
||||
/// <param name="text">The custom search text. Null to clear search.</param>
|
||||
public void Search(string text)
|
||||
{
|
||||
if (_searchBox != null)
|
||||
{
|
||||
_searchBox.SetText(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
_customSearch = text;
|
||||
if (VisibleInHierarchy)
|
||||
OnSearchFilterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the item to the view and registers for the click event.
|
||||
/// </summary>
|
||||
@@ -453,6 +504,8 @@ namespace FlaxEditor.GUI
|
||||
_searchBox?.Clear();
|
||||
UnlockChildrenRecursive();
|
||||
PerformLayout(true);
|
||||
if (_customSearch != null)
|
||||
OnSearchFilterChanged();
|
||||
}
|
||||
|
||||
private List<Item> GetVisibleItems()
|
||||
|
||||
@@ -129,7 +129,57 @@ namespace FlaxEditor.Windows
|
||||
/// </summary>
|
||||
private class CommandLineBox : TextBox
|
||||
{
|
||||
private sealed class Item : ItemsListContextMenu.Item
|
||||
{
|
||||
public CommandLineBox Owner;
|
||||
|
||||
public Item()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void GetTextRect(out Rectangle rect)
|
||||
{
|
||||
rect = new Rectangle(2, 0, Width - 4, Height);
|
||||
}
|
||||
|
||||
public override bool OnCharInput(char c)
|
||||
{
|
||||
if (Owner != null && (!Owner._searchPopup?.Visible ?? true))
|
||||
{
|
||||
// Redirect input into search textbox while typing and using command history
|
||||
Owner.Set(Owner.Text + c);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.Delete:
|
||||
case KeyboardKeys.Backspace:
|
||||
if (Owner != null && (!Owner._searchPopup?.Visible ?? true))
|
||||
{
|
||||
// Redirect input into search textbox while typing and using command history
|
||||
Owner.OnKeyDown(key);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Owner = null;
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
private OutputLogWindow _window;
|
||||
private ItemsListContextMenu _searchPopup;
|
||||
private bool _isSettingText;
|
||||
|
||||
public CommandLineBox(float x, float y, float width, OutputLogWindow window)
|
||||
: base(false, x, y, width)
|
||||
@@ -138,6 +188,100 @@ namespace FlaxEditor.Windows
|
||||
_window = window;
|
||||
}
|
||||
|
||||
private void Set(string command)
|
||||
{
|
||||
_isSettingText = true;
|
||||
SetText(command);
|
||||
SetSelection(command.Length);
|
||||
_isSettingText = false;
|
||||
}
|
||||
|
||||
private void ShowPopup(ref ItemsListContextMenu cm, IEnumerable<string> commands, string searchText = null)
|
||||
{
|
||||
if (cm == null)
|
||||
cm = new ItemsListContextMenu(180, 220, false);
|
||||
else
|
||||
cm.ClearItems();
|
||||
|
||||
// Add items
|
||||
ItemsListContextMenu.Item lastItem = null;
|
||||
foreach (var command in commands)
|
||||
{
|
||||
cm.AddItem(lastItem = new Item
|
||||
{
|
||||
Name = command,
|
||||
Owner = this,
|
||||
});
|
||||
lastItem.Focused += item =>
|
||||
{
|
||||
// Set command
|
||||
Set(item.Name);
|
||||
};
|
||||
}
|
||||
cm.ItemClicked += item =>
|
||||
{
|
||||
// Execute command
|
||||
OnKeyDown(KeyboardKeys.Return);
|
||||
};
|
||||
|
||||
// Setup popup
|
||||
var count = commands.Count();
|
||||
var totalHeight = count * lastItem.Height + cm.ItemsPanel.Margin.Height + cm.ItemsPanel.Spacing * (count - 1);
|
||||
cm.Height = 220;
|
||||
if (cm.Height > totalHeight)
|
||||
cm.Height = totalHeight; // Limit popup height if list is small
|
||||
if (searchText != null)
|
||||
{
|
||||
cm.SortItems();
|
||||
cm.Search(searchText);
|
||||
cm.UseVisibilityControl = false;
|
||||
cm.UseInput = false;
|
||||
}
|
||||
|
||||
// Show popup
|
||||
cm.Show(this, Float2.Zero, ContextMenuDirection.RightUp);
|
||||
cm.ScrollViewTo(lastItem);
|
||||
if (searchText != null)
|
||||
{
|
||||
RootWindow.Window.LostFocus += OnRootWindowLostFocus;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastItem.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRootWindowLostFocus()
|
||||
{
|
||||
// Prevent popup from staying active when editor window looses focus
|
||||
_searchPopup?.Hide();
|
||||
if (RootWindow?.Window != null)
|
||||
RootWindow.Window.LostFocus -= OnRootWindowLostFocus;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnTextChanged()
|
||||
{
|
||||
base.OnTextChanged();
|
||||
|
||||
// Skip when editing text from code
|
||||
if (_isSettingText)
|
||||
return;
|
||||
|
||||
// Show commands search popup based on current text input
|
||||
var text = Text.Trim();
|
||||
if (text.Length != 0)
|
||||
{
|
||||
DebugCommands.Search(text, out var matches);
|
||||
if (matches.Length != 0)
|
||||
{
|
||||
ShowPopup(ref _searchPopup, matches, text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_searchPopup?.Hide();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
@@ -146,6 +290,7 @@ namespace FlaxEditor.Windows
|
||||
case KeyboardKeys.Return:
|
||||
{
|
||||
// Run command
|
||||
_searchPopup?.Hide();
|
||||
var command = Text.Trim();
|
||||
if (command.Length == 0)
|
||||
return true;
|
||||
@@ -175,8 +320,7 @@ namespace FlaxEditor.Windows
|
||||
else if (matches.Length == 1)
|
||||
{
|
||||
// Exact match
|
||||
SetText(matches[0]);
|
||||
SetSelection(Text.Length);
|
||||
Set(matches[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -202,57 +346,60 @@ namespace FlaxEditor.Windows
|
||||
if (sharedLength > minLength)
|
||||
{
|
||||
// Use the largest shared part of all matches
|
||||
SetText(matches[0].Substring(0, sharedLength));
|
||||
SetSelection(sharedLength);
|
||||
Set(matches[0].Substring(0, sharedLength));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case KeyboardKeys.ArrowUp:
|
||||
{
|
||||
if (TextLength == 0)
|
||||
if (_searchPopup != null && _searchPopup.Visible)
|
||||
{
|
||||
// Route navigation to active popup
|
||||
var focusedItem = _searchPopup.RootWindow.FocusedControl as Item;
|
||||
if (focusedItem == null)
|
||||
_searchPopup.SelectItem((Item)_searchPopup.ItemsPanel.Children.Last());
|
||||
else
|
||||
_searchPopup.OnKeyDown(key);
|
||||
}
|
||||
else if (TextLength == 0)
|
||||
{
|
||||
if (_window._commandHistory != null && _window._commandHistory.Count != 0)
|
||||
{
|
||||
// Show command history popup
|
||||
var cm = new ItemsListContextMenu(180, 220, false);
|
||||
ItemsListContextMenu.Item lastItem = null;
|
||||
var count = _window._commandHistory.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var command = _window._commandHistory[i];
|
||||
cm.AddItem(lastItem = new ItemsListContextMenu.Item
|
||||
{
|
||||
Name = command,
|
||||
});
|
||||
}
|
||||
cm.ItemClicked += item =>
|
||||
{
|
||||
SetText(item.Name);
|
||||
SetSelection(Text.Length);
|
||||
};
|
||||
var totalHeight = count * lastItem.Height + cm.ItemsPanel.Margin.Height + cm.ItemsPanel.Spacing * (count - 1);
|
||||
if (cm.Height > totalHeight)
|
||||
cm.Height = totalHeight; // Limit popup height if history is small
|
||||
cm.Show(this, Float2.Zero, ContextMenuDirection.RightUp);
|
||||
lastItem.Focus();
|
||||
cm.ScrollViewTo(lastItem);
|
||||
_searchPopup?.Hide();
|
||||
ItemsListContextMenu cm = null;
|
||||
ShowPopup(ref cm, _window._commandHistory);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: focus similar commands (via popup)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case KeyboardKeys.ArrowDown:
|
||||
{
|
||||
// Ignore
|
||||
if (_searchPopup != null && _searchPopup.Visible)
|
||||
{
|
||||
// Route navigation to active popup
|
||||
var focusedItem = _searchPopup.RootWindow.FocusedControl as Item;
|
||||
if (focusedItem == null)
|
||||
_searchPopup.SelectItem((Item)_searchPopup.ItemsPanel.Children.First());
|
||||
else
|
||||
_searchPopup.OnKeyDown(key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_searchPopup?.Dispose();
|
||||
_searchPopup = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
private InterfaceOptions.TimestampsFormats _timestampsFormats;
|
||||
|
||||
Reference in New Issue
Block a user