Add Tab navigation for Editor UI

This commit is contained in:
Wojciech Figat
2021-12-21 18:13:45 +01:00
parent c178afdf6b
commit af75751bf1
15 changed files with 293 additions and 195 deletions

View File

@@ -62,7 +62,7 @@ namespace FlaxEditor.Content.Create
Offsets = new Margin(-ButtonsWidth - ButtonsMargin, ButtonsWidth, -ButtonsHeight - ButtonsMargin, ButtonsHeight),
Parent = this
};
createButton.Clicked += OnCreate;
createButton.Clicked += OnSubmit;
var cancelButton = new Button
{
Text = "Cancel",
@@ -89,27 +89,12 @@ namespace FlaxEditor.Content.Create
_settingsEditor.Select(_entry.Settings);
}
private void OnCreate()
/// <inheritdoc />
public override void OnSubmit()
{
Editor.Instance.ContentImporting.LetThemBeCreatedxD(_entry);
Close(DialogResult.OK);
}
private void OnCancel()
{
Close(DialogResult.Cancel);
}
private void OnSelectedChanged(List<TreeNode> before, List<TreeNode> after)
{
var selection = new List<object>(after.Count);
for (int i = 0; i < after.Count; i++)
{
if (after[i].Tag is CreateFileEntry fileEntry && fileEntry.HasSettings)
selection.Add(fileEntry.Settings);
}
_settingsEditor.Select(selection);
base.OnSubmit();
}
/// <inheritdoc />

View File

@@ -83,7 +83,7 @@ namespace FlaxEditor.Content.Import
Offsets = new Margin(-ButtonsWidth - ButtonsMargin, ButtonsWidth, -ButtonsHeight - ButtonsMargin, ButtonsHeight),
Parent = this
};
importButton.Clicked += OnImport;
importButton.Clicked += OnSubmit;
var cancelButton = new Button
{
Text = "Cancel",
@@ -223,24 +223,6 @@ namespace FlaxEditor.Content.Import
}
}
private void OnImport()
{
var entries = new List<ImportFileEntry>(_rootNode.ChildrenCount);
for (int i = 0; i < _rootNode.ChildrenCount; i++)
{
if (_rootNode.Children[i].Tag is ImportFileEntry fileEntry)
entries.Add(fileEntry);
}
Editor.Instance.ContentImporting.LetThemBeImportedxD(entries);
Close(DialogResult.OK);
}
private void OnCancel()
{
Close(DialogResult.Cancel);
}
private void OnSelectedChanged(List<TreeNode> before, List<TreeNode> after)
{
var selection = new List<object>(after.Count);
@@ -253,6 +235,20 @@ namespace FlaxEditor.Content.Import
_settingsEditor.Select(selection);
}
/// <inheritdoc />
public override void OnSubmit()
{
var entries = new List<ImportFileEntry>(_rootNode.ChildrenCount);
for (int i = 0; i < _rootNode.ChildrenCount; i++)
{
if (_rootNode.Children[i].Tag is ImportFileEntry fileEntry)
entries.Add(fileEntry);
}
Editor.Instance.ContentImporting.LetThemBeImportedxD(entries);
base.OnSubmit();
}
/// <inheritdoc />
protected override void SetupWindowSettings(ref CreateWindowSettings settings)
{
@@ -261,24 +257,5 @@ namespace FlaxEditor.Content.Import
settings.MinimumSize = new Vector2(300, 400);
settings.HasSizingFrame = true;
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (base.OnKeyDown(key))
return true;
switch (key)
{
case KeyboardKeys.Escape:
OnCancel();
return true;
case KeyboardKeys.Return:
OnImport();
return true;
}
return false;
}
}
}

View File

