From f3b93a58dd7f8830929cba33be85216d38cdc372 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 1 Oct 2024 15:34:55 -0600 Subject: [PATCH] Fixed RadioGroup 2 --- Terminal.Gui/View/View.Navigation.cs | 1 + Terminal.Gui/Views/CheckBox.cs | 19 ++- Terminal.Gui/Views/Label.cs | 6 +- Terminal.Gui/Views/RadioGroup.cs | 92 ++++++++--- UICatalog/Scenarios/Buttons.cs | 6 +- UICatalog/Scenarios/Generic.cs | 4 +- UnitTests/Views/CheckBoxTests.cs | 105 +++++++++--- UnitTests/Views/RadioGroupTests.cs | 235 ++++++++++++++++++++++++++- docfx/docs/navigation.md | 15 +- 9 files changed, 410 insertions(+), 73 deletions(-) diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs index 1e8f41bad..2ac8d540d 100644 --- a/Terminal.Gui/View/View.Navigation.cs +++ b/Terminal.Gui/View/View.Navigation.cs @@ -394,6 +394,7 @@ public partial class View // Focus and cross-view navigation management (TabStop /// See the View Navigation Deep Dive for more information: /// /// + /// if the focus changed; false otherwise. public bool SetFocus () { (bool focusSet, bool _) = SetHasFocusTrue (Application.Navigation?.GetFocused ()); diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index 8e5297ef0..b996d6274 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -1,4 +1,6 @@ #nullable enable +using System.Reflection.Metadata; + namespace Terminal.Gui; /// Shows a check box that can be cycled between three states. @@ -20,8 +22,13 @@ public class CheckBox : View CanFocus = true; - // Things this view knows how to do - AddCommand (Command.Accept, AdvanceCheckState); + // Select (Space key and single-click) - Advance state and raise Select event + AddCommand (Command.Select, AdvanceCheckState); + + // Accept (Enter key and double-click) - Raise Accept event - DO NOT advance state + AddCommand (Command.Accept, RaiseAcceptEvent); + + // Hotkey - Advance state and raise Select event - DO NOT raise Accept AddCommand (Command.HotKey, AdvanceCheckState); TitleChanged += Checkbox_TitleChanged; @@ -161,15 +168,9 @@ public class CheckBox : View return e.Cancel; } - // By default, Command.Accept calls OnAccept, so we need to call it here to ensure that the Accept event is fired. - if (RaiseAcceptEvent () == true) - { - return true; - } - CheckedState = e.NewValue; - return true; + return RaiseSelectEvent (); } /// Raised when the state is changing. diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index 4d81d7181..c532a3e76 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -17,7 +17,7 @@ public class Label : View Width = Dim.Auto (DimAutoStyle.Text); // Things this view knows how to do - AddCommand (Command.HotKey, FocusNext); + AddCommand (Command.HotKey, context => InvokeHotKeyOnNext(context)); TitleChanged += Label_TitleChanged; MouseClick += Label_MouseClick; @@ -51,12 +51,12 @@ public class Label : View set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value; } - private bool? FocusNext () + private bool? InvokeHotKeyOnNext (CommandContext context) { int me = SuperView?.Subviews.IndexOf (this) ?? -1; if (me != -1 && me < SuperView?.Subviews.Count - 1) { - SuperView?.Subviews [me + 1].SetFocus (); + SuperView?.Subviews [me + 1].InvokeCommand(Command.HotKey, context.Key, context.KeyBinding); } return true; diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index ab6829af6..537dc9e6e 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -1,4 +1,5 @@ -namespace Terminal.Gui; +#nullable enable +namespace Terminal.Gui; /// Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time. public class RadioGroup : View, IDesignable, IOrientation @@ -82,16 +83,17 @@ public class RadioGroup : View, IDesignable, IOrientation } } - SelectedItem = Cursor; - - return true; + return SetSelectedItem (Cursor); }); AddCommand ( Command.Accept, () => { - SelectedItem = Cursor; + if (!SetSelectedItem (Cursor)) + { + return false; + } return RaiseAcceptEvent () is false; } @@ -101,14 +103,41 @@ public class RadioGroup : View, IDesignable, IOrientation Command.HotKey, ctx => { - if (ctx.KeyBinding?.Context is { } && (int)ctx.KeyBinding?.Context! < _radioLabels.Count) - { - SelectedItem = (int)ctx.KeyBinding?.Context!; + var item = ctx.KeyBinding?.Context as int?; - return RaiseSelectEvent () is true or null; + if (HasFocus) + { + if (ctx is { KeyBinding: { } } && (ctx.KeyBinding.Value.BoundView != this || HotKey == ctx.Key?.NoAlt.NoCtrl.NoShift)) + { + // It's this.HotKey OR Another View (Label?) forwarded the hotkey command to us - Act just like `Space` (Select) + return InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding); + } } - return !SetFocus (); + if (item is { } && item < _radioLabels.Count) + { + if (item.Value == SelectedItem) + { + return true; + } + + // If a RadioItem.HotKey is pressed we always set the selected item - never SetFocus + if (SetSelectedItem (item.Value)) + { + return true; + } + + return false; + } + + if (SelectedItem == -1 && SetSelectedItem (0)) + { + return true; + } + + SetFocus (); + + return true; }); _orientationHelper = new (this); @@ -247,12 +276,36 @@ public class RadioGroup : View, IDesignable, IOrientation public int SelectedItem { get => _selected; - set + set => SetSelectedItem (value); + } + + /// + /// INTERNAL Sets the selected item. + /// + /// + /// true if the selection changed. + private bool SetSelectedItem (int value) + { + if (_selected == value || value > _radioLabels.Count - 1) { - OnSelectedItemChanged (value, SelectedItem); - Cursor = Math.Max (_selected, 0); - SetNeedsDisplay (); + return false; } + + if (RaiseSelectEvent () == true) + { + return false; + } + + int savedSelected = _selected; + _selected = value; + Cursor = Math.Max (_selected, 0); + + OnSelectedItemChanged (value, SelectedItem); + SelectedItemChanged?.Invoke (this, new (SelectedItem, savedSelected)); + + SetNeedsDisplay (); + + return true; } /// @@ -370,16 +423,7 @@ public class RadioGroup : View, IDesignable, IOrientation /// Called whenever the current selected item changes. Invokes the event. /// /// - public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem) - { - if (_selected == selectedItem) - { - return; - } - - _selected = selectedItem; - SelectedItemChanged?.Invoke (this, new (selectedItem, previousSelectedItem)); - } + protected virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem) { } /// /// Gets or sets the index for the cursor. The cursor may or may not be the selected diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index f3e9158dd..a320e87ef 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -220,7 +220,7 @@ public class Buttons : Scenario var label = new Label { X = 2, Y = Pos.Bottom (computedFrame) + 1, - Text = "Text Alignment (changes the four buttons above): " + Text = "Text Ali_gnment (changes the four buttons above): " }; main.Add (label); @@ -229,7 +229,9 @@ public class Buttons : Scenario X = 4, Y = Pos.Bottom (label) + 1, SelectedItem = 2, - RadioLabels = new [] { "Start", "End", "Center", "Fill" } + RadioLabels = new [] { "_Start", "_End", "_Center", "_Fill" }, + Title = "_9 RadioGroup", + BorderStyle = LineStyle.Dotted }; main.Add (radioGroup); diff --git a/UICatalog/Scenarios/Generic.cs b/UICatalog/Scenarios/Generic.cs index 37891e7a6..625a9a454 100644 --- a/UICatalog/Scenarios/Generic.cs +++ b/UICatalog/Scenarios/Generic.cs @@ -17,8 +17,8 @@ public sealed class MyScenario : Scenario Title = GetQuitKeyAndName (), }; - var button = new Button { X = Pos.Center (), Y = Pos.Center (), Text = "_Press me!" }; - button.Accept += (s, e) => MessageBox.ErrorQuery ("Error", "You pressed the button!", "_Ok"); + var button = new CheckBox() { X = Pos.Center (), Y = Pos.Center (), Text = "_Press me!" }; + //button.Accept += (s, e) => MessageBox.ErrorQuery ("Error", "You pressed the button!", "_Ok"); appWindow.Add (button); // Run - Start the application. diff --git a/UnitTests/Views/CheckBoxTests.cs b/UnitTests/Views/CheckBoxTests.cs index 5d6e6a06e..b813d763b 100644 --- a/UnitTests/Views/CheckBoxTests.cs +++ b/UnitTests/Views/CheckBoxTests.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using Xunit.Abstractions; +// ReSharper disable AccessToModifiedClosure namespace Terminal.Gui.ViewsTests; @@ -172,47 +173,99 @@ public class CheckBoxTests (ITestOutputHelper output) [Fact] public void KeyBindings_Command () { - var toggled = false; + Application.Navigation = new ApplicationNavigation (); + Application.Top = new Toplevel (); + View otherView = new () { CanFocus = true }; var ckb = new CheckBox (); - ckb.CheckedStateChanging += (s, e) => toggled = true; + Application.Top.Add (ckb, otherView); + Application.Top.SetFocus (); + Assert.True (ckb.HasFocus); + + int checkedStateChangingCount = 0; + ckb.CheckedStateChanging += (s, e) => checkedStateChangingCount++; + + int selectCount = 0; + ckb.Select += (s, e) => selectCount++; + + int acceptCount = 0; + ckb.Accept += (s, e) => acceptCount++; Assert.Equal (CheckState.UnChecked, ckb.CheckedState); - Assert.False (toggled); + Assert.Equal (0, checkedStateChangingCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); Assert.Equal (Key.Empty, ckb.HotKey); + // Test while focused ckb.Text = "_Test"; Assert.Equal (Key.T, ckb.HotKey); - Assert.True (ckb.NewKeyDownEvent (Key.T)); + ckb.NewKeyDownEvent (Key.T); Assert.Equal (CheckState.Checked, ckb.CheckedState); - Assert.True (toggled); + Assert.Equal (1, checkedStateChangingCount); + Assert.Equal (1, selectCount); + Assert.Equal (0, acceptCount); ckb.Text = "T_est"; - toggled = false; Assert.Equal (Key.E, ckb.HotKey); - Assert.True (ckb.NewKeyDownEvent (Key.E.WithAlt)); - Assert.True (toggled); - Assert.Equal (CheckState.UnChecked, ckb.CheckedState); + ckb.NewKeyDownEvent (Key.E.WithAlt); + Assert.Equal (2, checkedStateChangingCount); + Assert.Equal (2, selectCount); + Assert.Equal (0, acceptCount); - toggled = false; - Assert.Equal (Key.E, ckb.HotKey); - Assert.True (ckb.NewKeyDownEvent (Key.E)); - Assert.True (toggled); - Assert.Equal (CheckState.Checked, ckb.CheckedState); + ckb.NewKeyDownEvent (Key.Space); + Assert.Equal (3, checkedStateChangingCount); + Assert.Equal (3, selectCount); + Assert.Equal (0, acceptCount); - toggled = false; - Assert.True (ckb.NewKeyDownEvent (Key.Space)); - Assert.True (toggled); - Assert.Equal (CheckState.UnChecked, ckb.CheckedState); - toggled = false; - Assert.True (ckb.NewKeyDownEvent (Key.Space)); - Assert.True (toggled); - Assert.Equal (CheckState.Checked, ckb.CheckedState); + ckb.NewKeyDownEvent (Key.Enter); + Assert.Equal (3, checkedStateChangingCount); + Assert.Equal (3, selectCount); + Assert.Equal (1, acceptCount); - toggled = false; - Assert.False (ckb.NewKeyDownEvent (Key.Enter)); - Assert.False (toggled); - Assert.Equal (CheckState.Checked, ckb.CheckedState); + //ckb.Text = "_Test"; + //Assert.Equal (Key.T, ckb.HotKey); + //Assert.True (ckb.NewKeyDownEvent (Key.T)); + //Assert.Equal (CheckState.Checked, ckb.CheckedState); + //Assert.True (checkedStateChangingCount); + //Assert.True (ckb.HasFocus); + + //ckb.Text = "T_est"; + //checkedStateChangingCount = false; + //Assert.Equal (Key.E, ckb.HotKey); + //Assert.True (ckb.NewKeyDownEvent (Key.E.WithAlt)); + //Assert.True (checkedStateChangingCount); + //Assert.Equal (CheckState.UnChecked, ckb.CheckedState); + + //checkedStateChangingCount = false; + //Assert.Equal (Key.E, ckb.HotKey); + //Assert.True (ckb.NewKeyDownEvent (Key.E)); + //Assert.True (checkedStateChangingCount); + //Assert.Equal (CheckState.Checked, ckb.CheckedState); + + //checkedStateChangingCount = false; + //Assert.False (ckb.NewKeyDownEvent (Key.Space)); + //Assert.True (checkedStateChangingCount); + //Assert.Equal (CheckState.UnChecked, ckb.CheckedState); + + + //ckb.SetFocus (); + //Assert.False (ckb.NewKeyDownEvent (Key.Space)); + //Assert.True (checkedStateChangingCount); + //Assert.Equal (CheckState.UnChecked, ckb.CheckedState); + + //checkedStateChangingCount = false; + //Assert.True (ckb.NewKeyDownEvent (Key.Space)); + //Assert.False (checkedStateChangingCount); + //Assert.Equal (CheckState.Checked, ckb.CheckedState); + + //checkedStateChangingCount = false; + //Assert.False (ckb.NewKeyDownEvent (Key.Enter)); + //Assert.False (checkedStateChangingCount); + //Assert.Equal (CheckState.Checked, ckb.CheckedState); + + Application.Top.Dispose (); + Application.ResetState (false); } [Fact] diff --git a/UnitTests/Views/RadioGroupTests.cs b/UnitTests/Views/RadioGroupTests.cs index 87515b61b..430e80ce1 100644 --- a/UnitTests/Views/RadioGroupTests.cs +++ b/UnitTests/Views/RadioGroupTests.cs @@ -76,7 +76,7 @@ public class RadioGroupTests (ITestOutputHelper output) } [Fact] - public void KeyBindings_Command () + public void Commands_HasFocus () { Application.Navigation = new (); var rg = new RadioGroup { RadioLabels = new [] { "Test", "New Test" } }; @@ -85,29 +85,61 @@ public class RadioGroupTests (ITestOutputHelper output) rg.SetFocus (); Assert.Equal (Orientation.Vertical, rg.Orientation); + int selectedItemChangedCount = 0; + rg.SelectedItemChanged += (s, e) => selectedItemChangedCount++; + + int selectCount = 0; + rg.Select += (s, e) => selectCount++; + + int acceptCount = 0; + rg.Accept += (s, e) => acceptCount++; + // By default the first item is selected Assert.Equal (0, rg.SelectedItem); + Assert.Equal (0, selectedItemChangedCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); + Assert.Equal (Key.Empty, rg.HotKey); + // With HasFocus // Test up/down without Select Assert.False (Application.OnKeyDown (Key.CursorUp)); // Should not change (should focus prev view if there was one, which there isn't) Assert.Equal (0, rg.SelectedItem); Assert.Equal (0, rg.Cursor); + Assert.Equal (0, selectedItemChangedCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); + Assert.True (Application.OnKeyDown (Key.CursorDown)); Assert.Equal (0, rg.SelectedItem); // Cursor changed, but selection didnt Assert.Equal (1, rg.Cursor); - Assert.False (Application.OnKeyDown (Key.CursorDown)); // Should not change (should focus next view if there was one, which there isn't) + Assert.Equal (0, selectedItemChangedCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); + + Assert.False (Application.OnKeyDown (Key.CursorDown)); // Should not change selection (should focus next view if there was one, which there isn't) Assert.Equal (0, rg.SelectedItem); Assert.Equal (1, rg.Cursor); + Assert.Equal (0, selectedItemChangedCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); - // Now test Select (Space) when Cursor != SelectedItem + // Test Select (Space) when Cursor != SelectedItem Assert.True (Application.OnKeyDown (Key.Space)); Assert.Equal (1, rg.SelectedItem); Assert.Equal (1, rg.Cursor); + Assert.Equal (1, selectedItemChangedCount); + Assert.Equal (1, selectCount); + Assert.Equal (0, acceptCount); // Now test Select (Space) when Cursor == SelectedItem - Should cycle Assert.True (Application.OnKeyDown (Key.Space)); Assert.Equal (0, rg.SelectedItem); Assert.Equal (0, rg.Cursor); + Assert.Equal (2, selectedItemChangedCount); + Assert.Equal (2, selectCount); + Assert.Equal (0, acceptCount); + Assert.True (Application.OnKeyDown (Key.Space)); Assert.Equal (1, rg.SelectedItem); Assert.Equal (1, rg.Cursor); @@ -131,12 +163,203 @@ public class RadioGroupTests (ITestOutputHelper output) Assert.True (Application.OnKeyDown (Key.Space)); Assert.Equal (1, rg.SelectedItem); Assert.Equal (1, rg.Cursor); + Assert.Equal (7, selectedItemChangedCount); + Assert.Equal (7, selectCount); + Assert.Equal (0, acceptCount); + + // Test HotKey + // Selected == Cursor (1) - Advance state and raise Select event - DO NOT raise Accept + + rg.HotKey = Key.L; + Assert.Equal (Key.L, rg.HotKey); + Assert.True (Application.OnKeyDown (Key.L)); + Assert.Equal (0, rg.SelectedItem); + Assert.Equal (0, rg.Cursor); + Assert.Equal (8, selectedItemChangedCount); + Assert.Equal (8, selectCount); + Assert.Equal (0, acceptCount); + + // Make Selected != Cursor + Assert.True (Application.OnKeyDown (Key.CursorDown)); + Assert.Equal (0, rg.SelectedItem); + Assert.Equal (1, rg.Cursor); + + // Selected != Cursor - Select Cursor and raise Select event - DO NOT raise Accept + Assert.True (Application.OnKeyDown (Key.L)); + Assert.Equal (1, rg.SelectedItem); + Assert.Equal (1, rg.Cursor); + Assert.Equal (9, selectedItemChangedCount); + Assert.Equal (9, selectCount); + Assert.Equal (0, acceptCount); Application.ResetState (ignoreDisposed: true); } [Fact] - public void HotKeys_Select_RadioLabels () + public void HotKey_HasFocus_False () + { + Application.Navigation = new (); + var rg = new RadioGroup { RadioLabels = new [] { "Test", "New Test" } }; + Application.Top = new Toplevel (); + + // With !HasFocus + View otherView = new () { Id = "otherView", CanFocus = true }; + + Label label = new () + { + Id = "label", + Title = "_R", + }; + + Application.Top.Add (label, rg, otherView); + otherView.SetFocus (); + + int selectedItemChangedCount = 0; + rg.SelectedItemChanged += (s, e) => selectedItemChangedCount++; + + int selectCount = 0; + rg.Select += (s, e) => selectCount++; + + int acceptCount = 0; + rg.Accept += (s, e) => acceptCount++; + + // By default the first item is selected + Assert.Equal (0, rg.SelectedItem); + Assert.Equal (Orientation.Vertical, rg.Orientation); + Assert.Equal (0, selectedItemChangedCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); + Assert.Equal (Key.Empty, rg.HotKey); + + Assert.False (rg.HasFocus); + + // Test HotKey + // Selected (0) == Cursor (0) - SetFocus + rg.HotKey = Key.L; + Assert.Equal (Key.L, rg.HotKey); + Assert.True (Application.OnKeyDown (Key.L)); + Assert.True (rg.HasFocus); + Assert.Equal (0, rg.SelectedItem); + Assert.Equal (0, rg.Cursor); + Assert.Equal (0, selectedItemChangedCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); + + // Make Selected != Cursor + Assert.True (Application.OnKeyDown (Key.CursorDown)); + Assert.Equal (0, rg.SelectedItem); + Assert.Equal (1, rg.Cursor); + + otherView.SetFocus (); + + // Selected != Cursor - SetFocus + Assert.True (Application.OnKeyDown (Key.L)); + Assert.True (rg.HasFocus); + Assert.Equal (0, rg.SelectedItem); + Assert.Equal (1, rg.Cursor); + Assert.Equal (0, selectedItemChangedCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); + + Assert.True (Application.OnKeyDown (Key.L)); + Assert.True (rg.HasFocus); + Assert.Equal (1, rg.SelectedItem); + Assert.Equal (1, rg.Cursor); + Assert.Equal (1, selectedItemChangedCount); + Assert.Equal (1, selectCount); + Assert.Equal (0, acceptCount); + + Application.ResetState (ignoreDisposed: true); + } + + + [Fact] + public void HotKeys_HasFocus_False_Does_Not_SetFocus_Selects () + { + Application.Navigation = new (); + var rg = new RadioGroup { RadioLabels = new [] { "Item _A", "Item _B" } }; + Application.Top = new Toplevel (); + + // With !HasFocus + View otherView = new () { Id = "otherView", CanFocus = true }; + + Label label = new () + { + Id = "label", + Title = "_R", + }; + + Application.Top.Add (label, rg, otherView); + otherView.SetFocus (); + + int selectedItemChangedCount = 0; + rg.SelectedItemChanged += (s, e) => selectedItemChangedCount++; + + int selectCount = 0; + rg.Select += (s, e) => selectCount++; + + int acceptCount = 0; + rg.Accept += (s, e) => acceptCount++; + + // By default the first item is selected + Assert.Equal (0, rg.SelectedItem); + Assert.Equal (Orientation.Vertical, rg.Orientation); + Assert.Equal (0, selectedItemChangedCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); + Assert.Equal (Key.Empty, rg.HotKey); + + Assert.False (rg.HasFocus); + + // Test RadioTitem.HotKey - Should never SetFocus + // Selected (0) == Cursor (0) + Assert.True (Application.OnKeyDown (Key.A)); + Assert.False (rg.HasFocus); + Assert.Equal (0, rg.SelectedItem); + Assert.Equal (0, rg.Cursor); + Assert.Equal (0, selectedItemChangedCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); + + rg.SetFocus (); + // Make Selected != Cursor + Assert.True (Application.OnKeyDown (Key.CursorDown)); + Assert.Equal (0, rg.SelectedItem); + Assert.Equal (1, rg.Cursor); + + otherView.SetFocus (); + + // Selected != Cursor + Assert.True (Application.OnKeyDown (Key.A)); + Assert.False (rg.HasFocus); + Assert.Equal (0, rg.SelectedItem); + Assert.Equal (1, rg.Cursor); + Assert.Equal (0, selectedItemChangedCount); + Assert.Equal (0, selectCount); + Assert.Equal (0, acceptCount); + + // Selected != Cursor - Should not set focus + Assert.True (Application.OnKeyDown (Key.B)); + Assert.False (rg.HasFocus); + Assert.Equal (1, rg.SelectedItem); + Assert.Equal (1, rg.Cursor); + Assert.Equal (1, selectedItemChangedCount); + Assert.Equal (1, selectCount); + Assert.Equal (0, acceptCount); + + Assert.True (Application.OnKeyDown (Key.B)); + Assert.False (rg.HasFocus); + Assert.Equal (1, rg.SelectedItem); + Assert.Equal (1, rg.Cursor); + Assert.Equal (1, selectedItemChangedCount); + Assert.Equal (1, selectCount); + Assert.Equal (0, acceptCount); + + + Application.ResetState (ignoreDisposed: true); + } + [Fact] + public void HotKeys_HasFocus_True_Selects () { var rg = new RadioGroup { RadioLabels = new [] { "_Left", "_Right", "Cen_tered", "_Justified" } }; Application.Top = new Toplevel (); @@ -251,7 +474,7 @@ public class RadioGroupTests (ITestOutputHelper output) } [Fact] - public void Accept_Command_Fires_Accept () + public void Accept_Command_Does_Not_Fire_Accept () { var group = new RadioGroup { RadioLabels = new [] { "_Left", "_Right", "Cen_tered", "_Justified" } }; var accepted = false; @@ -259,7 +482,7 @@ public class RadioGroupTests (ITestOutputHelper output) group.Accept += OnAccept; group.InvokeCommand (Command.Accept); - Assert.True (accepted); + Assert.False (accepted); return; diff --git a/docfx/docs/navigation.md b/docfx/docs/navigation.md index 56ab96de4..9467b91a8 100644 --- a/docfx/docs/navigation.md +++ b/docfx/docs/navigation.md @@ -414,6 +414,19 @@ Same for mouse interaction: This gets really interesting when there's a View like a `Shortcut` that is a composite of several subviews. +### New Model + +| | | | | **Keyboard** | | | | **Mouse** | | | | | +|----------------|-------------------------|------------|---------------|--------------|--------------------------------------------------------------------------------|--------------------------------------------------|---------------------------------------|-------------------------------|------------------------------|-------------------------------|----------------|---------------| +| | **Number
of States** | **Static** | **IsDefault** | **Hotkeys** | **Select
Command
`Space`** | **Accept
Command
`Enter`** | **Hotkey
Command** | **CanFocus
Click** | **CanFocus
DblCLick** | **!CanFocus
Click** | **RightClick** | **GrabMouse** | +| **View** | 1 | Yes | No | 1 | | OnAccept | Focus | Focus | | | | No | +| **Label** | 1 | Yes | No | 1 | | OnAccept | FocusNext | Focus | | FocusNext | | No | +| **Button** | 1 | No | Yes | 1 | Focus
OnAccept | Focus
OnAccept | Focus
OnAccept | Focus
OnAccept | | OnAccept | | No | +| **Checkbox** | 3 | No | No | 1 | AdvanceCheckState
OnSelect | AdvanceCheckState
OnAccept | AdvanceCheckState
OnAccept | AdvanceCheckState
OnAccept | | AdvanceCheckState
OnAccept | | No | +| **RadioGroup** | > 1 | No | No | 2+ | If cursor not selected,
select. Else, Advance
selected item
OnSelect | Set SelectedItem
OnAccept | Focus
Set SelectedItem
OnAccept | SetFocus
Set _cursor | | SetFocus
Set _cursor | | No | +| **Slider** | > 1 | No | No | 1 | SetFocusedOption
OnOptionsChanged | SetFocusedOption
OnOptionsChanged
OnAccept | Focus | SetFocus
SetFocusedOption | | SetFocus
SetFocusedOption | | Yes | +| **ListView** | > 1 | No | No | 1 | MarkUnMarkRow | OpenSelectedItem
OnAccept | OnAccept | SetMark
OnSelectedChanged | OpenSelectedItem
OnAccept | | | No | + ## `View` - base class ### `!HasFocus` @@ -627,7 +640,7 @@ In v2_develop it's all kinds of confused. Here's what it SHOULD do: * `Enter` - `Command.Accept` -> Advances state to selected RadioItem and Raises `Accept` * `Space` - `Command.Select` -> Advances state -* `Title.Hotkey` - `Command.Hotkey` -> does nothing +* `Title.Hotkey` - `Command.Hotkey` -> Advance state * `RadioItem.Hotkey` - `Command.Select` -> Advance State to RadioItem with hotkey. * `Click` - advances state to clicked RadioItem. * `Double Click` - Advances state to clicked RadioItem and then raises `Accept` (this is what Office does; it's pretty nice. Windows does nothing).