Browse Source

Added Next/PrevTabKeys.
Refactored ApplicationNavigation in prep for further work

Tig 1 year ago
parent
commit
8da833a4c6

+ 2 - 0
Terminal.Gui/Application/Application.Initialization.cs

@@ -74,6 +74,8 @@ public static partial class Application // Initialization (Init/Shutdown)
             ResetState ();
             ResetState ();
         }
         }
 
 
+        Navigation = new ();
+
         // For UnitTests
         // For UnitTests
         if (driver is { })
         if (driver is { })
         {
         {

+ 79 - 31
Terminal.Gui/Application/Application.Keyboard.cs

@@ -1,11 +1,64 @@
 #nullable enable
 #nullable enable
 using System.Text.Json.Serialization;
 using System.Text.Json.Serialization;
-using static System.Formats.Asn1.AsnWriter;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
 public static partial class Application // Keyboard handling
 public static partial class Application // Keyboard handling
 {
 {
+    private static Key _nextTabKey = Key.Empty; // Defined in config.json
+
+    /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    [JsonConverter (typeof (KeyJsonConverter))]
+    public static Key NextTabKey
+    {
+        get => _nextTabKey;
+        set
+        {
+            if (_nextTabKey != value)
+            {
+                Key oldKey = _nextTabKey;
+                _nextTabKey = value;
+
+                if (_nextTabKey == Key.Empty)
+                {
+                    KeyBindings.Remove (_nextTabKey);
+                }
+                else
+                {
+                    KeyBindings.ReplaceKey (oldKey, _nextTabKey);
+                }
+            }
+        }
+    }
+
+    private static Key _prevTabKey = Key.Empty; // Defined in config.json
+
+    /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    [JsonConverter (typeof (KeyJsonConverter))]
+    public static Key PrevTabKey
+    {
+        get => _prevTabKey;
+        set
+        {
+            if (_prevTabKey != value)
+            {
+                Key oldKey = _prevTabKey;
+                _prevTabKey = value;
+
+                if (_prevTabKey == Key.Empty)
+                {
+                    KeyBindings.Remove (_prevTabKey);
+                }
+                else
+                {
+                    KeyBindings.ReplaceKey (oldKey, _prevTabKey);
+                }
+            }
+        }
+    }
+
     private static Key _nextTabGroupKey = Key.Empty; // Defined in config.json
     private static Key _nextTabGroupKey = Key.Empty; // Defined in config.json
 
 
     /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
     /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
@@ -74,6 +127,7 @@ public static partial class Application // Keyboard handling
             {
             {
                 Key oldKey = _quitKey;
                 Key oldKey = _quitKey;
                 _quitKey = value;
                 _quitKey = value;
+
                 if (_quitKey == Key.Empty)
                 if (_quitKey == Key.Empty)
                 {
                 {
                     KeyBindings.Remove (_quitKey);
                     KeyBindings.Remove (_quitKey);
@@ -139,7 +193,7 @@ public static partial class Application // Keyboard handling
         }
         }
         else
         else
         {
         {
-            if (Application.Current.NewKeyDownEvent (keyEvent))
+            if (Current.NewKeyDownEvent (keyEvent))
             {
             {
                 return true;
                 return true;
             }
             }
@@ -147,7 +201,7 @@ public static partial class Application // Keyboard handling
 
 
         // Invoke any Application-scoped KeyBindings.
         // Invoke any Application-scoped KeyBindings.
         // The first view that handles the key will stop the loop.
         // The first view that handles the key will stop the loop.
-        foreach (var binding in KeyBindings.Bindings.Where (b => b.Key == keyEvent.KeyCode))
+        foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.Bindings.Where (b => b.Key == keyEvent.KeyCode))
         {
         {
             if (binding.Value.BoundView is { })
             if (binding.Value.BoundView is { })
             {
             {
@@ -193,7 +247,6 @@ public static partial class Application // Keyboard handling
             }
             }
         }
         }
 
 
-
         return false;
         return false;
     }
     }
 
 
@@ -252,13 +305,13 @@ public static partial class Application // Keyboard handling
     public static KeyBindings KeyBindings { get; internal set; } = new ();
     public static KeyBindings KeyBindings { get; internal set; } = new ();
 
 
     /// <summary>
     /// <summary>
-    /// Commands for Application.
+    ///     Commands for Application.
     /// </summary>
     /// </summary>
     private static Dictionary<Command, Func<CommandContext, bool?>> CommandImplementations { get; set; }
     private static Dictionary<Command, Func<CommandContext, bool?>> CommandImplementations { get; set; }
 
 
     /// <summary>
     /// <summary>
     ///     <para>
     ///     <para>
-    ///         Sets the function that will be invoked for a <see cref="Command"/>. 
+    ///         Sets the function that will be invoked for a <see cref="Command"/>.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
     ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
@@ -266,28 +319,23 @@ public static partial class Application // Keyboard handling
     ///     </para>
     ///     </para>
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
-    /// <para>
-    ///     This version of AddCommand is for commands that do not require a <see cref="CommandContext"/>.
-    /// </para>
+    ///     <para>
+    ///         This version of AddCommand is for commands that do not require a <see cref="CommandContext"/>.
+    ///     </para>
     /// </remarks>
     /// </remarks>
     /// <param name="command">The command.</param>
     /// <param name="command">The command.</param>
     /// <param name="f">The function.</param>
     /// <param name="f">The function.</param>
-    private static void AddCommand (Command command, Func<bool?> f)
-    {
-        CommandImplementations [command] = ctx => f ();
-    }
+    private static void AddCommand (Command command, Func<bool?> f) { CommandImplementations [command] = ctx => f (); }
 
 
-    static Application ()
-    {
-        AddApplicationKeyBindings();
-    }
+    static Application () { AddApplicationKeyBindings (); }
 
 
     internal static void AddApplicationKeyBindings ()
     internal static void AddApplicationKeyBindings ()
     {
     {
-        CommandImplementations = new Dictionary<Command, Func<CommandContext, bool?>> ();
+        CommandImplementations = new ();
+
         // Things this view knows how to do
         // Things this view knows how to do
         AddCommand (
         AddCommand (
-                    Command.QuitToplevel,  // TODO: IRunnable: Rename to Command.Quit to make more generic.
+                    Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic.
                     () =>
                     () =>
                     {
                     {
                         if (ApplicationOverlapped.OverlappedTop is { })
                         if (ApplicationOverlapped.OverlappedTop is { })
@@ -296,7 +344,7 @@ public static partial class Application // Keyboard handling
                         }
                         }
                         else
                         else
                         {
                         {
-                            Application.RequestStop ();
+                            RequestStop ();
                         }
                         }
 
 
                         return true;
                         return true;
@@ -363,24 +411,24 @@ public static partial class Application // Keyboard handling
                     }
                     }
                    );
                    );
 
 
-
         KeyBindings.Clear ();
         KeyBindings.Clear ();
 
 
-        KeyBindings.Add (Application.QuitKey, KeyBindingScope.Application, Command.QuitToplevel);
+        KeyBindings.Add (QuitKey, KeyBindingScope.Application, Command.QuitToplevel);
 
 
         KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextView);
         KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextView);
         KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextView);
         KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextView);
         KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousView);
         KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousView);
         KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousView);
         KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousView);