@@ -612,6 +612,15 @@ namespace FlaxEditor.Content
return result;
}
/// <inheritdoc />
public override void NavigationFocus()
{
base.NavigationFocus();
if (IsFocused)
(Parent as ContentView)?.Select(this);
}
/// <inheritdoc />
public override void Draw()
{
@@ -738,6 +747,15 @@ namespace FlaxEditor.Content
base.OnMouseLeave();
}
/// <inheritdoc />
public override void OnSubmit()
{
// Open
(Parent as ContentView).OnItemDoubleClick(this);
base.OnSubmit();
}
/// <inheritdoc />
public override int Compare(Control other)
{

View File

@@ -191,7 +191,7 @@ namespace FlaxEditor.CustomEditors.Editors
var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14);
// Draw frame
Render2D.DrawRectangle(frameRect, isEnabled && IsMouseOver ? style.BorderHighlighted : style.BorderNormal);
Render2D.DrawRectangle(frameRect, isEnabled && (IsMouseOver || IsNavFocused) ? style.BorderHighlighted : style.BorderNormal);
// Check if has item selected
if (isSelected)
@@ -331,6 +331,16 @@ namespace FlaxEditor.CustomEditors.Editors
return base.OnMouseDoubleClick(location, button);
}
/// <inheritdoc />
public override void OnSubmit()
{
base.OnSubmit();
// Picker dropdown menu
if (_supportsPickDropDown)
ShowDropDownMenu();
}
private void DoDrag()
{
// Do the drag drop operation if has selected element

View File

@@ -374,6 +374,72 @@ namespace FlaxEditor.GUI
}
}
/// <summary>
/// Shows the context menu popup.
/// </summary>
protected void ShowPopup()
{
// Ensure to have valid menu
if (_popupMenu == null)
{
_popupMenu = OnCreatePopup();
_popupMenu.MaximumItemsInViewCount = MaximumItemsInViewCount;
// Bind events
_popupMenu.VisibleChanged += cm =>
{
var win = Root;
_blockPopup = win != null && new Rectangle(Vector2.Zero, Size).Contains(PointFromWindow(win.MousePosition));
if (!_blockPopup)
Focus();
};
_popupMenu.ButtonClicked += btn =>
{
OnItemClicked((int)btn.Tag);
_popupMenu?.Hide();
};
}
// Check if menu hs been already shown
if (_popupMenu.Visible)
{
_popupMenu.Hide();
return;
}
PopupShowing?.Invoke(this);
// Check if has any items
if (_items.Count > 0)
{
// Setup items list
var itemControls = _popupMenu.Items.ToArray();
foreach (var e in itemControls)
e.Dispose();
if (Sorted)
_items.Sort();
var style = Style.Current;
for (int i = 0; i < _items.Count; i++)
{
var btn = _popupMenu.AddButton(_items[i]);
if (_selectedIndices.Contains(i))
{
btn.Icon = style.CheckBoxTick;
}
if (_tooltips != null && _tooltips.Length > i)
{
btn.TooltipText = _tooltips[i];
}
btn.Tag = i;
}
// Show dropdown list
_popupMenu.MinimumWidth = Width;
_popupMenu.Show(this, new Vector2(1, Height));
}
}
/// <summary>
/// Creates the popup menu.
/// </summary>
@@ -426,7 +492,7 @@ namespace FlaxEditor.GUI
borderColor = BorderColorSelected;
arrowColor = ArrowColorSelected;
}
else if (IsMouseOver)
else if (IsMouseOver || IsNavFocused)
{
backgroundColor = BackgroundColorHighlighted;
borderColor = BorderColorHighlighted;
@@ -478,11 +544,10 @@ namespace FlaxEditor.GUI
/// <inheritdoc />
public override bool OnMouseDown(Vector2 location, MouseButton button)
{
// Check mouse buttons
if (button == MouseButton.Left)
{
// Set flag
_mouseDown = true;
Focus();
}
return base.OnMouseDown(location, button);
@@ -491,72 +556,10 @@ namespace FlaxEditor.GUI
/// <inheritdoc />
public override bool OnMouseUp(Vector2 location, MouseButton button)
{
// Check flags
if (_mouseDown && !_blockPopup)
{
// Clear flag
_mouseDown = false;
// Ensure to have valid menu
if (_popupMenu == null)
{
_popupMenu = OnCreatePopup();
_popupMenu.MaximumItemsInViewCount = MaximumItemsInViewCount;
// Bind events
_popupMenu.VisibleChanged += cm =>
{
var win = Root;
_blockPopup = win != null && new Rectangle(Vector2.Zero, Size).Contains(PointFromWindow(win.MousePosition));
if (!_blockPopup)
Focus();
};
_popupMenu.ButtonClicked += btn =>
{
OnItemClicked((int)btn.Tag);
_popupMenu?.Hide();
};
}
// Check if menu hs been already shown
if (_popupMenu.Visible)
{
// Hide
_popupMenu.Hide();
return true;
}
PopupShowing?.Invoke(this);
// Check if has any items
if (_items.Count > 0)
{
// Setup items list
var itemControls = _popupMenu.Items.ToArray();
foreach (var e in itemControls)
e.Dispose();
if (Sorted)
_items.Sort();
var style = Style.Current;
for (int i = 0; i < _items.Count; i++)
{
var btn = _popupMenu.AddButton(_items[i]);
if (_selectedIndices.Contains(i))
{
btn.Icon = style.CheckBoxTick;
}
if (_tooltips != null && _tooltips.Length > i)
{
btn.TooltipText = _tooltips[i];
}
btn.Tag = i;
}
// Show dropdown list
_popupMenu.MinimumWidth = Width;
_popupMenu.Show(this, new Vector2(1, Height));
}
ShowPopup();
}
else
{
@@ -565,5 +568,13 @@ namespace FlaxEditor.GUI
return true;
}
/// <inheritdoc />
public override void OnSubmit()
{
base.OnSubmit();
ShowPopup();
}
}
}

View File

@@ -34,7 +34,6 @@ namespace FlaxEditor.GUI.Dialogs
private Color _value;
private bool _disableEvents;
private bool _useDynamicEditing;
private bool _isClosing;
private ColorValueBox.ColorPickerEvent _onChanged;
private ColorValueBox.ColorPickerClosedEvent _onClosed;
@@ -183,7 +182,7 @@ namespace FlaxEditor.GUI.Dialogs
Text = "Cancel",
Parent = this
};
_cCancel.Clicked += OnCancelClicked;
_cCancel.Clicked += OnCancel;
// OK
_cOK = new Button(_cCancel.Left - ButtonsWidth - PickerMargin, _cCancel.Y, ButtonsWidth)
@@ -191,42 +190,12 @@ namespace FlaxEditor.GUI.Dialogs
Text = "Ok",
Parent = this
};
_cOK.Clicked += OnOkClicked;
_cOK.Clicked += OnSubmit;
// Set initial color
SelectedColor = initialValue;
}
private void OnOkClicked()
{
if (_isClosing)
return;
_isClosing = true;
// Send color event if modified
if (_value != _initialValue)
{
_onChanged?.Invoke(_value, false);
}
Close(DialogResult.OK);
}
private void OnCancelClicked()
{
if (_isClosing)
return;
_isClosing = true;
// Restore color if modified
if (_useDynamicEditing && _initialValue != _value)
{
_onChanged?.Invoke(_initialValue, false);
}
Close(DialogResult.Cancel);
}
private void OnRGBAChanged()
{
if (_disableEvents)
@@ -300,11 +269,43 @@ namespace FlaxEditor.GUI.Dialogs
protected override void OnShow()
{
// Auto cancel on lost focus
((WindowRootControl)Root).Window.LostFocus += OnCancelClicked;
((WindowRootControl)Root).Window.LostFocus += OnCancel;
base.OnShow();
}
/// <inheritdoc />
public override void OnSubmit()
{
if (_disableEvents)
return;
_disableEvents = true;
// Send color event if modified
if (_value != _initialValue)
{
_onChanged?.Invoke(_value, false);
}
base.OnSubmit();
}
/// <inheritdoc />
public override void OnCancel()
{
if (_disableEvents)
return;
_disableEvents = true;
// Restore color if modified
if (_useDynamicEditing && _initialValue != _value)
{
_onChanged?.Invoke(_initialValue, false);
}
base.OnCancel();
}
/// <inheritdoc />
public override void OnDestroy()
{
@@ -316,7 +317,7 @@ namespace FlaxEditor.GUI.Dialogs
/// <inheritdoc />
public void ClosePicker()
{
OnCancelClicked();
OnCancel();
}
}
}

