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).