Browse Source

Fixed RadioGroup 2

Tig 10 months ago
parent
commit
f3b93a58dd

+ 1 - 0
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: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/navigation.html"/>
     ///         See the View Navigation Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/navigation.html"/>
     ///     </para>
     ///     </para>
     /// </remarks>
     /// </remarks>
+    /// <returns><see langword="true"/> if the focus changed; <see langword="true"/> false otherwise.</returns>
     public bool SetFocus ()
     public bool SetFocus ()
     {
     {
         (bool focusSet, bool _) = SetHasFocusTrue (Application.Navigation?.GetFocused ());
         (bool focusSet, bool _) = SetHasFocusTrue (Application.Navigation?.GetFocused ());

+ 10 - 9
Terminal.Gui/Views/CheckBox.cs

@@ -1,4 +1,6 @@
 #nullable enable
 #nullable enable
+using System.Reflection.Metadata;
+
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
 /// <summary>Shows a check box that can be cycled between three states.</summary>
 /// <summary>Shows a check box that can be cycled between three states.</summary>
@@ -20,8 +22,13 @@ public class CheckBox : View
 
 
         CanFocus = true;
         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);
         AddCommand (Command.HotKey, AdvanceCheckState);
 
 
         TitleChanged += Checkbox_TitleChanged;
         TitleChanged += Checkbox_TitleChanged;
@@ -161,15 +168,9 @@ public class CheckBox : View
             return e.Cancel;
             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;
         CheckedState = e.NewValue;
 
 
-        return true;
+        return RaiseSelectEvent ();
     }
     }
 
 
     /// <summary>Raised when the <see cref="CheckBox"/> state is changing.</summary>
     /// <summary>Raised when the <see cref="CheckBox"/> state is changing.</summary>

+ 3 - 3
Terminal.Gui/Views/Label.cs

@@ -17,7 +17,7 @@ public class Label : View
         Width = Dim.Auto (DimAutoStyle.Text);
         Width = Dim.Auto (DimAutoStyle.Text);
 
 
         // Things this view knows how to do
         // Things this view knows how to do
-        AddCommand (Command.HotKey, FocusNext);
+        AddCommand (Command.HotKey, context =>  InvokeHotKeyOnNext(context));
 
 
         TitleChanged += Label_TitleChanged;
         TitleChanged += Label_TitleChanged;
         MouseClick += Label_MouseClick;
         MouseClick += Label_MouseClick;
@@ -51,12 +51,12 @@ public class Label : View
         set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
         set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
     }
     }
 
 
-    private bool? FocusNext ()
+    private bool? InvokeHotKeyOnNext (CommandContext context)
     {
     {
         int me = SuperView?.Subviews.IndexOf (this) ?? -1;
         int me = SuperView?.Subviews.IndexOf (this) ?? -1;
         if (me != -1 && me < SuperView?.Subviews.Count - 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;
         return true;

+ 67 - 23
Terminal.Gui/Views/RadioGroup.cs

@@ -1,4 +1,5 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 
 /// <summary>Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time.</summary>
 /// <summary>Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time.</summary>
 public class RadioGroup : View, IDesignable, IOrientation
 public class RadioGroup : View, IDesignable, IOrientation
@@ -82,16 +83,17 @@ public class RadioGroup : View, IDesignable, IOrientation
                             }
                             }
                         }
                         }
 
 
-                        SelectedItem = Cursor;
-
-                        return true;
+                        return SetSelectedItem (Cursor);
                     });
                     });
 
 
         AddCommand (
         AddCommand (
                     Command.Accept,
                     Command.Accept,
                     () =>
                     () =>
                     {
                     {
-                        SelectedItem = Cursor;
+                        if (!SetSelectedItem (Cursor))
+                        {
+                            return false;
+                        }
 
 
                         return RaiseAcceptEvent () is false;
                         return RaiseAcceptEvent () is false;
                     }
                     }
@@ -101,14 +103,41 @@ public class RadioGroup : View, IDesignable, IOrientation
                     Command.HotKey,
                     Command.HotKey,
                     ctx =>
                     ctx =>
                     {
                     {
-                        if (ctx.KeyBinding?.Context is { } && (int)ctx.KeyBinding?.Context! < _radioLabels.Count)
+                        var item = ctx.KeyBinding?.Context as int?;
+
+                        if (HasFocus)
                         {
                         {
-                            SelectedItem = (int)ctx.KeyBinding?.Context!;
+                            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 RaiseSelectEvent () is true or null;
+                        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;
                         }
                         }
 
 
-                        return !SetFocus ();
+                        if (SelectedItem == -1 && SetSelectedItem (0))
+                        {
+                            return true;
+                        }
+
+                        SetFocus ();
+
+                        return true;
                     });
                     });
 
 
         _orientationHelper = new (this);
         _orientationHelper = new (this);