View File

@@ -220,6 +220,14 @@ namespace FlaxEditor.GUI.Dialogs
}
}
/// <summary>
/// Called to cancel action and close the dialog.
/// </summary>
public virtual void OnCancel()
{
Close(DialogResult.Cancel);
}
/// <summary>
/// Closes dialog with the specified result.
/// </summary>
@@ -243,6 +251,7 @@ namespace FlaxEditor.GUI.Dialogs
/// </summary>
protected virtual void OnShow()
{
Focus();
}
/// <summary>
@@ -254,5 +263,37 @@ namespace FlaxEditor.GUI.Dialogs
{
return true;
}
/// <inheritdoc />
public override void OnSubmit()
{
base.OnSubmit();
Close(DialogResult.OK);
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (base.OnKeyDown(key))
return true;
switch (key)
{
case KeyboardKeys.Return:
if (Root?.FocusedControl != null)
Root.SubmitFocused();
else
OnSubmit();
return true;
case KeyboardKeys.Escape:
OnCancel();
return true;
case KeyboardKeys.Tab:
Root?.Navigate(NavDirection.Next);
return true;
}
return false;
}
}
}

View File

@@ -78,8 +78,6 @@ namespace FlaxEditor.GUI.Input
if (_value != value)
{
_value = value;
// Fire event
OnValueChanged();
}
}
@@ -128,16 +126,24 @@ namespace FlaxEditor.GUI.Input
var r = new Rectangle(2, 2, Width - 4, Height - 4);
Render2D.FillRectangle(r, _value);
Render2D.DrawRectangle(r, IsMouseOver ? style.BackgroundSelected : Color.Black);
Render2D.DrawRectangle(r, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);
}
/// <inheritdoc />
public override bool OnMouseUp(Vector2 location, MouseButton button)
{
Focus();
OnSubmit();
return true;
}
/// <inheritdoc />
public override void OnSubmit()
{
base.OnSubmit();
// Show color picker dialog
_currentDialog = ShowPickColorDialog?.Invoke(this, _value, OnColorChanged, OnPickerClosed);
return true;
}
private void OnColorChanged(Color color, bool sliding)

