diff --git a/README.md b/README.md index 5e6742332..5b793db2f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ dotnet run * [API Documentation](https://gui-cs.github.io/Terminal.GuiV2Docs/api/Terminal.Gui.html) * [Documentation Home](https://gui-cs.github.io/Terminal.GuiV2Docs) -The above documentation matches the most recent Nuget release from the `v2_develop` branch. Get the [v1 documentation here]( (This is the v2 API documentation. For v1 go here: https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.html) +The above documentation matches the most recent Nuget release from the `v2_develop` branch. Get the [v1 documentation here](This is the v2 API documentation. For v1 go here: https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.html) See the [`Terminal.Gui/` README](https://github.com/gui-cs/Terminal.Gui/tree/master/Terminal.Gui) for an overview of how the library is structured. diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index d795075eb..acbfce7d0 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -1,45 +1,49 @@ #nullable enable -using System.Text.Json.Serialization; - namespace Terminal.Gui; public static partial class Application // Keyboard handling { + private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrrides private static Key _nextTabKey = Key.Tab; // Resources/config.json overrrides - /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static Key NextTabKey - { - get => _nextTabKey; - set - { - if (_nextTabKey != value) - { - ReplaceKey (_nextTabKey, value); - _nextTabKey = value; - } - } - } + private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrrides private static Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrrides - /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static Key PrevTabKey - { - get => _prevTabKey; - set - { - if (_prevTabKey != value) - { - ReplaceKey (_prevTabKey, value); - _prevTabKey = value; - } - } - } + private static Key _quitKey = Key.Esc; // Resources/config.json overrrides - private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrrides + static Application () { AddApplicationKeyBindings (); } + + /// Gets the key bindings for this view. + public static KeyBindings KeyBindings { get; internal set; } = new (); + + /// + /// Event fired when the user presses a key. Fired by . + /// + /// Set to to indicate the key was handled and to prevent + /// additional processing. + /// + /// + /// + /// All drivers support firing the event. Some drivers (Curses) do not support firing the + /// and events. + /// Fired after and before . + /// + public static event EventHandler? KeyDown; + + /// + /// Event fired when the user releases a key. Fired by . + /// + /// Set to to indicate the key was handled and to prevent + /// additional processing. + /// + /// + /// + /// All drivers support firing the event. Some drivers (Curses) do not support firing the + /// and events. + /// Fired after . + /// + public static event EventHandler? KeyUp; /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] @@ -56,71 +60,21 @@ public static partial class Application // Keyboard handling } } - private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrrides - - /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. + /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static Key PrevTabGroupKey + public static Key NextTabKey { - get => _prevTabGroupKey; + get => _nextTabKey; set { - if (_prevTabGroupKey != value) + if (_nextTabKey != value) { - ReplaceKey (_prevTabGroupKey, value); - _prevTabGroupKey = value; + ReplaceKey (_nextTabKey, value); + _nextTabKey = value; } } } - private static Key _quitKey = Key.Esc; // Resources/config.json overrrides - - /// Gets or sets the key to quit the application. - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static Key QuitKey - { - get => _quitKey; - set - { - if (_quitKey != value) - { - ReplaceKey (_quitKey, value); - _quitKey = value; - } - } - } - - private static void ReplaceKey (Key oldKey, Key newKey) - { - if (KeyBindings.Bindings.Count == 0) - { - return; - } - - if (newKey == Key.Empty) - { - KeyBindings.Remove (oldKey); - } - else - { - KeyBindings.ReplaceKey (oldKey, newKey); - } - } - - /// - /// Event fired when the user presses a key. Fired by . - /// - /// Set to to indicate the key was handled and to prevent - /// additional processing. - /// - /// - /// - /// All drivers support firing the event. Some drivers (Curses) do not support firing the - /// and events. - /// Fired after and before . - /// - public static event EventHandler? KeyDown; - /// /// Called by the when the user presses a key. Fires the event /// then calls on all top level views. Called after and @@ -217,20 +171,6 @@ public static partial class Application // Keyboard handling return false; } - /// - /// Event fired when the user releases a key. Fired by . - /// - /// Set to to indicate the key was handled and to prevent - /// additional processing. - /// - /// - /// - /// All drivers support firing the event. Some drivers (Curses) do not support firing the - /// and events. - /// Fired after . - /// - public static event EventHandler? KeyUp; - /// /// Called by the when the user releases a key. Fires the event /// then calls on all top level views. Called after . @@ -268,33 +208,50 @@ public static partial class Application // Keyboard handling return false; } - /// Gets the key bindings for this view. - public static KeyBindings KeyBindings { get; internal set; } = new (); + /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static Key PrevTabGroupKey + { + get => _prevTabGroupKey; + set + { + if (_prevTabGroupKey != value) + { + ReplaceKey (_prevTabGroupKey, value); + _prevTabGroupKey = value; + } + } + } - /// - /// Commands for Application. - /// - private static Dictionary>? CommandImplementations { get; set; } + /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static Key PrevTabKey + { + get => _prevTabKey; + set + { + if (_prevTabKey != value) + { + ReplaceKey (_prevTabKey, value); + _prevTabKey = value; + } + } + } - /// - /// - /// Sets the function that will be invoked for a . - /// - /// - /// If AddCommand has already been called for will - /// replace the old one. - /// - /// - /// - /// - /// 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 (); } - - static Application () { AddApplicationKeyBindings (); } + /// Gets or sets the key to quit the application. + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static Key QuitKey + { + get => _quitKey; + set + { + if (_quitKey != value) + { + ReplaceKey (_quitKey, value); + _quitKey = value; + } + } + } internal static void AddApplicationKeyBindings () { @@ -303,7 +260,7 @@ public static partial class Application // Keyboard handling // Things this view knows how to do AddCommand ( Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic. - () => + static () => { if (ApplicationOverlapped.OverlappedTop is { }) { @@ -320,7 +277,7 @@ public static partial class Application // Keyboard handling AddCommand ( Command.Suspend, - () => + static () => { Driver?.Suspend (); @@ -330,40 +287,22 @@ public static partial class Application // Keyboard handling AddCommand ( Command.NextView, - () => - { - View? current = Application.Current; - if (current is { }) - { - return current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - } - return false; - } - ); + static () => Current?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop)); AddCommand ( Command.PreviousView, - () => - { - View? current = Application.Current; - if (current is { }) - { - return current.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop); - } - return false; - } - ); + static () => Current?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop)); AddCommand ( Command.NextViewOrTop, - () => + static () => { + // TODO: This OverlapppedTop tomfoolery goes away in addressing #2491 if (ApplicationOverlapped.OverlappedTop is null) { - View? current = Application.Current; - if (current is { }) + if ((View?)Current is { }) { - return current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup); + return Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup); } } else @@ -379,19 +318,19 @@ public static partial class Application // Keyboard handling AddCommand ( Command.PreviousViewOrTop, - () => + static () => { + // TODO: This OverlapppedTop tomfoolery goes away in addressing #2491 if (ApplicationOverlapped.OverlappedTop is null) { - View? current = Application.Current; - if (current is { }) + if ((View?)Current is { }) { - return current.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup); + return Current.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup); } } else { - ApplicationOverlapped.OverlappedMovePrevious(); + ApplicationOverlapped.OverlappedMovePrevious (); return true; } @@ -402,7 +341,7 @@ public static partial class Application // Keyboard handling AddCommand ( Command.Refresh, - () => + static () => { Refresh (); @@ -464,4 +403,44 @@ public static partial class Application // Keyboard handling .Distinct () .ToList (); } + + /// + /// + /// Sets the function that will be invoked for a . + /// + /// + /// If AddCommand has already been called for will + /// replace the old one. + /// + /// + /// + /// + /// 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 (); } + + /// + /// Commands for Application. + /// + private static Dictionary>? CommandImplementations { get; set; } + + private static void ReplaceKey (Key oldKey, Key newKey) + { + if (KeyBindings.Bindings.Count == 0) + { + return; + } + + if (newKey == Key.Empty) + { + KeyBindings.Remove (oldKey); + } + else + { + KeyBindings.ReplaceKey (oldKey, newKey); + } + } } diff --git a/UICatalog/Scenarios/Adornments.cs b/UICatalog/Scenarios/Adornments.cs index 0f46fedd4..3602e22be 100644 --- a/UICatalog/Scenarios/Adornments.cs +++ b/UICatalog/Scenarios/Adornments.cs @@ -19,11 +19,13 @@ public class Adornments : Scenario var editor = new AdornmentsEditor { AutoSelectViewToEdit = true, + // This is for giggles, to show that the editor can be moved around. Arrangement = ViewArrangement.Movable, - X = Pos.AnchorEnd(), + X = Pos.AnchorEnd () }; - editor.Border.Thickness = new Thickness (1, 2, 1, 1); + + editor.Border.Thickness = new (1, 2, 1, 1); app.Add (editor); @@ -31,7 +33,8 @@ public class Adornments : Scenario { Title = "The _Window", Arrangement = ViewArrangement.Movable, - // X = Pos.Center (), + + // X = Pos.Center (), Width = Dim.Percent (60), Height = Dim.Percent (80) }; @@ -127,9 +130,36 @@ public class Adornments : Scenario #endif }; + Application.MouseEvent += ApplicationOnMouseEvent; + Application.Run (app); app.Dispose (); Application.Shutdown (); + + return; + + void ApplicationOnMouseEvent (object sender, MouseEvent e) + { + if (!editor.AutoSelectViewToEdit || editor.FrameToScreen ().Contains (e.Position)) + { + return; + } + + // TODO: Add a setting (property) so only subviews of a specified view are considered. + View view = e.View; + + if (view is { } && e.Flags == MouseFlags.Button1Clicked) + { + if (view is Adornment adornment) + { + editor.ViewToEdit = adornment.Parent; + } + else + { + editor.ViewToEdit = view; + } + } + } } } diff --git a/UICatalog/Scenarios/AdornmentsEditor.cs b/UICatalog/Scenarios/AdornmentsEditor.cs index 015a3498a..a86ab70ae 100644 --- a/UICatalog/Scenarios/AdornmentsEditor.cs +++ b/UICatalog/Scenarios/AdornmentsEditor.cs @@ -21,8 +21,6 @@ public class AdornmentsEditor : View TabStop = TabBehavior.TabGroup; - //Application.MouseEvent += Application_MouseEvent; - Application.Navigation!.FocusedChanged += ApplicationNavigationOnFocusedChanged; Initialized += AdornmentsEditor_Initialized; } @@ -155,35 +153,4 @@ public class AdornmentsEditor : View Add (_diagRulerCheckBox); _diagRulerCheckBox.Y = Pos.Bottom (_diagPaddingCheckBox); } - - private void Application_MouseEvent (object sender, MouseEvent e) - { - if (!AutoSelectViewToEdit || FrameToScreen ().Contains (e.Position)) - { - return; - } - - // TODO: Add a setting (property) so only subviews of a specified view are considered. - View view = e.View; - - if (view is { } && e.Flags == MouseFlags.Button1Clicked) - { - if (view is Adornment adornment) - { - ViewToEdit = adornment.Parent; - } - else - { - ViewToEdit = view; - } - } - } - - private void ApplicationNavigationOnFocusedChanged (object sender, EventArgs e) - { - if (ApplicationNavigation.IsInHierarchy (this, Application.Navigation!.GetFocused ())) - { - return; - } - } } diff --git a/UICatalog/Scenarios/ContentScrolling.cs b/UICatalog/Scenarios/ContentScrolling.cs index d94e0a7e4..da09d7476 100644 --- a/UICatalog/Scenarios/ContentScrolling.cs +++ b/UICatalog/Scenarios/ContentScrolling.cs @@ -407,8 +407,35 @@ public class ContentScrolling : Scenario app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags; + Application.MouseEvent += ApplicationOnMouseEvent; + Application.Run (app); app.Dispose (); Application.Shutdown (); + + return; + + void ApplicationOnMouseEvent (object sender, MouseEvent e) + { + if (!editor.AutoSelectViewToEdit || editor.FrameToScreen ().Contains (e.Position)) + { + return; + } + + // TODO: Add a setting (property) so only subviews of a specified view are considered. + View view = e.View; + + if (view is { } && e.Flags == MouseFlags.Button1Clicked) + { + if (view is Adornment adornment) + { + editor.ViewToEdit = adornment.Parent; + } + else + { + editor.ViewToEdit = view; + } + } + } } } diff --git a/UICatalog/Scenarios/Navigation.cs b/UICatalog/Scenarios/Navigation.cs new file mode 100644 index 000000000..64fd5b30b --- /dev/null +++ b/UICatalog/Scenarios/Navigation.cs @@ -0,0 +1,165 @@ +using Terminal.Gui; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("Navigation", "Navigation Tester")] +[ScenarioCategory ("Mouse and Keyboard")] +[ScenarioCategory ("Layout")] +public class Navigation : Scenario +{ + public override void Main () + { + Application.Init (); + + Window app = new () + { + Title = GetQuitKeyAndName (), + TabStop = TabBehavior.TabGroup + }; + + var editor = new AdornmentsEditor + { + X = 0, + Y = 0, + AutoSelectViewToEdit = true, + TabStop = TabBehavior.NoStop + }; + app.Add (editor); + + FrameView testFrame = new () + { + Title = "_1 Test Frame", + X = Pos.Right (editor), + Width = Dim.Fill (), + Height = Dim.Fill (), + }; + + app.Add (testFrame); + + Button button = new () + { + X = 0, + Y = 0, + Title = $"TopButton _{GetNextHotKey ()}", + }; + + testFrame.Add (button); + + var tiledView1 = CreateTiledView (0, 2, 2); + var tiledView2 = CreateTiledView (1, Pos.Right (tiledView1), Pos.Top (tiledView1)); + + testFrame.Add (tiledView1); + testFrame.Add (tiledView2); + + var tiledView3 = CreateTiledView (1, Pos.Right (tiledView2), Pos.Top (tiledView2)); + tiledView3.TabStop = TabBehavior.TabGroup; + tiledView3.BorderStyle = LineStyle.Double; + testFrame.Add (tiledView3); + + var overlappedView1 = CreateOverlappedView (2, Pos.Center () - 5, Pos.Center ()); + var tiledSubView = CreateTiledView (4, 0, 2); + overlappedView1.Add (tiledSubView); + + var overlappedView2 = CreateOverlappedView (3, Pos.Center () + 10, Pos.Center () + 5); + + // BUGBUG: F6 through nested tab groups doesn't work yet. +#if NESTED_TABGROUPS + var overlappedInOverlapped1 = CreateOverlappedView (4, 1, 4); + overlappedView2.Add (overlappedInOverlapped1); + + var overlappedInOverlapped2 = CreateOverlappedView (5, 10, 7); + overlappedView2.Add (overlappedInOverlapped2); + +#endif + + testFrame.Add (overlappedView1); + testFrame.Add (overlappedView2); + + button = new () + { + X = Pos.AnchorEnd (), + Y = Pos.AnchorEnd (), + Title = $"TopButton _{GetNextHotKey ()}", + }; + + testFrame.Add (button); + + Application.Run (app); + app.Dispose (); + Application.Shutdown (); + } + + private int _hotkeyCount; + + private char GetNextHotKey () + { + return (char)((int)'A' + _hotkeyCount++); + } + + private View CreateTiledView (int id, Pos x, Pos y) + { + View overlapped = new View + { + X = x, + Y = y, + Height = Dim.Auto (), + Width = Dim.Auto (), + Title = $"Tiled{id} _{GetNextHotKey ()}", + Id = $"Tiled{id}", + BorderStyle = LineStyle.Single, + CanFocus = true, // Can't drag without this? BUGBUG + TabStop = TabBehavior.TabStop, + Arrangement = ViewArrangement.Fixed + }; + + Button button = new () + { + Title = $"Tiled Button{id} _{GetNextHotKey ()}" + }; + overlapped.Add (button); + + button = new () + { + Y = Pos.Bottom (button), + Title = $"Tiled Button{id} _{GetNextHotKey ()}" + }; + overlapped.Add (button); + + return overlapped; + } + + + private View CreateOverlappedView (int id, Pos x, Pos y) + { + View overlapped = new View + { + X = x, + Y = y, + Height = Dim.Auto (), + Width = Dim.Auto (), + Title = $"Overlapped{id} _{GetNextHotKey ()}", + ColorScheme = Colors.ColorSchemes ["Toplevel"], + Id = $"Overlapped{id}", + ShadowStyle = ShadowStyle.Transparent, + BorderStyle = LineStyle.Double, + CanFocus = true, // Can't drag without this? BUGBUG + TabStop = TabBehavior.TabGroup, + Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped + }; + + Button button = new () + { + Title = $"Button{id} _{GetNextHotKey ()}" + }; + overlapped.Add (button); + + button = new () + { + Y = Pos.Bottom (button), + Title = $"Button{id} _{GetNextHotKey ()}" + }; + overlapped.Add (button); + + return overlapped; + } +} diff --git a/UICatalog/Scenarios/ShadowStyles.cs b/UICatalog/Scenarios/ShadowStyles.cs index 849116396..dac89d040 100644 --- a/UICatalog/Scenarios/ShadowStyles.cs +++ b/UICatalog/Scenarios/ShadowStyles.cs @@ -55,11 +55,36 @@ public class ShadowStyles : Scenario }; app.Add (button); + Application.MouseEvent += ApplicationOnMouseEvent; + Application.Run (app); app.Dispose (); Application.Shutdown (); return; + + void ApplicationOnMouseEvent (object sender, MouseEvent e) + { + if (!editor.AutoSelectViewToEdit || editor.FrameToScreen ().Contains (e.Position)) + { + return; + } + + // TODO: Add a setting (property) so only subviews of a specified view are considered. + View view = e.View; + + if (view is { } && e.Flags == MouseFlags.Button1Clicked) + { + if (view is Adornment adornment) + { + editor.ViewToEdit = adornment.Parent; + } + else + { + editor.ViewToEdit = view; + } + } + } } } diff --git a/UICatalog/Scenarios/ViewExperiments.cs b/UICatalog/Scenarios/ViewExperiments.cs index 7fe8af83a..7054b5aa5 100644 --- a/UICatalog/Scenarios/ViewExperiments.cs +++ b/UICatalog/Scenarios/ViewExperiments.cs @@ -1,4 +1,5 @@ -using Terminal.Gui; +using System; +using Terminal.Gui; namespace UICatalog.Scenarios; @@ -47,36 +48,6 @@ public class ViewExperiments : Scenario testFrame.Add (button); - var tiledView1 = CreateTiledView (0, 2, 2); - var tiledView2 = CreateTiledView (1, Pos.Right (tiledView1), Pos.Top (tiledView1)); - - testFrame.Add (tiledView1); - testFrame.Add (tiledView2); - - var tiledView3 = CreateTiledView (1, Pos.Right (tiledView2), Pos.Top (tiledView2)); - tiledView3.TabStop = TabBehavior.TabGroup; - tiledView3.BorderStyle = LineStyle.Double; - testFrame.Add (tiledView3); - - var overlappedView1 = CreateOverlappedView (2, Pos.Center () - 5, Pos.Center ()); - var tiledSubView = CreateTiledView (4, 0, 2); - overlappedView1.Add (tiledSubView); - - var overlappedView2 = CreateOverlappedView (3, Pos.Center () + 10, Pos.Center () + 5); - - // BUGBUG: F6 through nested tab groups doesn't work yet. -#if NESTED_TABGROUPS - var overlappedInOverlapped1 = CreateOverlappedView (4, 1, 4); - overlappedView2.Add (overlappedInOverlapped1); - - var overlappedInOverlapped2 = CreateOverlappedView (5, 10, 7); - overlappedView2.Add (overlappedInOverlapped2); - -#endif - - testFrame.Add (overlappedView1); - testFrame.Add (overlappedView2); - button = new () { X = Pos.AnchorEnd (), @@ -85,10 +56,54 @@ public class ViewExperiments : Scenario }; testFrame.Add (button); + Application.MouseEvent += ApplicationOnMouseEvent; + Application.Navigation.FocusedChanged += NavigationOnFocusedChanged; + Application.Run (app); app.Dispose (); + Application.Shutdown (); + + return; + + + void NavigationOnFocusedChanged (object sender, EventArgs e) + { + if (!ApplicationNavigation.IsInHierarchy (testFrame, Application.Navigation!.GetFocused ())) + { + return; + } + + editor.ViewToEdit = Application.Navigation!.GetFocused (); + } + void ApplicationOnMouseEvent (object sender, MouseEvent e) + { + if (e.Flags != MouseFlags.Button1Clicked) + { + return; + } + + if (!editor.AutoSelectViewToEdit || !testFrame.FrameToScreen ().Contains (e.Position)) + { + return; + } + + // TODO: Add a setting (property) so only subviews of a specified view are considered. + View view = e.View; + + if (view is { } && e.Flags == MouseFlags.Button1Clicked) + { + if (view is Adornment adornment) + { + editor.ViewToEdit = adornment.Parent; + } + else + { + editor.ViewToEdit = view; + } + } + } } private int _hotkeyCount; @@ -110,7 +125,7 @@ public class ViewExperiments : Scenario Id = $"Tiled{id}", BorderStyle = LineStyle.Single, CanFocus = true, // Can't drag without this? BUGBUG - TabStop = TabBehavior.TabStop, + TabStop = TabBehavior.TabGroup, Arrangement = ViewArrangement.Fixed };