@@ -247,12 +276,36 @@ public class RadioGroup : View, IDesignable, IOrientation
     public int SelectedItem
     public int SelectedItem
     {
     {
         get => _selected;
         get => _selected;
-        set
+        set => SetSelectedItem (value);
+    }
+
+    /// <summary>
+    ///     INTERNAL Sets the selected item.
+    /// </summary>
+    /// <param name="value"></param>
+    /// <returns>true if the selection changed.</returns>
+    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;
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
@@ -370,16 +423,7 @@ public class RadioGroup : View, IDesignable, IOrientation
     /// <summary>Called whenever the current selected item changes. Invokes the <see cref="SelectedItemChanged"/> event.</summary>
     /// <summary>Called whenever the current selected item changes. Invokes the <see cref="SelectedItemChanged"/> event.</summary>
     /// <param name="selectedItem"></param>
     /// <param name="selectedItem"></param>
     /// <param name="previousSelectedItem"></param>
     /// <param name="previousSelectedItem"></param>
-    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) { }
 
 
     /// <summary>
     /// <summary>
     ///     Gets or sets the <see cref="RadioLabels"/> index for the cursor. The cursor may or may not be the selected
     ///     Gets or sets the <see cref="RadioLabels"/> index for the cursor. The cursor may or may not be the selected

+ 4 - 2
UICatalog/Scenarios/Buttons.cs

@@ -220,7 +220,7 @@ public class Buttons : Scenario
         var label = new Label
         var label = new Label
         {
         {
             X = 2, Y = Pos.Bottom (computedFrame) + 1, 
             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);
         main.Add (label);
 
 
@@ -229,7 +229,9 @@ public class Buttons : Scenario
             X = 4,
             X = 4,
             Y = Pos.Bottom (label) + 1,
             Y = Pos.Bottom (label) + 1,
             SelectedItem = 2,
             SelectedItem = 2,
-            RadioLabels = new [] { "Start", "End", "Center", "Fill" }
+            RadioLabels = new [] { "_Start", "_End", "_Center", "_Fill" },
+            Title = "_9 RadioGroup",
+            BorderStyle = LineStyle.Dotted
         };
         };
         main.Add (radioGroup);
         main.Add (radioGroup);
 
 

+ 2 - 2
UICatalog/Scenarios/Generic.cs

@@ -17,8 +17,8 @@ public sealed class MyScenario : Scenario
             Title = GetQuitKeyAndName (),
             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);
         appWindow.Add (button);
 
 
         // Run - Start the application.
         // Run - Start the application.

+ 83 - 30
UnitTests/Views/CheckBoxTests.cs

@@ -1,5 +1,6 @@
 using System.ComponentModel;
 using System.ComponentModel;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
+// ReSharper disable AccessToModifiedClosure
 
 
 namespace Terminal.Gui.ViewsTests;
 namespace Terminal.Gui.ViewsTests;
 
 