View File

@@ -110,7 +110,7 @@ namespace FlaxEditor.GUI
bool enabled = EnabledInHierarchy;
// Draw background
if (enabled && (IsMouseOver || Checked))
if (enabled && (IsMouseOver || IsNavFocused || Checked))
Render2D.FillRectangle(clientRect, Checked ? style.BackgroundSelected : _mouseDown ? style.BackgroundHighlighted : (style.LightBackground * 1.3f));
// Draw icon

View File

@@ -1103,6 +1103,15 @@ namespace FlaxEditor.GUI.Tree
Height = Mathf.Max(_headerHeight, y);
}
/// <inheritdoc />
protected override bool CanNavigateChild(Control child)
{
// Closed tree node skips navigation for hidden children
if (IsCollapsed && child is TreeNode)
return false;
return base.CanNavigateChild(child);
}
/// <inheritdoc />
protected override void OnParentChangedInternal()
{

View File

@@ -527,7 +527,7 @@ namespace FlaxEditor.Modules
Text = "OK",
Parent = this,
};
okButton.Clicked += OnOk;
okButton.Clicked += OnSubmit;
var cancelButton = new Button(okButton.Left - 54, okButton.Y, 50)
{
@@ -539,7 +539,8 @@ namespace FlaxEditor.Modules
_dialogSize = okButton.BottomRight + new Vector2(8);
}
private void OnOk()
/// <inheritdoc />
public override void OnSubmit()
{
var name = _textbox.Text;
if (name.Length == 0)
@@ -553,32 +554,11 @@ namespace FlaxEditor.Modules
return;
}
Close(DialogResult.OK);
base.OnSubmit();
var path = StringUtils.CombinePaths(Globals.ProjectCacheFolder, "Layout_" + name + ".xml");
Editor.Instance.Windows.SaveLayout(path);
}
private void OnCancel()
{
Close(DialogResult.Cancel);
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.Escape:
OnCancel();
return true;
case KeyboardKeys.Return:
OnOk();
return true;
}
return base.OnKeyDown(key);
}
}
/// <summary>