-        KeyBindings.Add (Key.Tab, KeyBindingScope.Application, Command.NextView);
-        KeyBindings.Add (Key.Tab.WithShift, KeyBindingScope.Application, Command.PreviousView);
+        KeyBindings.Add (NextTabKey, KeyBindingScope.Application, Command.NextView);
+        KeyBindings.Add (PrevTabKey, KeyBindingScope.Application, Command.PreviousView);
 
 
-        KeyBindings.Add (Application.NextTabGroupKey, KeyBindingScope.Application, Command.NextViewOrTop); // Needed on Unix
-        KeyBindings.Add (Application.PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousViewOrTop); // Needed on Unix
+        KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextViewOrTop); // Needed on Unix
+        KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousViewOrTop); // Needed on Unix
 
 
         // TODO: Refresh Key should be configurable
         // TODO: Refresh Key should be configurable
         KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
         KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
 
 
+        // TODO: Suspend Key should be configurable
         if (Environment.OSVersion.Platform == PlatformID.Unix)
         if (Environment.OSVersion.Platform == PlatformID.Unix)
         {
         {
             KeyBindings.Add (Key.Z.WithCtrl, KeyBindingScope.Application, Command.Suspend);
             KeyBindings.Add (Key.Z.WithCtrl, KeyBindingScope.Application, Command.Suspend);
@@ -431,10 +479,10 @@ public static partial class Application // Keyboard handling
     /// <param name="view">The view that is bound to the key.</param>
     /// <param name="view">The view that is bound to the key.</param>
     internal static void RemoveKeyBindings (View view)
     internal static void RemoveKeyBindings (View view)
     {
     {
-        var list = KeyBindings.Bindings
-                          .Where (kv => kv.Value.Scope != KeyBindingScope.Application)
-                          .Select (kv => kv.Value)
-                          .Distinct ()
-                          .ToList ();
+        List<KeyBinding> list = KeyBindings.Bindings
+                                           .Where (kv => kv.Value.Scope != KeyBindingScope.Application)
+                                           .Select (kv => kv.Value)
+                                           .Distinct ()
+                                           .ToList ();
     }
     }
 }
 }