@@ -172,47 +173,99 @@ public class CheckBoxTests (ITestOutputHelper output)
     [Fact]
     [Fact]
     public void KeyBindings_Command ()
     public void KeyBindings_Command ()
     {
     {
-        var toggled = false;
+        Application.Navigation = new ApplicationNavigation ();
+        Application.Top = new Toplevel ();
+        View otherView = new () { CanFocus = true };
         var ckb = new CheckBox ();
         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.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);
         Assert.Equal (Key.Empty, ckb.HotKey);
 
 
+        // Test while focused
         ckb.Text = "_Test";
         ckb.Text = "_Test";
         Assert.Equal (Key.T, ckb.HotKey);
         Assert.Equal (Key.T, ckb.HotKey);
-        Assert.True (ckb.NewKeyDownEvent (Key.T));
+        ckb.NewKeyDownEvent (Key.T);
         Assert.Equal (CheckState.Checked, ckb.CheckedState);
         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";
         ckb.Text = "T_est";
-        toggled = false;
         Assert.Equal (Key.E, ckb.HotKey);
         Assert.Equal (Key.E, ckb.HotKey);
-        Assert.True (ckb.NewKeyDownEvent (Key.E.WithAlt));
-        Assert.True (toggled);
-        Assert.Equal (CheckState.UnChecked, ckb.CheckedState);
-
-        toggled = false;
-        Assert.Equal (Key.E, ckb.HotKey);
-        Assert.True (ckb.NewKeyDownEvent (Key.E));
-        Assert.True (toggled);
-        Assert.Equal (CheckState.Checked, ckb.CheckedState);
-
-        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);
-
-        toggled = false;
-        Assert.False (ckb.NewKeyDownEvent (Key.Enter));
-        Assert.False (toggled);
-        Assert.Equal (CheckState.Checked, ckb.CheckedState);
+        ckb.NewKeyDownEvent (Key.E.WithAlt);
+        Assert.Equal (2, checkedStateChangingCount);
+        Assert.Equal (2, selectCount);
+        Assert.Equal (0, acceptCount);
+
+        ckb.NewKeyDownEvent (Key.Space);
+        Assert.Equal (3, checkedStateChangingCount);
+        Assert.Equal (3, selectCount);
+        Assert.Equal (0, acceptCount);
+
+
+        ckb.NewKeyDownEvent (Key.Enter);
+        Assert.Equal (3, checkedStateChangingCount);
+        Assert.Equal (3, selectCount);
+        Assert.Equal (1, acceptCount);
+
+        //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]
     [Fact]

+ 229 - 6
UnitTests/Views/RadioGroupTests.cs

@@ -76,7 +76,7 @@ public class RadioGroupTests (ITestOutputHelper output)
     }
     }
 
 
     [Fact]
     [Fact]
