Add debug commands search popup to Output Log

This commit is contained in:
Wojtek Figat
2024-10-24 19:26:11 +02:00
parent 5904c0eea5
commit 87d35f0314
3 changed files with 265 additions and 49 deletions

View File

@@ -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();
}

View File

@@ -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()

View File

@@ -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;