+ 3 - 147
Terminal.Gui/Application/Application.Navigation.cs

@@ -1,154 +1,10 @@
 #nullable enable
 #nullable enable
-using System.Diagnostics;
-using System.Reflection.PortableExecutable;
-using System.Security.Cryptography;
-
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
-/// <summary>
-///     Helper class for <see cref="Application"/> navigation.
-/// </summary>
-internal static class ApplicationNavigation
+public static partial class Application // Navigation stuff
 {
 {
     /// <summary>
     /// <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>
-    ///     Moves the focus to the next focusable view.
-    ///     Honors <see cref="ViewArrangement.Overlapped"/> and will only move to the next subview
-    ///     if the current and next subviews are not overlapped.
-    /// </summary>
-    internal static void MoveNextView ()
-    {
-        View? old = GetDeepestFocusedSubview (Application.Current!.Focused);
-
-        if (!Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop))
-        {
-            Application.Current.AdvanceFocus (NavigationDirection.Forward, null);
-        }
-
-        if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
-        {
-            old?.SetNeedsDisplay ();
-            Application.Current.Focused?.SetNeedsDisplay ();
-        }
-        else
-        {
-            ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward);
-        }
-    }
-
-    /// <summary>
-    ///     Moves the focus to the next <see cref="Toplevel"/> subview or the next subview that has <see cref="ApplicationOverlapped.OverlappedTop"/> set.
-    /// </summary>
-    internal static void MoveNextViewOrTop ()
-    {
-        if (ApplicationOverlapped.OverlappedTop is null)
-        {
-            Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top;
-
-            if (!Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup))
-            {
-                Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
-
-                if (Application.Current.Focused is null)
-                {
-                    Application.Current.RestoreFocus ();
-                }
-            }
-
-            if (top != Application.Current.Focused && top != Application.Current.Focused?.Focused)
-            {
-                top?.SetNeedsDisplay ();
-                Application.Current.Focused?.SetNeedsDisplay ();
-            }
-            else
-            {
-                ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward);
-            }
-
-
-
-            //top!.AdvanceFocus (NavigationDirection.Forward);
-
-            //if (top.Focused is null)
-            //{
-            //    top.AdvanceFocus (NavigationDirection.Forward);
-            //}
-
-            //top.SetNeedsDisplay ();
-            ApplicationOverlapped.BringOverlappedTopToFront ();
-        }
-        else
-        {
-            ApplicationOverlapped.OverlappedMoveNext ();
-        }
-    }
-
-    // TODO: These methods should return bool to indicate if the focus was moved or not.
-
-    /// <summary>
-    ///     Moves the focus to the next view. Honors <see cref="ViewArrangement.Overlapped"/> and will only move to the next subview
-    ///     if the current and next subviews are not overlapped.
+    ///     Gets the <see cref="ApplicationNavigation"/> instance for the current <see cref="Application"/>.
     /// </summary>
     /// </summary>
