diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs
index a2aacaab5..d9b4529d0 100644
--- a/Terminal.Gui/Application/Application.Initialization.cs
+++ b/Terminal.Gui/Application/Application.Initialization.cs
@@ -74,6 +74,8 @@ public static partial class Application // Initialization (Init/Shutdown)
ResetState ();
}
+ Navigation = new ();
+
// For UnitTests
if (driver is { })
{
diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs
index c7a5f8b50..0981bbd2d 100644
--- a/Terminal.Gui/Application/Application.Keyboard.cs
+++ b/Terminal.Gui/Application/Application.Keyboard.cs
@@ -1,11 +1,64 @@
#nullable enable
using System.Text.Json.Serialization;
-using static System.Formats.Asn1.AsnWriter;
namespace Terminal.Gui;
public static partial class Application // Keyboard handling
{
+ private static Key _nextTabKey = Key.Empty; // Defined in config.json
+
+ /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
+ [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
+
+ /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
+ [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
/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
@@ -74,6 +127,7 @@ public static partial class Application // Keyboard handling
{
Key oldKey = _quitKey;
_quitKey = value;
+
if (_quitKey == Key.Empty)
{
KeyBindings.Remove (_quitKey);
@@ -139,7 +193,7 @@ public static partial class Application // Keyboard handling
}
else
{
- if (Application.Current.NewKeyDownEvent (keyEvent))
+ if (Current.NewKeyDownEvent (keyEvent))
{
return true;
}
@@ -147,7 +201,7 @@ public static partial class Application // Keyboard handling
// Invoke any Application-scoped KeyBindings.
// 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 binding in KeyBindings.Bindings.Where (b => b.Key == keyEvent.KeyCode))
{
if (binding.Value.BoundView is { })
{
@@ -193,7 +247,6 @@ public static partial class Application // Keyboard handling
}
}
-
return false;
}
@@ -252,13 +305,13 @@ public static partial class Application // Keyboard handling
public static KeyBindings KeyBindings { get; internal set; } = new ();
///
- /// Commands for Application.
+ /// Commands for Application.
///
private static Dictionary> CommandImplementations { get; set; }
///
///
- /// Sets the function that will be invoked for a .
+ /// Sets the function that will be invoked for a .
///
///
/// If AddCommand has already been called for will
@@ -266,28 +319,23 @@ public static partial class Application // Keyboard handling
///
///
///
- ///
- /// This version of AddCommand is for commands that do not require a .
- ///
+ ///
+ /// This version of AddCommand is for commands that do not require a .
+ ///
///
/// The command.
/// The function.
- private static void AddCommand (Command command, Func f)
- {
- CommandImplementations [command] = ctx => f ();
- }
+ private static void AddCommand (Command command, Func f) { CommandImplementations [command] = ctx => f (); }
- static Application ()
- {
- AddApplicationKeyBindings();
- }
+ static Application () { AddApplicationKeyBindings (); }
internal static void AddApplicationKeyBindings ()
{
- CommandImplementations = new Dictionary> ();
+ CommandImplementations = new ();
+
// Things this view knows how to do
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 { })
@@ -296,7 +344,7 @@ public static partial class Application // Keyboard handling
}
else
{
- Application.RequestStop ();
+ RequestStop ();
}
return true;
@@ -363,24 +411,24 @@ public static partial class Application // Keyboard handling
}
);
-
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.CursorDown, KeyBindingScope.Application, Command.NextView);
KeyBindings.Add (Key.CursorLeft, 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
KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
+ // TODO: Suspend Key should be configurable
if (Environment.OSVersion.Platform == PlatformID.Unix)
{
KeyBindings.Add (Key.Z.WithCtrl, KeyBindingScope.Application, Command.Suspend);
@@ -431,10 +479,10 @@ public static partial class Application // Keyboard handling
/// The view that is bound to the key.
internal static void RemoveKeyBindings (View view)
{
- var list = KeyBindings.Bindings
- .Where (kv => kv.Value.Scope != KeyBindingScope.Application)
- .Select (kv => kv.Value)
- .Distinct ()
- .ToList ();
+ List list = KeyBindings.Bindings
+ .Where (kv => kv.Value.Scope != KeyBindingScope.Application)
+ .Select (kv => kv.Value)
+ .Distinct ()
+ .ToList ();
}
}
diff --git a/Terminal.Gui/Application/Application.Navigation.cs b/Terminal.Gui/Application/Application.Navigation.cs
index bab8f9e77..440cd4b42 100644
--- a/Terminal.Gui/Application/Application.Navigation.cs
+++ b/Terminal.Gui/Application/Application.Navigation.cs
@@ -1,154 +1,10 @@
#nullable enable
-using System.Diagnostics;
-using System.Reflection.PortableExecutable;
-using System.Security.Cryptography;
-
namespace Terminal.Gui;
-///
-/// Helper class for navigation.
-///
-internal static class ApplicationNavigation
+public static partial class Application // Navigation stuff
{
///
- /// Gets the deepest focused subview of the specified .
+ /// Gets the instance for the current .
///
- ///
- ///
- 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;
- }
-
- ///
- /// Moves the focus to the next focusable view.
- /// Honors and will only move to the next subview
- /// if the current and next subviews are not overlapped.
- ///
- 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);
- }
- }
-
- ///
- /// Moves the focus to the next subview or the next subview that has set.
- ///
- 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.
-
- ///
- /// Moves the focus to the next view. Honors and will only move to the next subview
- /// if the current and next subviews are not overlapped.
- ///
- 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; }
}
diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs
index b2d2f9c59..e5332ee7f 100644
--- a/Terminal.Gui/Application/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -148,6 +148,8 @@ public static partial class Application
KeyDown = null;
KeyUp = null;
SizeChanging = null;
+
+ Navigation = null;
AddApplicationKeyBindings ();
Colors.Reset ();
diff --git a/Terminal.Gui/Application/ApplicationNavigation.cs b/Terminal.Gui/Application/ApplicationNavigation.cs
new file mode 100644
index 000000000..8794dc2f2
--- /dev/null
+++ b/Terminal.Gui/Application/ApplicationNavigation.cs
@@ -0,0 +1,159 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+///
+/// Helper class for navigation. Held by
+///
+public class ApplicationNavigation
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ApplicationNavigation ()
+ {
+ // TODO: Move navigation key bindings here from AddApplicationKeyBindings
+ }
+
+ ///
+ /// Gets the deepest focused subview of the specified .
+ ///
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// Moves the focus to the next focusable view.
+ /// Honors and will only move to the next subview
+ /// if the current and next subviews are not overlapped.
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Moves the focus to the next subview or the next subview that has
+ /// set.
+ ///
+ 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.
+
+ ///
+ /// Moves the focus to the next view. Honors and will only move to the next
+ /// subview
+ /// if the current and next subviews are not overlapped.
+ ///
+ 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 ();
+ }
+ }
+}
diff --git a/Terminal.Gui/Application/Application.Overlapped.cs b/Terminal.Gui/Application/ApplicationOverlapped.cs
similarity index 100%
rename from Terminal.Gui/Application/Application.Overlapped.cs
rename to Terminal.Gui/Application/ApplicationOverlapped.cs
diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json
index 688537779..a80d8334e 100644
--- a/Terminal.Gui/Resources/config.json
+++ b/Terminal.Gui/Resources/config.json
@@ -17,6 +17,8 @@
// to throw exceptions.
"ConfigurationManager.ThrowOnJsonErrors": false,
+ "Application.NextTabKey": "Tab",
+ "Application.PrevTabKey": "Shift+Tab",
"Application.NextTabGroupKey": "F6",
"Application.PrevTabGroupKey": "Shift+F6",
"Application.QuitKey": "Esc",
diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs
index 268eebcd6..ce03ac11f 100644
--- a/UnitTests/Application/ApplicationTests.cs
+++ b/UnitTests/Application/ApplicationTests.cs
@@ -201,6 +201,9 @@ public class ApplicationTests
// Keyboard
Assert.Empty (Application.GetViewKeyBindings ());
+ // Navigation
+ Assert.Null (Application.Navigation);
+
// Events - Can't check
//Assert.Null (Application.NotifyNewRunState);
//Assert.Null (Application.NotifyNewRunState);
@@ -241,6 +244,8 @@ public class ApplicationTests
//Application.WantContinuousButtonPressedView = new View ();
+ Application.Navigation = new ();
+
Application.ResetState ();
CheckReset ();