Moved view navigation out of Toplevel and into Application (via ViewNavigation static class).

This commit is contained in:
Tig
2024-07-24 12:56:55 -06:00
parent 4a56b84324
commit 0c56dfeb5a
4 changed files with 187 additions and 163 deletions

View File

@@ -330,7 +330,7 @@ public static partial class Application // Keyboard handling
() =>
{
// TODO: Move this method to Application.Navigation.cs
Current.MoveNextView ();
ViewNavigation.MoveNextView ();
return true;
}
@@ -341,7 +341,7 @@ public static partial class Application // Keyboard handling
() =>
{
// TODO: Move this method to Application.Navigation.cs
Current.MovePreviousView ();
ViewNavigation.MovePreviousView ();
return true;
}
@@ -352,7 +352,7 @@ public static partial class Application // Keyboard handling
() =>
{
// TODO: Move this method to Application.Navigation.cs
Current.MoveNextViewOrTop ();
ViewNavigation.MoveNextViewOrTop ();
return true;
}
@@ -363,7 +363,7 @@ public static partial class Application // Keyboard handling
() =>
{
// TODO: Move this method to Application.Navigation.cs
Current.MovePreviousViewOrTop ();
ViewNavigation.MovePreviousViewOrTop ();
return true;
}

View File

@@ -1,6 +1,169 @@
#nullable enable
using static Terminal.Gui.View;
using System.Reflection;
namespace Terminal.Gui;
internal static class ViewNavigation
{
/// <summary>
/// Gets the deepest focused subview of the specified <paramref name="view"/>.
/// </summary>
/// <param name="view"></param>
/// <returns></returns>
internal static View GetDeepestFocusedSubview (View view)
{
if (view is null)
{
return null;
}
foreach (View v in view.Subviews)
{
if (v.HasFocus)
{
return GetDeepestFocusedSubview (v);
}
}
return view;
}
/// <summary>
/// Sets the focus to the next view in the <see cref="TabIndexes"/> list. If the last view is focused, the first view is focused.
/// </summary>
/// <param name="viewsInTabIndexes"></param>
/// <param name="direction"></param>
internal static void FocusNearestView (IEnumerable<View>? viewsInTabIndexes, NavigationDirection direction)
{
if (viewsInTabIndexes is null)
{
return;
}
var found = false;
var focusProcessed = false;
var idx = 0;
foreach (View v in viewsInTabIndexes)
{
if (v == Application.Current)
{
found = true;
}
if (found && v != Application.Current)
{
if (direction == NavigationDirection.Forward)
{
Application.Current.SuperView?.FocusNext ();
}
else
{
Application.Current.SuperView?.FocusPrev ();
}
focusProcessed = true;
if (Application.Current.SuperView?.Focused is { } && Application.Current.SuperView.Focused != Application.Current)
{
return;
}
}
else if (found && !focusProcessed && idx == viewsInTabIndexes.Count () - 1)
{
viewsInTabIndexes.ToList () [0].SetFocus ();
}
idx++;
}
}
/// <summary>
/// Moves the focus to
/// </summary>
internal static void MoveNextView ()
{
View old = GetDeepestFocusedSubview (Application.Current.Focused);
if (!Application.Current.FocusNext ())
{
Application.Current.FocusNext ();
}
if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
{
old?.SetNeedsDisplay ();
Application.Current.Focused?.SetNeedsDisplay ();
}
else
{
FocusNearestView (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward);
}
}
internal static void MoveNextViewOrTop ()
{
if (Application.OverlappedTop is null)
{
Toplevel top = Application.Current.Modal ? Application.Current : Application.Top;
top.FocusNext ();
if (top.Focused is null)
{
top.FocusNext ();
}
top.SetNeedsDisplay ();
Application.BringOverlappedTopToFront ();
}
else
{
Application.OverlappedMoveNext ();
}
}
internal static void MovePreviousView ()
{
View old = GetDeepestFocusedSubview (Application.Current.Focused);
if (!Application.Current.FocusPrev ())
{
Application.Current.FocusPrev ();
}
if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
{
old?.SetNeedsDisplay ();
Application.Current.Focused?.SetNeedsDisplay ();
}
else
{
FocusNearestView (Application.Current.SuperView?.TabIndexes?.Reverse (), NavigationDirection.Backward);
}
}
internal static void MovePreviousViewOrTop ()
{
if (Application.OverlappedTop is null)
{
Toplevel top = Application.Current.Modal ? Application.Current : Application.Top;
top.FocusPrev ();
if (top.Focused is null)
{
top.FocusPrev ();
}
top.SetNeedsDisplay ();
Application.BringOverlappedTopToFront ();
}
else
{
Application.OverlappedMovePrevious ();
}
}
}
public static partial class Application // App-level View Navigation
{

View File

@@ -110,6 +110,25 @@ public class KeyBindings
}
}
/// <summary>
/// <para>Adds a new key combination that will trigger the commands in <paramref name="commands"/>.</para>
/// <para>
/// If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
/// <paramref name="commands"/>.
/// </para>
/// </summary>
/// <remarks>
/// Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
/// focus to another view and perform multiple commands there).
/// </remarks>
/// <param name="key">The key to check.</param>
/// <param name="scope">The scope for the command.</param>
/// <param name="commands">
/// The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
/// multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
/// consumed if any took effect.
/// </param>
public void Add (Key key, KeyBindingScope scope, params Command [] commands)
{
if (BoundView is { } && scope.FastHasFlags (KeyBindingScope.Application))

View File

@@ -411,165 +411,7 @@ public partial class Toplevel : View
/// <inheritdoc/>
public override bool OnLeave (View view) { return MostFocused?.OnLeave (view) ?? base.OnLeave (view); }
/// <summary>
/// Sets the focus to the next view in the <see cref="TabIndexes"/> list. If the last view is focused, the first view is focused.
/// </summary>
/// <param name="viewsInTabIndexes"></param>
/// <param name="direction"></param>
private void FocusNearestView (IEnumerable<View> viewsInTabIndexes, NavigationDirection direction)
{
if (viewsInTabIndexes is null)
{
return;
}
var found = false;
var focusProcessed = false;
var idx = 0;
foreach (View v in viewsInTabIndexes)
{
if (v == this)
{
found = true;
}
if (found && v != this)
{
if (direction == NavigationDirection.Forward)
{
SuperView?.FocusNext ();
}
else
{
SuperView?.FocusPrev ();
}
focusProcessed = true;
if (SuperView.Focused is { } && SuperView.Focused != this)
{
return;
}
}
else if (found && !focusProcessed && idx == viewsInTabIndexes.Count () - 1)
{
viewsInTabIndexes.ToList () [0].SetFocus ();
}
idx++;
}
}
/// <summary>
/// Gets the deepest focused subview of the specified <paramref name="view"/>.
/// </summary>
/// <param name="view"></param>
/// <returns></returns>
private View GetDeepestFocusedSubview (View view)
{
if (view is null)
{
return null;
}
foreach (View v in view.Subviews)
{
if (v.HasFocus)
{
return GetDeepestFocusedSubview (v);
}
}
return view;
}
/// <summary>
/// Moves the focus to
/// </summary>
internal void MoveNextView ()
{
View old = GetDeepestFocusedSubview (Focused);
if (!FocusNext ())
{
FocusNext ();
}
if (old != Focused && old != Focused?.Focused)
{
old?.SetNeedsDisplay ();
Focused?.SetNeedsDisplay ();
}
else
{
FocusNearestView (SuperView?.TabIndexes, NavigationDirection.Forward);
}
}
internal void MoveNextViewOrTop ()
{
if (Application.OverlappedTop is null)
{
Toplevel top = Modal ? this : Application.Top;
top.FocusNext ();
if (top.Focused is null)
{
top.FocusNext ();
}
top.SetNeedsDisplay ();
Application.BringOverlappedTopToFront ();
}
else
{
Application.OverlappedMoveNext ();
}
}
internal void MovePreviousView ()
{
View old = GetDeepestFocusedSubview (Focused);
if (!FocusPrev ())
{
FocusPrev ();
}
if (old != Focused && old != Focused?.Focused)
{
old?.SetNeedsDisplay ();
Focused?.SetNeedsDisplay ();
}
else
{
FocusNearestView (SuperView?.TabIndexes?.Reverse (), NavigationDirection.Backward);
}
}
internal void MovePreviousViewOrTop ()
{
if (Application.OverlappedTop is null)
{
Toplevel top = Modal ? this : Application.Top;
top.FocusPrev ();
if (top.Focused is null)
{
top.FocusPrev ();
}
top.SetNeedsDisplay ();
Application.BringOverlappedTopToFront ();
}
else
{
Application.OverlappedMovePrevious ();
}
}
#endregion
#region Size / Position Management