-    internal static void MovePreviousView ()
-    {
-        View? old = GetDeepestFocusedSubview (Application.Current!.Focused);
-
-        if (!Application.Current.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop))
-        {
-            Application.Current.AdvanceFocus (NavigationDirection.Backward, null);
-        }
-
-        if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
-        {
-            old?.SetNeedsDisplay ();
-            Application.Current.Focused?.SetNeedsDisplay ();
-        }
-        else
-        {
-            ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes?.Reverse (), NavigationDirection.Backward);
-        }
-    }
-
-    internal static void MovePreviousViewOrTop ()
-    {
-        if (ApplicationOverlapped.OverlappedTop is null)
-        {
-            Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top;
-            top!.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup);
-
-            if (top.Focused is null)
-            {
-                top.AdvanceFocus (NavigationDirection.Backward, null);
-            }
-
-            top.SetNeedsDisplay ();
-            ApplicationOverlapped.BringOverlappedTopToFront ();
-        }
-        else
-        {
-            ApplicationOverlapped.OverlappedMovePrevious ();
-        }
-    }
+    public static ApplicationNavigation? Navigation { get; internal set; }
 }
 }

+ 2 - 0
Terminal.Gui/Application/Application.cs

@@ -148,6 +148,8 @@ public static partial class Application
         KeyDown = null;
         KeyDown = null;
         KeyUp = null;
         KeyUp = null;
         SizeChanging = null;
         SizeChanging = null;
+
+        Navigation = null;
         AddApplicationKeyBindings ();
         AddApplicationKeyBindings ();
 
 
         Colors.Reset ();
         Colors.Reset ();

+ 159 - 0
Terminal.Gui/Application/ApplicationNavigation.cs

