From 3f3ceae9c7219206cd03fcc8d5cdbaaf1422bae2 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 14 Oct 2024 16:35:10 -0600 Subject: [PATCH] API docs --- Terminal.Gui/View/View.Keyboard.cs | 79 ++----- Terminal.Gui/Views/Menu/Menu.cs | 2 +- UICatalog/Scenarios/Keys.cs | 166 +++++++++------ UICatalog/Scenarios/VkeyPacketSimulator.cs | 2 +- UnitTests/Application/KeyboardTests.cs | 32 +-- UnitTests/View/{ => Keyboard}/HotKeyTests.cs | 12 +- UnitTests/View/Keyboard/KeyboardEventTests.cs | 199 ++---------------- .../{ => Keyboard}/ViewKeyBindingTests.cs | 54 ++--- docfx/docs/keyboard.md | 38 ++-- 9 files changed, 211 insertions(+), 373 deletions(-) rename UnitTests/View/{ => Keyboard}/HotKeyTests.cs (97%) rename UnitTests/View/{ => Keyboard}/ViewKeyBindingTests.cs (76%) diff --git a/Terminal.Gui/View/View.Keyboard.cs b/Terminal.Gui/View/View.Keyboard.cs index b78eb2804b..10f1f97df8 100644 --- a/Terminal.Gui/View/View.Keyboard.cs +++ b/Terminal.Gui/View/View.Keyboard.cs @@ -255,9 +255,7 @@ private void SetHotKeyFromTitle () /// /// If a more focused subview does not handle the key press, this method raises / /// to allow the - /// view to pre-process the key press. If / is not handled - /// / will be raised to invoke any key - /// bindings. + /// view to pre-process the key press. If / is not handled any commands bound to the key will be invoked. /// Then, only if no key bindings are /// handled, / will be raised allowing the view to /// process the key press. @@ -292,8 +290,8 @@ public bool NewKeyDownEvent (Key key) // During (this is what can be cancelled) - // TODO: NewKeyDownEvent returns bool. It should be bool? so state of RaiseInvokingKeyBindingsAndInvokeCommands can be reflected up stack - if (RaiseInvokingKeyBindingsAndInvokeCommands (key) is true || key.Handled) + // TODO: NewKeyDownEvent returns bool. It should be bool? so state of InvokeCommands can be reflected up stack + if (InvokeCommandsBoundToKey (key) is true || key.Handled) { return true; } @@ -336,7 +334,7 @@ bool RaiseKeyDownNotHandled (Key k) /// /// Called when the user presses a key, allowing subscribers to pre-process the key down event. Called - /// before and are raised. Set + /// before key bindings are invoked and is raised. Set /// /// to true to /// stop the key from being processed further. @@ -357,7 +355,7 @@ bool RaiseKeyDownNotHandled (Key k) /// /// Raised when the user presses a key, allowing subscribers to pre-process the key down event. Called - /// before and are raised. Set + /// before key bindings are invoked and is raised. Set /// /// to true to /// stop the key from being processed further. @@ -508,8 +506,7 @@ bool RaiseKeyUp (Key k) private Dictionary CommandImplementations { get; } = new (); /// - /// INTERNAL API: Raises the event and invokes the commands bound to - /// . + /// INTERNAL API: Invokes any commands bound to on this view, adornments, and subviews. /// /// /// @@ -517,28 +514,13 @@ bool RaiseKeyUp (Key k) /// continue. /// if a command was invoked and was not handled (or cancelled); input processing should /// continue. - /// if was handled or a command was invoked and handled (or + /// if at least one command was invoked and handled (or /// cancelled); input processing should stop. /// - internal bool? RaiseInvokingKeyBindingsAndInvokeCommands (Key key) + internal bool? InvokeCommandsBoundToKey (Key key) { KeyBindingScope scope = KeyBindingScope.Focused | KeyBindingScope.HotKey; - // During - if (OnInvokingKeyBindings (key, scope)) - { - return true; - } - - InvokingKeyBindings?.Invoke (this, key); - - if (key.Handled) - { - return true; - } - - // After - // * If no key binding was found, `InvokeKeyBindings` returns `null`. // Continue passing the event (return `false` from `OnInvokeKeyBindings`). // * If key bindings were found, but none handled the key (all `Command`s returned `false`), @@ -547,29 +529,29 @@ bool RaiseKeyUp (Key k) // `InvokeKeyBindings` returns `true`. Continue passing the event (return `false` from `OnInvokeKeyBindings`). bool? handled = InvokeCommands (key, scope); - if (handled is { } && (bool)handled) + if (handled is true) { // Stop processing if any key binding handled the key. // DO NOT stop processing if there are no matching key bindings or none of the key bindings handled the key return handled; } - if (Margin is { } && ProcessAdornmentKeyBindings (Margin, key, scope, ref handled)) + if (Margin is { } && InvokeCommandsBoundToKeyOnAdornment (Margin, key, scope, ref handled)) { return true; } - if (Padding is { } && ProcessAdornmentKeyBindings (Padding, key, scope, ref handled)) + if (Padding is { } && InvokeCommandsBoundToKeyOnAdornment (Padding, key, scope, ref handled)) { return true; } - if (Border is { } && ProcessAdornmentKeyBindings (Border, key, scope, ref handled)) + if (Border is { } && InvokeCommandsBoundToKeyOnAdornment (Border, key, scope, ref handled)) { return true; } - if (ProcessSubViewKeyBindings (key, scope, ref handled)) + if (InvokeCommandsBoundToKeyOnSubviews (key, scope, ref handled)) { return true; } @@ -577,32 +559,9 @@ bool RaiseKeyUp (Key k) return handled; } - /// - /// Called when a key is pressed that may be mapped to a key binding. Set to true to - /// stop the key from being processed by other views. - /// - /// - /// See for an overview of Terminal.Gui keyboard APIs. - /// - /// Contains the details about the key that produced the event. - /// The scope. - /// - /// if the event was raised and was not handled (or cancelled); input processing should - /// continue. - /// if the event was raised and handled (or cancelled); input processing should stop. - /// - protected virtual bool OnInvokingKeyBindings (Key key, KeyBindingScope scope) { return false; } - - // TODO: This does not carry KeyBindingScope, but OnInvokingKeyBindings does - /// - /// Raised when a key is pressed that may be mapped to a key binding. Set to true to - /// stop the key from being processed by other views. - /// - public event EventHandler? InvokingKeyBindings; - - private bool ProcessAdornmentKeyBindings (Adornment adornment, Key key, KeyBindingScope scope, ref bool? handled) + private static bool InvokeCommandsBoundToKeyOnAdornment (Adornment adornment, Key key, KeyBindingScope scope, ref bool? handled) { - bool? adornmentHandled = adornment.RaiseInvokingKeyBindingsAndInvokeCommands (key); + bool? adornmentHandled = adornment.InvokeCommandsBoundToKey (key); if (adornmentHandled is true) { @@ -616,7 +575,7 @@ private bool ProcessAdornmentKeyBindings (Adornment adornment, Key key, KeyBindi foreach (View subview in adornment.Subviews) { - bool? subViewHandled = subview.RaiseInvokingKeyBindingsAndInvokeCommands (key); + bool? subViewHandled = subview.InvokeCommandsBoundToKey (key); if (subViewHandled is { }) { @@ -632,7 +591,7 @@ private bool ProcessAdornmentKeyBindings (Adornment adornment, Key key, KeyBindi return false; } - private bool ProcessSubViewKeyBindings (Key key, KeyBindingScope scope, ref bool? handled, bool invoke = true) + private bool InvokeCommandsBoundToKeyOnSubviews (Key key, KeyBindingScope scope, ref bool? handled, bool invoke = true) { // Now, process any key bindings in the subviews that are tagged to KeyBindingScope.HotKey. foreach (View subview in Subviews) @@ -654,7 +613,7 @@ private bool ProcessSubViewKeyBindings (Key key, KeyBindingScope scope, ref bool return true; } - bool? subViewHandled = subview.RaiseInvokingKeyBindingsAndInvokeCommands (key); + bool? subViewHandled = subview.InvokeCommandsBoundToKey (key); if (subViewHandled is { }) { @@ -667,7 +626,7 @@ private bool ProcessSubViewKeyBindings (Key key, KeyBindingScope scope, ref bool } } - bool recurse = subview.ProcessSubViewKeyBindings (key, scope, ref handled, invoke); + bool recurse = subview.InvokeCommandsBoundToKeyOnSubviews (key, scope, ref handled, invoke); if (recurse || (handled is { } && (bool)handled)) { diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index b3429b190c..b961f68bc7 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -309,7 +309,7 @@ private bool ExpandCollapse (MenuItem? menuItem) protected override bool OnKeyDownNotHandled (Key keyEvent) { // We didn't handle the key, pass it on to host - return _host.RaiseInvokingKeyBindingsAndInvokeCommands (keyEvent) == true; + return _host.InvokeCommandsBoundToKey (keyEvent) == true; } private void Current_TerminalResized (object? sender, SizeChangedEventArgs e) diff --git a/UICatalog/Scenarios/Keys.cs b/UICatalog/Scenarios/Keys.cs index a96f256bb2..7851d342d1 100644 --- a/UICatalog/Scenarios/Keys.cs +++ b/UICatalog/Scenarios/Keys.cs @@ -10,109 +10,136 @@ public class Keys : Scenario public override void Main () { Application.Init (); - ObservableCollection keyPressedList = []; - ObservableCollection invokingKeyBindingsList = new (); + ObservableCollection keyDownList = []; + ObservableCollection keyDownNotHandledList = new (); var win = new Window { Title = GetQuitKeyAndName () }; - var editLabel = new Label { X = 0, Y = 0, Text = "Type text here:" }; - win.Add (editLabel); - var edit = new TextField { X = Pos.Right (editLabel) + 1, Y = Pos.Top (editLabel), Width = Dim.Fill (2) }; + var label = new Label + { + X = 0, + Y = 0, + Text = "_Type text here:" + }; + win.Add (label); + + var edit = new TextField + { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = Dim.Fill (2), + Height = 1, + }; win.Add (edit); - edit.KeyDown += (s, a) => { keyPressedList.Add (a.ToString ()); }; + label = new Label + { + X = 0, + Y = Pos.Bottom (label), + Text = "Last _Application.KeyDown:" + }; + win.Add (label); + var labelAppKeypress = new Label + { + X = Pos.Right (label) + 1, + Y = Pos.Top (label) + }; + win.Add (labelAppKeypress); - edit.InvokingKeyBindings += (s, a) => - { - if (edit.KeyBindings.TryGet (a, out KeyBinding binding)) - { - invokingKeyBindingsList.Add ($"{a}: {string.Join (",", binding.Commands)}"); - } - }; + Application.KeyDown += (s, e) => labelAppKeypress.Text = e.ToString (); - // Last KeyPress: ______ - var keyPressedLabel = new Label + label = new () { - X = Pos.Left (editLabel), Y = Pos.Top (editLabel) + 1, Text = "Last TextView.KeyPressed:" + X = 0, + Y = Pos.Bottom (label), + Text = "_Last TextField.KeyDown:" }; - win.Add (keyPressedLabel); - var labelTextViewKeypress = new Label { X = Pos.Right (keyPressedLabel) + 1, Y = Pos.Top (keyPressedLabel) }; - win.Add (labelTextViewKeypress); + win.Add (label); - edit.KeyDown += (s, e) => labelTextViewKeypress.Text = e.ToString (); - - keyPressedLabel = new Label + var lastTextFieldKeyDownLabel = new Label { - X = Pos.Left (keyPressedLabel), Y = Pos.Bottom (keyPressedLabel), Text = "Last Application.KeyDown:" + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Height = 1, }; - win.Add (keyPressedLabel); - var labelAppKeypress = new Label { X = Pos.Right (keyPressedLabel) + 1, Y = Pos.Top (keyPressedLabel) }; - win.Add (labelAppKeypress); + win.Add (lastTextFieldKeyDownLabel); - Application.KeyDown += (s, e) => labelAppKeypress.Text = e.ToString (); + edit.KeyDown += (s, e) => lastTextFieldKeyDownLabel.Text = e.ToString (); - // Key stroke log: - var keyLogLabel = new Label + // Application key event log: + label = new Label { - X = Pos.Left (editLabel), Y = Pos.Top (editLabel) + 4, Text = "Application Key Events:" + X = 0, + Y = Pos.Bottom (label) + 1, + Text = "Application Key Events:" }; - win.Add (keyLogLabel); + win.Add (label); int maxKeyString = Key.CursorRight.WithAlt.WithCtrl.WithShift.ToString ().Length; - var yOffset = 1; - ObservableCollection keyEventlist = new (); - var keyEventListView = new ListView + ObservableCollection keyEventList = new (); + + var appKeyEventListView = new ListView { X = 0, - Y = Pos.Top (keyLogLabel) + yOffset, - Width = "Key Down:".Length + maxKeyString, + Y = Pos.Bottom (label), + Width = "KeyDown:".Length + maxKeyString, Height = Dim.Fill (), - Source = new ListWrapper (keyEventlist) + Source = new ListWrapper (keyEventList) }; - keyEventListView.ColorScheme = Colors.ColorSchemes ["TopLevel"]; - win.Add (keyEventListView); - - // OnKeyPressed - var onKeyPressedLabel = new Label + appKeyEventListView.ColorScheme = Colors.ColorSchemes ["TopLevel"]; + win.Add (appKeyEventListView); + + // View key events... + edit.KeyDown += (s, a) => { keyDownList.Add (a.ToString ()); }; + + edit.KeyDownNotHandled += (s, a) => + { + if (edit.KeyBindings.TryGet (a, out KeyBinding binding)) + { + keyDownNotHandledList.Add ($"{a}: {string.Join (",", binding.Commands)}"); + } + }; + + // KeyDown + label = new Label { - X = Pos.Right (keyEventListView) + 1, Y = Pos.Top (editLabel) + 4, Text = "TextView KeyDown:" + X = Pos.Right (appKeyEventListView) + 1, + Y = Pos.Top (label), + Text = "TextView Key Down:" }; - win.Add (onKeyPressedLabel); + win.Add (label); - yOffset = 1; - - var onKeyPressedListView = new ListView + var onKeyDownListView = new ListView { - X = Pos.Left (onKeyPressedLabel), - Y = Pos.Top (onKeyPressedLabel) + yOffset, + X = Pos.Left (label), + Y = Pos.Bottom (label), Width = maxKeyString, Height = Dim.Fill (), - Source = new ListWrapper (keyPressedList) + Source = new ListWrapper (keyDownList) }; - onKeyPressedListView.ColorScheme = Colors.ColorSchemes ["TopLevel"]; - win.Add (onKeyPressedListView); + onKeyDownListView.ColorScheme = Colors.ColorSchemes ["TopLevel"]; + win.Add (onKeyDownListView); - // OnInvokeKeyBindings - var onInvokingKeyBindingsLabel = new Label + // KeyDownNotHandled + label = new Label { - X = Pos.Right (onKeyPressedListView) + 1, - Y = Pos.Top (editLabel) + 4, - Text = "TextView InvokingKeyBindings:" + X = Pos.Right (onKeyDownListView) + 1, + Y = Pos.Top (label), + Text = "TextView KeyDownNotHandled:" }; - win.Add (onInvokingKeyBindingsLabel); + win.Add (label); - var onInvokingKeyBindingsListView = new ListView + var onKeyDownNotHandledListView = new ListView { - X = Pos.Left (onInvokingKeyBindingsLabel), - Y = Pos.Top (onInvokingKeyBindingsLabel) + yOffset, - Width = Dim.Fill (1), + X = Pos.Left (label), + Y = Pos.Bottom (label), + Width = maxKeyString, Height = Dim.Fill (), - Source = new ListWrapper (invokingKeyBindingsList) + Source = new ListWrapper (keyDownNotHandledList) }; - onInvokingKeyBindingsListView.ColorScheme = Colors.ColorSchemes ["TopLevel"]; - win.Add (onInvokingKeyBindingsListView); + onKeyDownNotHandledListView.ColorScheme = Colors.ColorSchemes ["TopLevel"]; + win.Add (onKeyDownNotHandledListView); - //Application.KeyDown += (s, a) => KeyDownPressUp (a, "Down"); Application.KeyDown += (s, a) => KeyDownPressUp (a, "Down"); Application.KeyUp += (s, a) => KeyDownPressUp (a, "Up"); @@ -120,10 +147,9 @@ void KeyDownPressUp (Key args, string updown) { // BUGBUG: KeyEvent.ToString is badly broken var msg = $"Key{updown,-7}: {args}"; - keyEventlist.Add (msg); - keyEventListView.MoveDown (); - onKeyPressedListView.MoveDown (); - onInvokingKeyBindingsListView.MoveDown (); + keyEventList.Add (msg); + appKeyEventListView.MoveDown (); + onKeyDownNotHandledListView.MoveDown (); } Application.Run (win); diff --git a/UICatalog/Scenarios/VkeyPacketSimulator.cs b/UICatalog/Scenarios/VkeyPacketSimulator.cs index 059203ea7c..1659eaebc5 100644 --- a/UICatalog/Scenarios/VkeyPacketSimulator.cs +++ b/UICatalog/Scenarios/VkeyPacketSimulator.cs @@ -148,7 +148,7 @@ public override void Main () } }; - tvInput.InvokingKeyBindings += (s, e) => + tvInput.KeyDownNotHandled += (s, e) => { Key ev = e; diff --git a/UnitTests/Application/KeyboardTests.cs b/UnitTests/Application/KeyboardTests.cs index 2cbc322883..ba472d3aba 100644 --- a/UnitTests/Application/KeyboardTests.cs +++ b/UnitTests/Application/KeyboardTests.cs @@ -163,39 +163,39 @@ public void KeyBinding_Application_RemoveKeyBinding_Removes () public void KeyBinding_OnKeyDown () { var view = new ScopedKeyBindingView (); - var invoked = false; - view.InvokingKeyBindings += (s, e) => invoked = true; + var keyWasHandled = false; + view.KeyDownNotHandled += (s, e) => keyWasHandled = true; var top = new Toplevel (); top.Add (view); Application.Begin (top); Application.RaiseKeyDownEvent (Key.A); - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.True (view.ApplicationCommand); - invoked = false; + keyWasHandled = false; view.ApplicationCommand = false; Application.KeyBindings.Remove (KeyCode.A); Application.RaiseKeyDownEvent (Key.A); // old - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.False (view.ApplicationCommand); Application.KeyBindings.Add (Key.A.WithCtrl, view, Command.Save); Application.RaiseKeyDownEvent (Key.A); // old - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.False (view.ApplicationCommand); Application.RaiseKeyDownEvent (Key.A.WithCtrl); // new - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.True (view.ApplicationCommand); - invoked = false; + keyWasHandled = false; Application.RaiseKeyDownEvent (Key.H); - Assert.True (invoked); + Assert.False (keyWasHandled); - invoked = false; + keyWasHandled = false; Assert.False (view.HasFocus); Application.RaiseKeyDownEvent (Key.F); - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.True (view.ApplicationCommand); Assert.True (view.HotKeyCommand); @@ -208,23 +208,23 @@ public void KeyBinding_OnKeyDown () public void KeyBinding_OnKeyDown_Negative () { var view = new ScopedKeyBindingView (); - var invoked = false; - view.InvokingKeyBindings += (s, e) => invoked = true; + var keyWasHandled = false; + view.KeyDownNotHandled += (s, e) => keyWasHandled = true; var top = new Toplevel (); top.Add (view); Application.Begin (top); Application.RaiseKeyDownEvent (Key.A.WithCtrl); - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.False (view.ApplicationCommand); Assert.False (view.HotKeyCommand); Assert.False (view.FocusedCommand); - invoked = false; + keyWasHandled = false; Assert.False (view.HasFocus); Application.RaiseKeyDownEvent (Key.Z); - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.False (view.ApplicationCommand); Assert.False (view.HotKeyCommand); Assert.False (view.FocusedCommand); diff --git a/UnitTests/View/HotKeyTests.cs b/UnitTests/View/Keyboard/HotKeyTests.cs similarity index 97% rename from UnitTests/View/HotKeyTests.cs rename to UnitTests/View/Keyboard/HotKeyTests.cs index a3edaaf6c0..4c1e7812cd 100644 --- a/UnitTests/View/HotKeyTests.cs +++ b/UnitTests/View/Keyboard/HotKeyTests.cs @@ -95,7 +95,7 @@ public void NewKeyDownEvent_Ignores_Focus_KeyBindings_SuperView () { var view = new View (); view.KeyBindings.Add (Key.A, Command.HotKey); // implies KeyBindingScope.Focused - so this should not be invoked - view.InvokingKeyBindings += (s, e) => { Assert.Fail (); }; + view.KeyDownNotHandled += (s, e) => { Assert.Fail (); }; var superView = new View (); superView.Add (view); @@ -109,8 +109,11 @@ public void NewKeyDownEvent_Honors_HotKey_KeyBindings_SuperView () { var view = new View (); view.KeyBindings.Add (Key.A, KeyBindingScope.HotKey, Command.HotKey); - bool invoked = false; - view.InvokingKeyBindings += (s, e) => { invoked = true; }; + bool hotKeyInvoked = false; + view.HandlingHotKey += (s, e) => { hotKeyInvoked = true; }; + + bool notHandled = false; + view.KeyDownNotHandled += (s, e) => { notHandled = true; }; var superView = new View (); superView.Add (view); @@ -118,7 +121,8 @@ public void NewKeyDownEvent_Honors_HotKey_KeyBindings_SuperView () var ke = Key.A; superView.NewKeyDownEvent (ke); - Assert.True (invoked); + Assert.False (notHandled); + Assert.True (hotKeyInvoked); } diff --git a/UnitTests/View/Keyboard/KeyboardEventTests.cs b/UnitTests/View/Keyboard/KeyboardEventTests.cs index 6b484ec58e..12cffb9a73 100644 --- a/UnitTests/View/Keyboard/KeyboardEventTests.cs +++ b/UnitTests/View/Keyboard/KeyboardEventTests.cs @@ -7,8 +7,8 @@ namespace Terminal.Gui.ViewTests; public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews { /// - /// This tests that when a new key down event is sent to the view will fire the 3 key-down related - /// events: KeyDown, InvokingKeyBindings, and ProcessKeyDown. Note that KeyUp is independent. + /// This tests that when a new key down event is sent to the view will fire the key-down related + /// events: KeyDown and KeyDownNotHandled. Note that KeyUp is independent. /// [Theory] [MemberData (nameof (AllViewTypes))] @@ -33,27 +33,18 @@ public void AllViews_NewKeyDownEvent_All_EventsFire (Type viewType) keyDown = true; }; - var invokingKeyBindings = false; - - view.InvokingKeyBindings += (s, a) => - { - a.Handled = false; // don't handle it so the other events are called - invokingKeyBindings = true; - }; - - var keyDownProcessed = false; + var keyDownNotHandled = false; view.KeyDownNotHandled += (s, a) => { a.Handled = true; - keyDownProcessed = true; + keyDownNotHandled = true; }; // Key.Empty is invalid, but it's used here to test that the event is fired Assert.True (view.NewKeyDownEvent (Key.Empty)); // this will be true because the ProcessKeyDown event handled it Assert.True (keyDown); - Assert.True (invokingKeyBindings); - Assert.True (keyDownProcessed); + Assert.True (keyDownNotHandled); view.Dispose (); } @@ -96,7 +87,7 @@ public void AllViews_NewKeyUpEvent_All_EventsFire (Type viewType) public void NewKeyDownUpEvents_Events_Are_Raised_With_Only_Key_Modifiers (bool shift, bool alt, bool control) { var keyDown = false; - var keyPressed = false; + var keyDownNotHandled = false; var keyUp = false; var view = new OnNewKeyTestView (); @@ -112,7 +103,7 @@ public void NewKeyDownUpEvents_Events_Are_Raised_With_Only_Key_Modifiers (bool s Assert.True (view.OnKeyDownCalled); keyDown = true; }; - view.KeyDownNotHandled += (s, e) => { keyPressed = true; }; + view.KeyDownNotHandled += (s, e) => { keyDownNotHandled = true; }; view.KeyUp += (s, e) => { @@ -125,11 +116,6 @@ public void NewKeyDownUpEvents_Events_Are_Raised_With_Only_Key_Modifiers (bool s keyUp = true; }; - //view.ProcessKeyDownEvent (new (Key.Null | (shift ? Key.ShiftMask : 0) | (alt ? Key.AltMask : 0) | (control ? Key.CtrlMask : 0))); - //Assert.True (keyDown); - //Assert.True (view.OnKeyDownWasCalled); - //Assert.True (view.OnProcessKeyDownWasCalled); - view.NewKeyDownEvent ( new ( KeyCode.Null @@ -138,7 +124,7 @@ public void NewKeyDownUpEvents_Events_Are_Raised_With_Only_Key_Modifiers (bool s | (control ? KeyCode.CtrlMask : 0) ) ); - Assert.True (keyPressed); + Assert.True (keyDownNotHandled); Assert.True (view.OnKeyDownCalled); Assert.True (view.OnProcessKeyDownCalled); @@ -154,107 +140,11 @@ public void NewKeyDownUpEvents_Events_Are_Raised_With_Only_Key_Modifiers (bool s Assert.True (view.OnKeyUpCalled); } - [Fact] - public void NewKeyDownEvent_InvokingKeyBindings_Handled_Cancels () - { - var view = new View (); - var keyPressInvoked = false; - var invokingKeyBindingsInvoked = false; - var processKeyPressInvoked = false; - var setHandledTo = false; - - view.KeyDown += (s, e) => - { - keyPressInvoked = true; - Assert.False (e.Handled); - Assert.Equal (KeyCode.N, e.KeyCode); - }; - - view.InvokingKeyBindings += (s, e) => - { - invokingKeyBindingsInvoked = true; - e.Handled = setHandledTo; - Assert.Equal (setHandledTo, e.Handled); - Assert.Equal (KeyCode.N, e.KeyCode); - }; - - view.KeyDownNotHandled += (s, e) => - { - processKeyPressInvoked = true; - processKeyPressInvoked = true; - Assert.False (e.Handled); - Assert.Equal (KeyCode.N, e.KeyCode); - }; - - view.NewKeyDownEvent (Key.N); - Assert.True (keyPressInvoked); - Assert.True (invokingKeyBindingsInvoked); - Assert.True (processKeyPressInvoked); - - keyPressInvoked = false; - invokingKeyBindingsInvoked = false; - processKeyPressInvoked = false; - setHandledTo = true; - view.NewKeyDownEvent (Key.N); - Assert.True (keyPressInvoked); - Assert.True (invokingKeyBindingsInvoked); - Assert.False (processKeyPressInvoked); - } - - [Fact] - public void NewKeyDownEvent_InvokingKeyBindings_Handled_True_Stops_Processing () - { - var keyDown = false; - var invokingKeyBindings = false; - var keyPressed = false; - - var view = new OnNewKeyTestView (); - Assert.True (view.CanFocus); - view.CancelVirtualMethods = false; - - view.KeyDown += (s, e) => - { - Assert.Equal (KeyCode.A, e.KeyCode); - Assert.False (keyDown); - Assert.True (view.OnKeyDownCalled); - e.Handled = false; - keyDown = true; - }; - - view.InvokingKeyBindings += (s, e) => - { - Assert.Equal (KeyCode.A, e.KeyCode); - Assert.False (keyPressed); - Assert.True (view.OnInvokingKeyBindingsCalled); - e.Handled = true; - invokingKeyBindings = true; - }; - - view.KeyDownNotHandled += (s, e) => - { - Assert.Equal (KeyCode.A, e.KeyCode); - Assert.False (keyPressed); - Assert.False (view.OnProcessKeyDownCalled); - e.Handled = true; - keyPressed = true; - }; - - view.NewKeyDownEvent (Key.A); - Assert.True (keyDown); - Assert.True (invokingKeyBindings); - Assert.False (keyPressed); - - Assert.True (view.OnKeyDownCalled); - Assert.True (view.OnInvokingKeyBindingsCalled); - Assert.False (view.OnProcessKeyDownCalled); - } - [Fact] public void NewKeyDownEvent_Handled_True_Stops_Processing () { var keyDown = false; - var invokingKeyBindings = false; - var keyPressed = false; + var keyDownNotHandled = false; var view = new OnNewKeyTestView (); Assert.True (view.CanFocus); @@ -269,31 +159,21 @@ public void NewKeyDownEvent_Handled_True_Stops_Processing () keyDown = true; }; - view.InvokingKeyBindings += (s, e) => - { - Assert.Equal (KeyCode.A, e.KeyCode); - Assert.False (keyPressed); - Assert.False (view.OnInvokingKeyBindingsCalled); - e.Handled = true; - invokingKeyBindings = true; - }; view.KeyDownNotHandled += (s, e) => { Assert.Equal (KeyCode.A, e.KeyCode); - Assert.False (keyPressed); + Assert.False (keyDownNotHandled); Assert.False (view.OnProcessKeyDownCalled); e.Handled = true; - keyPressed = true; + keyDownNotHandled = true; }; view.NewKeyDownEvent (Key.A); Assert.True (keyDown); - Assert.False (invokingKeyBindings); - Assert.False (keyPressed); + Assert.False (keyDownNotHandled); Assert.True (view.OnKeyDownCalled); - Assert.False (view.OnInvokingKeyBindingsCalled); Assert.False (view.OnProcessKeyDownCalled); } @@ -301,8 +181,7 @@ public void NewKeyDownEvent_Handled_True_Stops_Processing () public void NewKeyDownEvent_KeyDown_Handled_Stops_Processing () { var view = new View (); - var invokingKeyBindingsInvoked = false; - var processKeyPressInvoked = false; + var keyDownNotHandled = false; var setHandledTo = false; view.KeyDown += (s, e) => @@ -312,38 +191,27 @@ public void NewKeyDownEvent_KeyDown_Handled_Stops_Processing () Assert.Equal (KeyCode.N, e.KeyCode); }; - view.InvokingKeyBindings += (s, e) => - { - invokingKeyBindingsInvoked = true; - Assert.False (e.Handled); - Assert.Equal (KeyCode.N, e.KeyCode); - }; - view.KeyDownNotHandled += (s, e) => { - processKeyPressInvoked = true; + keyDownNotHandled = true; Assert.False (e.Handled); Assert.Equal (KeyCode.N, e.KeyCode); }; view.NewKeyDownEvent (Key.N); - Assert.True (invokingKeyBindingsInvoked); - Assert.True (processKeyPressInvoked); + Assert.True (keyDownNotHandled); - invokingKeyBindingsInvoked = false; - processKeyPressInvoked = false; + keyDownNotHandled = false; setHandledTo = true; view.NewKeyDownEvent (Key.N); - Assert.False (invokingKeyBindingsInvoked); - Assert.False (processKeyPressInvoked); + Assert.False (keyDownNotHandled); } [Fact] public void NewKeyDownEvent_ProcessKeyDown_Handled_Stops_Processing () { var keyDown = false; - var invokingKeyBindings = false; - var processKeyDown = false; + var keyDownNotHandled = false; var view = new OnNewKeyTestView (); Assert.True (view.CanFocus); @@ -358,31 +226,20 @@ public void NewKeyDownEvent_ProcessKeyDown_Handled_Stops_Processing () keyDown = true; }; - view.InvokingKeyBindings += (s, e) => - { - Assert.Equal (KeyCode.A, e.KeyCode); - Assert.False (processKeyDown); - Assert.True (view.OnInvokingKeyBindingsCalled); - e.Handled = false; - invokingKeyBindings = true; - }; - view.KeyDownNotHandled += (s, e) => { Assert.Equal (KeyCode.A, e.KeyCode); - Assert.False (processKeyDown); + Assert.False (keyDownNotHandled); Assert.True (view.OnProcessKeyDownCalled); e.Handled = true; - processKeyDown = true; + keyDownNotHandled = true; }; view.NewKeyDownEvent (Key.A); Assert.True (keyDown); - Assert.True (invokingKeyBindings); - Assert.True (processKeyDown); + Assert.True (keyDownNotHandled); Assert.True (view.OnKeyDownCalled); - Assert.True (view.OnInvokingKeyBindingsCalled); Assert.True (view.OnProcessKeyDownCalled); } @@ -409,7 +266,6 @@ public void NewKeyUpEvent_KeyUp_Handled_True_Stops_Processing () Assert.True (view.OnKeyUpCalled); Assert.False (view.OnKeyDownCalled); - Assert.False (view.OnInvokingKeyBindingsCalled); Assert.False (view.OnProcessKeyDownCalled); } @@ -417,12 +273,12 @@ public void NewKeyUpEvent_KeyUp_Handled_True_Stops_Processing () [InlineData (null, null)] [InlineData (true, true)] [InlineData (false, false)] - public void RaiseInvokingKeyBindingsAndInvokeCommands_Returns_Nullable_Properly (bool? toReturn, bool? expected) + public void InvokeCommandsBoundToKey_Returns_Nullable_Properly (bool? toReturn, bool? expected) { var view = new KeyBindingsTestView (); view.CommandReturns = toReturn; - bool? result = view.RaiseInvokingKeyBindingsAndInvokeCommands (Key.A); + bool? result = view.InvokeCommandsBoundToKey (Key.A); Assert.Equal (expected, result); } @@ -444,20 +300,11 @@ public class OnNewKeyTestView : View { public OnNewKeyTestView () { CanFocus = true; } public bool CancelVirtualMethods { set; private get; } - public bool OnInvokingKeyBindingsCalled { get; set; } public bool OnKeyDownCalled { get; set; } public bool OnProcessKeyDownCalled { get; set; } public bool OnKeyUpCalled { get; set; } public override string Text { get; set; } - protected override bool OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope) - { - - OnInvokingKeyBindingsCalled = true; - - return CancelVirtualMethods; - } - protected override bool OnKeyDown (Key keyEvent) { OnKeyDownCalled = true; diff --git a/UnitTests/View/ViewKeyBindingTests.cs b/UnitTests/View/Keyboard/ViewKeyBindingTests.cs similarity index 76% rename from UnitTests/View/ViewKeyBindingTests.cs rename to UnitTests/View/Keyboard/ViewKeyBindingTests.cs index 05471e4b51..b1483c4dad 100644 --- a/UnitTests/View/ViewKeyBindingTests.cs +++ b/UnitTests/View/Keyboard/ViewKeyBindingTests.cs @@ -11,37 +11,38 @@ public class ViewKeyBindingTests (ITestOutputHelper output) public void Focus_KeyBinding () { var view = new ScopedKeyBindingView (); - var invoked = false; - view.InvokingKeyBindings += (s, e) => invoked = true; + var keyWasHandled = false; + view.KeyDownNotHandled += (s, e) => keyWasHandled = true; var top = new Toplevel (); top.Add (view); Application.Begin (top); Application.RaiseKeyDownEvent (Key.A); - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.True (view.ApplicationCommand); - invoked = false; + keyWasHandled = false; Application.RaiseKeyDownEvent (Key.H); - Assert.True (invoked); + Assert.True (view.HotKeyCommand); + Assert.False (keyWasHandled); - invoked = false; + keyWasHandled = false; Assert.False (view.HasFocus); Application.RaiseKeyDownEvent (Key.F); - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.False (view.FocusedCommand); - invoked = false; + keyWasHandled = false; view.CanFocus = true; view.SetFocus (); Assert.True (view.HasFocus); Application.RaiseKeyDownEvent (Key.F); - Assert.True (invoked); + Assert.True (view.FocusedCommand); + Assert.False (keyWasHandled); // Command was invoked, but wasn't handled Assert.True (view.ApplicationCommand); Assert.True (view.HotKeyCommand); - Assert.True (view.FocusedCommand); top.Dispose (); } @@ -50,23 +51,23 @@ public void Focus_KeyBinding () public void Focus_KeyBinding_Negative () { var view = new ScopedKeyBindingView (); - var invoked = false; - view.InvokingKeyBindings += (s, e) => invoked = true; + var keyWasHandled = false; + view.KeyDownNotHandled += (s, e) => keyWasHandled = true; var top = new Toplevel (); top.Add (view); Application.Begin (top); Application.RaiseKeyDownEvent (Key.Z); - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.False (view.ApplicationCommand); Assert.False (view.HotKeyCommand); Assert.False (view.FocusedCommand); - invoked = false; + keyWasHandled = false; Assert.False (view.HasFocus); Application.RaiseKeyDownEvent (Key.F); - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.False (view.ApplicationCommand); Assert.False (view.HotKeyCommand); Assert.False (view.FocusedCommand); @@ -78,28 +79,29 @@ public void Focus_KeyBinding_Negative () public void HotKey_KeyBinding () { var view = new ScopedKeyBindingView (); - var invoked = false; - view.InvokingKeyBindings += (s, e) => invoked = true; + var keyWasHandled = false; + view.KeyDownNotHandled += (s, e) => keyWasHandled = true; var top = new Toplevel (); top.Add (view); Application.Begin (top); - invoked = false; + keyWasHandled = false; Application.RaiseKeyDownEvent (Key.H); - Assert.True (invoked); Assert.True (view.HotKeyCommand); + Assert.False (keyWasHandled); view.HotKey = KeyCode.Z; - invoked = false; + keyWasHandled = false; view.HotKeyCommand = false; Application.RaiseKeyDownEvent (Key.H); // old hot key - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.False (view.HotKeyCommand); Application.RaiseKeyDownEvent (Key.Z); // new hot key - Assert.True (invoked); Assert.True (view.HotKeyCommand); + Assert.False (keyWasHandled); + top.Dispose (); } @@ -108,18 +110,18 @@ public void HotKey_KeyBinding () public void HotKey_KeyBinding_Negative () { var view = new ScopedKeyBindingView (); - var invoked = false; - view.InvokingKeyBindings += (s, e) => invoked = true; + var keyWasHandled = false; + view.KeyDownNotHandled += (s, e) => keyWasHandled = true; var top = new Toplevel (); top.Add (view); Application.Begin (top); Application.RaiseKeyDownEvent (Key.Z); - Assert.False (invoked); + Assert.False (keyWasHandled); Assert.False (view.HotKeyCommand); - invoked = false; + keyWasHandled = false; Application.RaiseKeyDownEvent (Key.F); Assert.False (view.HotKeyCommand); top.Dispose (); diff --git a/docfx/docs/keyboard.md b/docfx/docs/keyboard.md index 8b29a1a6d0..ddace3282c 100644 --- a/docfx/docs/keyboard.md +++ b/docfx/docs/keyboard.md @@ -59,22 +59,25 @@ The Command can be invoked even if the `View` that defines them is not focused o ### **Handling Keyboard Events** -Keyboard events are retrieved from [Console Drivers](drivers.md) and passed on -to the [Application](~/api/Terminal.Gui.Application.yml) class by the [Main Loop](mainloop.md). +Keyboard events are retrieved from [Console Drivers](drivers.md) each iteration of the [Application](~/api/Terminal.Gui.Application.yml) [Main Loop](mainloop.md). The console driver raises the @Terminal.Gui.ConsoleDriver.KeyDown and @Terminal.Gui.ConsoleDriver.KeyUp events which invoke @Terminal.Gui.Application.RaiseKeyDown(Terminal.Gui.Key) and @Terminal.Gui.Application.RaiseKeyUp(Terminal.Gui.Key) respectively. -[Application](~/api/Terminal.Gui.Application.yml) then determines the current [Toplevel](~/api/Terminal.Gui.Toplevel.yml) view -(either the default created by calling @Terminal.Gui.Application.Init(Terminal.Gui.ConsoleDriver,System.String), or the one set by calling `Application.Run`). The mouse event, using [Viewport-relative coordinates](xref:Terminal.Gui.View.Viewport) is then passed to the @Terminal.Gui.View.NewKeyDownEvent(Terminal.Gui.Key) method of the current [Toplevel](~/api/Terminal.Gui.Toplevel.yml) view. + NOTE: Not all drivers/platforms support sensing distinct KeyUp events. These drivers will simulate KeyUp events by raising @Terminal.Gui.ConsoleDriver.KeyUp after @Terminal.Gui.ConsoleDriver.KeyDown. -If the view is enabled, the @Terminal.Gui.View.NewKeyDownEvent(Terminal.Gui.Key) method will do the following: +@Terminal.Gui.Application.RaiseKeyDown(Terminal.Gui.Key) raises @Terminal.Gui.Application.KeyDown and then calls @Terminal.Gui.View.NewKeyDownEvent(Terminal.Gui.Key) on all toplevel Views. If no View handles the key event, any Application-scoped key bindings will be invoked. -1) If the view has a subview that has focus, 'ProcessKeyDown' on the focused view will be called. If the focused view handles the key press, processing stops. -2) If there is no focused sub-view, or the focused sub-view does not handle the key press, @Terminal.Gui.View.OnKeyDown(Terminal.Gui.Key) will be called. If the view handles the key press, processing stops. -3) If the view does not handle the key press, @Terminal.Gui.TextField.OnInvokingKeyBindings(Terminal.Gui.Key,Terminal.Gui.KeyBindingScope) will be called. This method calls @Terminal.Gui.View.InvokeKeyBindings(Terminal.Gui.Key,Terminal.Gui.KeyBindingScope) to invoke any keys bound to commands. If the key is bound and any of it's command handlers return true, processing stops. -4) If the key is not bound, or the bound command handlers do not return true, @Terminal.Gui.View.OnProcessKeyDown(Terminal.Gui.Key) is called. If the view handles the key press, processing stops. +@Terminal.Gui.Application.RaiseKeyDown(Terminal.Gui.Key) raises @Terminal.Gui.Application.KeyDown and then calls @Terminal.Gui.View.NewKeyUpEvent(Terminal.Gui.Key) on all toplevel Views. + +If a view is enabled, the @Terminal.Gui.View.NewKeyDownEvent(Terminal.Gui.Key) method will do the following: + +1) If the view has a subview that has focus, 'NewKeyDown' on the focused view will be called. This is recursive. If the most-focused view handles the key press, processing stops. +2) If there is no most-focused sub-view, or a most-focused sub-view does not handle the key press, @Terminal.Gui.View.OnKeyDown(Terminal.Gui.Key) will be called. If the view handles the key press, processing stops. +3) If @Terminal.Gui.View.OnKeyDown(Terminal.Gui.Key) does not handle the event. @Terminal.Gui.View.KeyDown will be raised. +4) If the view does not handle the key down event, any bindings for the key will be invoked (see @Terminal.Gui.View.KeyBindings). If the key is bound and any of it's command handlers return true, processing stops. +5) If the key is not bound, or the bound command handlers do not return true, @Terminal.Gui.View.OnKeyDownNotHandled(Terminal.Gui.Key) is called. ## **Application Key Handling** -To define application key handling logic for an entire application in cases where the methods listed above are not suitable, use the `Application.OnKeyDown` event. +To define application key handling logic for an entire application in cases where the methods listed above are not suitable, use the @Terminal.Gui.Application.KeyDown event. ## **Key Down/Up Events** @@ -90,17 +93,14 @@ To define application key handling logic for an entire application in cases wher - `NewKeyDownEvent` is called on the most-focused SubView (if any) that has focus. If that call returns true, the method returns. - Calls `OnKeyDown`. - **During** - - Assuming `OnKeyDown` call returns false (indicating the key wasn't handled) - - `OnInvokingKeyBindings` is called to invoke any bound commands. - - `OnInvokingKeyBindings` fires the `InvokingKeyBindings` event + - Assuming `OnKeyDown` call returns false (indicating the key wasn't handled) any commands bound to the key will be invoked. - **After** - - Assuming `OnInvokingKeyBindings` returns false (indicating the key wasn't handled) - - `OnProcessKeyDown` is called to process the key. - - `OnProcessKeyDown` fires the `ProcessKeyDown` event + - Assuming no keybinding was found or all invoked commands were not handled: + - `OnKeyDownNotHandled` is called to process the key. + - `KeyDownNotHandled` is raised. -- Subclasses of `View` can (rarely) override `OnKeyDown` to see keys before they are processed by `OnInvokingKeyBindings` and `OnProcessKeyDown -- Subclasses of `View` can (rarely) override `OnInvokingKeyBindings` to see keys before they are processed by `OnProcessKeyDown` -- Subclasses of `View` can (often) override `OnProcessKeyDown` to do normal key processing. +- Subclasses of `View` can (rarely) override `OnKeyDown` (or subscribe to `KeyDown`) to see keys before they are processed +- Subclasses of `View` can (often) override `OnKeyDownNotHandled` to do key processing for keys that were not previously handled. `TextField` and `TextView` are examples. ## ConsoleDriver