View File

@@ -117,7 +117,7 @@ namespace FlaxEditor.Tools.Terrain
Offsets = new Margin(-ButtonsWidth - ButtonsMargin, ButtonsWidth, -ButtonsHeight - ButtonsMargin, ButtonsHeight),
Parent = this
};
importButton.Clicked += OnCreate;
importButton.Clicked += OnSubmit;
var cancelButton = new Button
{
Text = "Cancel",
@@ -201,12 +201,22 @@ namespace FlaxEditor.Tools.Terrain
_isDone = true;
}
private void OnCancel()
/// <inheritdoc />
public override void OnSubmit()
{
if (_isWorking)
return;
Close(DialogResult.Cancel);
base.OnSubmit();
}
/// <inheritdoc />
public override void OnCancel()
{
if (_isWorking)
return;
base.OnCancel();
}
/// <inheritdoc />

View File

@@ -24,6 +24,11 @@ namespace FlaxEditor.Windows
/// </summary>
protected virtual bool CanOpenContentFinder => true;
/// <summary>
/// Gets a value indicating whether this window can use UI navigation (tab/enter).
/// </summary>
protected virtual bool CanUseNavigation => true;
/// <summary>
/// Initializes a new instance of the <see cref="EditorWindow"/> class.
/// </summary>
@@ -184,6 +189,32 @@ namespace FlaxEditor.Windows
#endregion
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (base.OnKeyDown(key))
return true;
switch (key)
{
case KeyboardKeys.Return:
if (CanUseNavigation && Root?.FocusedControl != null)
{
Root.SubmitFocused();
return true;
}
break;
case KeyboardKeys.Tab:
if (CanUseNavigation && Root != null)
{
Root.Navigate(NavDirection.Next);
return true;
}
break;
}
return false;
}
/// <inheritdoc />
public override void OnDestroy()
{

View File

@@ -323,6 +323,9 @@ namespace FlaxEditor.Windows
/// <inheritdoc />
protected override bool CanOpenContentFinder => false;
/// <inheritdoc />
protected override bool CanUseNavigation => false;
/// <inheritdoc />
public override void OnPlayBegin()
{
@@ -533,6 +536,22 @@ namespace FlaxEditor.Windows
Screen.CursorVisible = true;
}
/// <inheritdoc />
public override Control OnNavigate(NavDirection direction, Vector2 location, Control caller, System.Collections.Generic.List<Control> visited)
{
// Block leaking UI navigation focus outside the game window
if (IsFocused && caller != this)
{
// Pick the first UI control if game UI is not focused yet
foreach (var child in _guiRoot.Children)
{
if (child.Visible)
return child.OnNavigate(direction, Vector2.Zero, this, visited);
}
}
return null;
}
/// <inheritdoc />
public override bool OnMouseDown(Vector2 location, MouseButton button)
{