@@ -0,0 +1,159 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Helper class for <see cref="Application"/> navigation. Held by <see cref="Application.Navigation"/>
+/// </summary>
+public class ApplicationNavigation
+{
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="ApplicationNavigation"/> class.
+    /// </summary>
+    public ApplicationNavigation ()
+    {
+        // TODO: Move navigation key bindings here from AddApplicationKeyBindings
+    }
+
+    /// <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>
+    ///     Moves the focus to the next focusable view.
+    ///     Honors <see cref="ViewArrangement.Overlapped"/> and will only move to the next subview
+    ///     if the current and next subviews are not overlapped.
+    /// </summary>
+    internal static void MoveNextView ()
+    {
+        View? old = GetDeepestFocusedSubview (Application.Current!.Focused);
+
+        if (!Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop))
+        {
+            Application.Current.AdvanceFocus (NavigationDirection.Forward, null);
+        }
+
+        if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
+        {
+            old?.SetNeedsDisplay ();
+            Application.Current.Focused?.SetNeedsDisplay ();
+        }
+        else
+        {
+            ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward);
+        }
+    }
+
+    /// <summary>
+    ///     Moves the focus to the next <see cref="Toplevel"/> subview or the next subview that has
+    ///     <see cref="ApplicationOverlapped.OverlappedTop"/> set.
+    /// </summary>
+    internal static void MoveNextViewOrTop ()
+    {
+        if (ApplicationOverlapped.OverlappedTop is null)
+        {
+            Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top;
+
+            if (!Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup))
+            {
+                Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
+
+                if (Application.Current.Focused is null)
+                {
+                    Application.Current.RestoreFocus ();
+                }
+            }
+
+            if (top != Application.Current.Focused && top != Application.Current.Focused?.Focused)
+            {
+                top?.SetNeedsDisplay ();
+                Application.Current.Focused?.SetNeedsDisplay ();
+            }
+            else
+            {
+                ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward);
+            }
+
+            //top!.AdvanceFocus (NavigationDirection.Forward);
+
+            //if (top.Focused is null)
+            //{
+            //    top.AdvanceFocus (NavigationDirection.Forward);
+            //}
+
+            //top.SetNeedsDisplay ();
+            ApplicationOverlapped.BringOverlappedTopToFront ();
+        }
+        else
+        {
+            ApplicationOverlapped.OverlappedMoveNext ();
+        }
+    }
+
+    // TODO: These methods should return bool to indicate if the focus was moved or not.
+
+    /// <summary>
+    ///     Moves the focus to the next view. Honors <see cref="ViewArrangement.Overlapped"/> and will only move to the next
+    ///     subview
+    ///     if the current and next subviews are not overlapped.
+    /// </summary>
+    internal static void MovePreviousView ()
+    {
+        View? old = GetDeepestFocusedSubview (Application.Current!.Focused);
+
+        if (!Application.Current.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop))
+        {
+            Application.Current.AdvanceFocus (NavigationDirection.Backward, null);
+        }
+
+        if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
+        {
+            old?.SetNeedsDisplay ();
+            Application.Current.Focused?.SetNeedsDisplay ();
+        }
+        else
+        {
+            ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes?.Reverse (), NavigationDirection.Backward);
+        }
+    }
+
+    internal static void MovePreviousViewOrTop ()
+    {
+        if (ApplicationOverlapped.OverlappedTop is null)
+        {
+            Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top;
+            top!.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup);
+
+            if (top.Focused is null)
+            {
+                top.AdvanceFocus (NavigationDirection.Backward, null);
+            }
+
+            top.SetNeedsDisplay ();
+            ApplicationOverlapped.BringOverlappedTopToFront ();
+        }
+        else
+        {
+            ApplicationOverlapped.OverlappedMovePrevious ();
+        }
+    }
+}

+ 0 - 0
Terminal.Gui/Application/Application.Overlapped.cs → Terminal.Gui/Application/ApplicationOverlapped.cs


+ 2 - 0
Terminal.Gui/Resources/config.json

@@ -17,6 +17,8 @@
   // to throw exceptions. 
   // to throw exceptions. 
   "ConfigurationManager.ThrowOnJsonErrors": false,
   "ConfigurationManager.ThrowOnJsonErrors": false,
 
 
+  "Application.NextTabKey": "Tab",
+  "Application.PrevTabKey": "Shift+Tab",
   "Application.NextTabGroupKey": "F6",
   "Application.NextTabGroupKey": "F6",
   "Application.PrevTabGroupKey": "Shift+F6",
   "Application.PrevTabGroupKey": "Shift+F6",
   "Application.QuitKey": "Esc",
   "Application.QuitKey": "Esc",

+ 5 - 0
UnitTests/Application/ApplicationTests.cs

@@ -201,6 +201,9 @@ public class ApplicationTests
             // Keyboard
             // Keyboard
             Assert.Empty (Application.GetViewKeyBindings ());
             Assert.Empty (Application.GetViewKeyBindings ());
 
 
+            // Navigation
+            Assert.Null (Application.Navigation);
+
             // Events - Can't check
             // Events - Can't check
             //Assert.Null (Application.NotifyNewRunState);
             //Assert.Null (Application.NotifyNewRunState);
             //Assert.Null (Application.NotifyNewRunState);
             //Assert.Null (Application.NotifyNewRunState);
@@ -241,6 +244,8 @@ public class ApplicationTests
 
 
         //Application.WantContinuousButtonPressedView = new View ();
         //Application.WantContinuousButtonPressedView = new View ();
 
 
+        Application.Navigation = new ();
+
         Application.ResetState ();
         Application.ResetState ();
         CheckReset ();
         CheckReset ();