-    public void KeyBindings_Command ()
+    public void Commands_HasFocus ()
     {
     {
         Application.Navigation = new ();
         Application.Navigation = new ();
         var rg = new RadioGroup { RadioLabels = new [] { "Test", "New Test" } };
         var rg = new RadioGroup { RadioLabels = new [] { "Test", "New Test" } };
@@ -85,29 +85,61 @@ public class RadioGroupTests (ITestOutputHelper output)
         rg.SetFocus ();
         rg.SetFocus ();
         Assert.Equal (Orientation.Vertical, rg.Orientation);
         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
         // By default the first item is selected
         Assert.Equal (0, rg.SelectedItem);
         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
         // 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.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.SelectedItem);
         Assert.Equal (0, rg.Cursor);
         Assert.Equal (0, rg.Cursor);
+        Assert.Equal (0, selectedItemChangedCount);
+        Assert.Equal (0, selectCount);
+        Assert.Equal (0, acceptCount);
+
         Assert.True (Application.OnKeyDown (Key.CursorDown));
         Assert.True (Application.OnKeyDown (Key.CursorDown));
         Assert.Equal (0, rg.SelectedItem); // Cursor changed, but selection didnt
         Assert.Equal (0, rg.SelectedItem); // Cursor changed, but selection didnt
         Assert.Equal (1, rg.Cursor);
         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 (0, rg.SelectedItem);
         Assert.Equal (1, rg.Cursor);
         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.True (Application.OnKeyDown (Key.Space));
         Assert.Equal (1, rg.SelectedItem);
         Assert.Equal (1, rg.SelectedItem);
         Assert.Equal (1, rg.Cursor);
         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
         // Now test Select (Space) when Cursor == SelectedItem - Should cycle
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.Equal (0, rg.SelectedItem);
         Assert.Equal (0, rg.SelectedItem);
         Assert.Equal (0, rg.Cursor);
         Assert.Equal (0, rg.Cursor);
+        Assert.Equal (2, selectedItemChangedCount);
+        Assert.Equal (2, selectCount);
+        Assert.Equal (0, acceptCount);
+
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.Equal (1, rg.SelectedItem);
         Assert.Equal (1, rg.SelectedItem);
         Assert.Equal (1, rg.Cursor);
         Assert.Equal (1, rg.Cursor);
@@ -131,12 +163,203 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.Equal (1, rg.SelectedItem);
         Assert.Equal (1, rg.SelectedItem);
         Assert.Equal (1, rg.Cursor);
         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);
         Application.ResetState (ignoreDisposed: true);
     }
     }
 
 
     [Fact]
     [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" } };
         var rg = new RadioGroup { RadioLabels = new [] { "_Left", "_Right", "Cen_tered", "_Justified" } };
         Application.Top = new Toplevel ();
         Application.Top = new Toplevel ();
@@ -251,7 +474,7 @@ public class RadioGroupTests (ITestOutputHelper output)
     }
     }
 
 
     [Fact]
     [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 group = new RadioGroup { RadioLabels = new [] { "_Left", "_Right", "Cen_tered", "_Justified" } };
         var accepted = false;
         var accepted = false;
@@ -259,7 +482,7 @@ public class RadioGroupTests (ITestOutputHelper output)
         group.Accept += OnAccept;
         group.Accept += OnAccept;
         group.InvokeCommand (Command.Accept);
         group.InvokeCommand (Command.Accept);
 
 
-        Assert.True (accepted);
+        Assert.False (accepted);
 
 
         return;
         return;
 
 

+ 14 - 1
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. 
 This gets really interesting when there's a View like a `Shortcut` that is a composite of several subviews. 
 
 
+### New Model
+
+|                |                         |            |               | **Keyboard** |                                                                                |                                                  |                                       | **Mouse**                     |                              |                               |                |               |
+|----------------|-------------------------|------------|---------------|--------------|--------------------------------------------------------------------------------|--------------------------------------------------|---------------------------------------|-------------------------------|------------------------------|-------------------------------|----------------|---------------|
+|                | **Number<br>of States** | **Static** | **IsDefault** | **Hotkeys**  | **Select<br>Command<br>`Space`**                                               | **Accept<br>Command<br>`Enter`**                 | **Hotkey<br>Command**                 | **CanFocus<br>Click**         | **CanFocus<br>DblCLick**     | **!CanFocus<br>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<br>OnAccept                                                              | Focus<br>OnAccept                                | Focus<br>OnAccept                     | Focus<br>OnAccept             |                              | OnAccept                      |                | No            |
+| **Checkbox**   | 3                       | No         | No            | 1            | AdvanceCheckState<br>OnSelect                                                  | AdvanceCheckState<br>OnAccept                    | AdvanceCheckState<br>OnAccept         | AdvanceCheckState<br>OnAccept |                              | AdvanceCheckState<br>OnAccept |                | No            |
+| **RadioGroup** | > 1                     | No         | No            | 2+           | If cursor not selected,<br>select. Else, Advance <br>selected item<br>OnSelect | Set SelectedItem<br>OnAccept                     | Focus<br>Set SelectedItem<br>OnAccept | SetFocus<br>Set _cursor       |                              | SetFocus<br>Set _cursor       |                | No            |
+| **Slider**     | > 1                     | No         | No            | 1            | SetFocusedOption<br>OnOptionsChanged                                           | SetFocusedOption<br>OnOptionsChanged<br>OnAccept | Focus                                 | SetFocus<br>SetFocusedOption  |                              | SetFocus<br>SetFocusedOption  |                | Yes           |
+| **ListView**   | > 1                     | No         | No            | 1            | MarkUnMarkRow                                                                  | OpenSelectedItem<br>OnAccept                     | OnAccept                              | SetMark<br>OnSelectedChanged  | OpenSelectedItem<br>OnAccept |                               |                | No            |
+
 ## `View` - base class
 ## `View` - base class
 
 
 ### `!HasFocus`
 ### `!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` 
 * `Enter` - `Command.Accept` -> Advances state to selected RadioItem and Raises `Accept` 
 * `Space` - `Command.Select` -> Advances state
 * `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.
 * `RadioItem.Hotkey` - `Command.Select` -> Advance State to RadioItem with hotkey.
 * `Click` - advances state to clicked RadioItem.
 * `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).
 * `Double Click` - Advances state to clicked RadioItem and then raises `Accept` (this is what Office does; it's pretty nice. Windows does nothing).