Преглед на файлове

More refactoring. Still WIP

Tig преди 10 месеца
родител
ревизия
6adeec6548

+ 3 - 3
Terminal.Gui/View/View.Mouse.cs

@@ -371,8 +371,8 @@ public partial class View // Mouse APIs
         // Post-conditions
 
         // Always invoke Select command on MouseClick
-        // By default, this will raise Select/OnSelect - Subclasses can override this via AddCommand (Command.Select ...).
-        args.Handled = InvokeCommand (Command.Select) == true;
+        // By default, this will raise Selected/OnSelected - Subclasses can override this via AddCommand (Command.Select ...).
+        args.Handled = InvokeCommand (Command.Select, null, new KeyBinding ([Command.Select], scope: KeyBindingScope.Focused, boundView: this, context: args.MouseEvent)) == true;
 
         return args.Handled;
     }
@@ -399,7 +399,7 @@ public partial class View // Mouse APIs
 
             if (SetPressedHighlight (HighlightStyle.None))
             {
-                // BUGBUG: If we retrun true here we never generate a moues click!
+                // BUGBUG: If we return true here we never generate a mouse click!
                 return true;
             }
 

+ 5 - 0
Terminal.Gui/Views/Label.cs

@@ -65,6 +65,11 @@ public class Label : View
 
     private bool? InvokeHotKeyOnNext (CommandContext context)
     {
+        if (CanFocus)
+        {
+            return SetFocus ();
+        }
+
         int me = SuperView?.Subviews.IndexOf (this) ?? -1;
 
         if (me != -1 && me < SuperView?.Subviews.Count - 1)

+ 101 - 95
Terminal.Gui/Views/RadioGroup.cs

@@ -16,7 +16,96 @@ public class RadioGroup : View, IDesignable, IOrientation
         Width = Dim.Auto (DimAutoStyle.Content);
         Height = Dim.Auto (DimAutoStyle.Content);
 
-        // Things this view knows how to do
+
+        // Select (Space key or mouse click) - The default implementation sets focus. RadioGroup does not.
+        AddCommand (
+                    Command.Select,
+                    () =>
+                    {
+                        bool cursorChanged = false;
+                        if (SelectedItem == Cursor)
+                        {
+                            cursorChanged = MoveDownRight ();
+                            if (!cursorChanged)
+                            {
+                                cursorChanged = MoveHome ();
+                            }
+                        }
+
+                        bool selectedItemChanged = false;
+                        if (SelectedItem != Cursor)
+                        {
+                            selectedItemChanged = ChangeSelectedItem (Cursor);
+                        }
+
+                        if (cursorChanged || selectedItemChanged)
+                        {
+                            if (RaiseSelected () == true)
+                            {
+                                return true;
+                            }
+                        }
+
+                        return cursorChanged || selectedItemChanged;
+                    });
+
+        // Accept (Enter key) - Raise Accept event - DO NOT advance state
+        AddCommand (Command.Accept, () => RaiseAccepted());
+
+        // Hotkey - ctx may indicate a radio item hotkey was pressed. Beahvior depends on HasFocus
+        //          If HasFocus and it's this.HotKey invoke Select command - DO NOT raise Accept
+        //          If it's a radio item HotKey select that item and raise Seelcted event - DO NOT raise Accept
+        //          If nothing is selected, select first and raise Selected event - DO NOT raise Accept
+        AddCommand (Command.HotKey,
+                    ctx =>
+                            {
+                                var item = ctx.KeyBinding?.Context as int?;
+
+                                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);
+                                    }
+                                }
+
+                                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
+                                    bool selectedItemChanged = ChangeSelectedItem (item.Value);
+
+                                    if (selectedItemChanged)
+                                    {
+                                        // Doesn't matter if it's handled
+                                        RaiseSelected ();
+                                        return true;
+                                    }
+
+
+                                    return false;
+                                }
+
+                                if (SelectedItem == -1 && ChangeSelectedItem (0))
+                                {
+                                    if (RaiseSelected () == true)
+                                    {
+                                        return true;
+                                    }
+                                    return false;
+                                }
+
+                                // Default Command.Hotkey sets focus
+                                SetFocus ();
+
+                                return true;
+                            });
+
         AddCommand (
                     Command.Up,
                     () =>
@@ -73,84 +162,6 @@ public class RadioGroup : View, IDesignable, IOrientation
                     }
                    );
 
-        // Select (Space key or mouse click) - The default implementation sets focus. RadioGroup does not.
-        AddCommand (
-                    Command.Select,
-                    () =>
-                    {
-                        bool cursorChanged = false;
-                        if (SelectedItem == Cursor)
-                        {
-                            cursorChanged = MoveDownRight ();
-                            if (!cursorChanged)
-                            {
-                                cursorChanged = MoveHome ();
-                            }
-                        }
-
-                        if (SelectedItem != Cursor)
-                        {
-                            if (ChangeSelectedItem (Cursor) && RaiseSelected () == true)
-                            {
-                                return true;
-                            }
-                        }
-
-                        return cursorChanged;
-                    });
-
-        // Accept (Enter key) - Raise Accept event - DO NOT advance state
-        AddCommand (Command.Accept, RaiseAccepted);
-
-        // Hotkey - ctx may indicate a radio item hotkey was pressed. Beahvior depends on HasFocus
-        //          If HasFocus and it's this.HotKey invoke Select command - DO NOT raise Accept
-        //          If it's a radio item HotKey select that item and raise Seelcted event - DO NOT raise Accept
-        //          If nothing is selected, select first and raise Selected event - DO NOT raise Accept
-        AddCommand (Command.HotKey,
-                    ctx =>
-                    {
-                        var item = ctx.KeyBinding?.Context as int?;
-
-                        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);
-                            }
-                        }
-
-                        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 (ChangeSelectedItem (item.Value) && RaiseSelected () == true)
-                            {
-                                return true;
-                            }
-
-                            return false;
-                        }
-
-                        if (SelectedItem == -1 && ChangeSelectedItem (0))
-                        {
-                            if (RaiseSelected () == true)
-                            {
-                                return true;
-                            }
-                            return false;
-                        }
-
-                        // Default Command.Hotkey sets focus
-                        SetFocus ();
-
-                        return true;
-                    });
-
         // ReSharper disable once UseObjectOrCollectionInitializer
         _orientationHelper = new (this);
         _orientationHelper.Orientation = Orientation.Vertical;
@@ -225,30 +236,25 @@ public class RadioGroup : View, IDesignable, IOrientation
 
                 if (c > -1)
                 {
-                    if (ChangeSelectedItem (c))
-                    {
-                        Cursor = c;
-                        e.Handled = true;
-                    }
+                    // Just like the user pressing the items' hotkey
+                    e.Handled = InvokeCommand (Command.HotKey, null, new KeyBinding ([Command.HotKey], KeyBindingScope.HotKey, boundView: this, context: c)) == true;
                 }
             }
+
+            return;
         }
 
         if (DoubleClickAccepts && e.MouseEvent.Flags.HasFlag (MouseFlags.Button1DoubleClicked))
         {
-            int savedSelectedItem = SelectedItem;
-
-            if (RaiseAccepted () == true)
-            {
-                e.Handled = false;
-                _selected = savedSelectedItem;
-            }
+            // NOTE: Drivers ALWAYS generate a Button1Clicked event before Button1DoubleClicked
+            // NOTE: So, we've already selected an item.
 
-            if (SuperView?.InvokeCommand (Command.Accept) is false or null)
-            {
-                e.Handled = true;
-            }
+            // Just like the user pressing `Enter`
+            InvokeCommand (Command.Accept);
         }
+
+        // HACK: Always eat so Select is not invoked by base
+        e.Handled = true;
     }
 
     private List<(int pos, int length)>? _horizontal;

+ 0 - 10
Terminal.Gui/Views/Shortcut.cs

@@ -386,16 +386,6 @@ public class Shortcut : View, IOrientation, IDesignable
         }
     }
 
-    private bool? OnSelect (CommandContext ctx)
-    {
-        if (CommandView.GetSupportedCommands ().Contains (Command.Select))
-        {
-            return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding);
-        }
-
-        return false;
-    }
-
     private void Shortcut_MouseClick (object sender, MouseEventEventArgs e)
     {
         // When the Shortcut is clicked, we want to invoke the Command and Set focus

+ 12 - 4
Terminal.Gui/Views/Slider.cs

@@ -1496,8 +1496,13 @@ public class Slider<T> : View, IOrientation
         OnOptionsChanged ();
     }
 
-    private void SetFocusedOption ()
+    private bool SetFocusedOption ()
     {
+        if (_options.Count == 0)
+        {
+            return false;
+        }
+        bool changed = false;
         switch (_config._type)
         {
             case SliderType.Single:
@@ -1530,6 +1535,7 @@ public class Slider<T> : View, IOrientation
 
                 // Raise slider changed event.
                 OnOptionsChanged ();
+                changed = true;
 
                 break;
             case SliderType.Multiple:
@@ -1550,6 +1556,7 @@ public class Slider<T> : View, IOrientation
                 }
 
                 OnOptionsChanged ();
+                changed = true;
 
                 break;
 
@@ -1683,11 +1690,14 @@ public class Slider<T> : View, IOrientation
 
                 // Raise Slider Option Changed Event.
                 OnOptionsChanged ();
+                changed = true;
 
                 break;
             default:
                 throw new ArgumentOutOfRangeException (_config._type.ToString ());
         }
+
+        return changed;
     }
 
     internal bool ExtendPlus ()
@@ -1772,9 +1782,7 @@ public class Slider<T> : View, IOrientation
 
     internal bool Select ()
     {
-        SetFocusedOption ();
-
-        return true;
+        return SetFocusedOption ();
     }
 
     internal new bool Accept ()

+ 17 - 16
Terminal.Gui/Views/TableView/TableView.cs

@@ -238,24 +238,18 @@ public class TableView : View
                     }
                    );
 
-        AddCommand (
-                    Command.Accept,
-                    () =>
-                    {
-                        // BUGBUG: This should return false if the event is not handled
-                        OnCellActivated (new CellActivatedEventArgs (Table, SelectedColumn, SelectedRow));
-
-                        return true;
-                    }
-                   );
+        AddCommand (Command.Accept, () => OnCellActivated (new CellActivatedEventArgs (Table, SelectedColumn, SelectedRow)));
 
         AddCommand (
                     Command.Select, // was Command.ToggleChecked
                     () =>
                     {
-                        ToggleCurrentCellSelection ();
+                        if (ToggleCurrentCellSelection () is true)
+                        {
+                            return RaiseSelected () is true;
+                        }
 
-                        return true;
+                        return false;
                     }
                    );
 
@@ -1250,7 +1244,12 @@ public class TableView : View
 
     /// <summary>Invokes the <see cref="CellActivated"/> event</summary>
     /// <param name="args"></param>
-    protected virtual void OnCellActivated (CellActivatedEventArgs args) { CellActivated?.Invoke (this, args); }
+    /// <returns><see langword="true"/> if the CellActivated event was raised.</returns>
+    protected virtual bool OnCellActivated (CellActivatedEventArgs args)
+    {
+        CellActivated?.Invoke (this, args);
+        return CellActivated is { };
+    }
 
     /// <summary>Invokes the <see cref="CellToggled"/> event</summary>
     /// <param name="args"></param>
@@ -2047,19 +2046,19 @@ public class TableView : View
                                  );
     }
 
-    private void ToggleCurrentCellSelection ()
+    private bool? ToggleCurrentCellSelection ()
     {
         var e = new CellToggledEventArgs (Table, selectedColumn, selectedRow);
         OnCellToggled (e);
 
         if (e.Cancel)
         {
-            return;
+            return false;
         }
 
         if (!MultiSelect)
         {
-            return;
+            return null;
         }
 
         TableSelection [] regions = GetMultiSelectedRegionsContaining (selectedColumn, selectedRow).ToArray ();
@@ -2104,6 +2103,8 @@ public class TableView : View
                                           );
             }
         }
+
+        return true;
     }
 
     /// <summary>

+ 10 - 3
Terminal.Gui/Views/TextView.cs

@@ -2010,6 +2010,10 @@ public class TextView : View
         LayoutComplete += TextView_LayoutComplete;
 
         // Things this view knows how to do
+
+        // Note - NewLine is only bound to Enter if Multiline is true
+        AddCommand (Command.NewLine, () => ProcessEnterKey ());
+
         AddCommand (
                     Command.PageDown,
                     () =>
@@ -2276,8 +2280,6 @@ public class TextView : View
                     }
                    );
 
-        AddCommand (Command.Accept, () => ProcessEnterKey ());
-
         AddCommand (
                     Command.End,
                     () =>
@@ -2404,7 +2406,9 @@ public class TextView : View
                     }
                    );
 
-        // Default keybindings for this view
+        KeyBindings.Remove (Key.Enter);
+        KeyBindings.Add (Key.Enter, Multiline ? Command.NewLine : Command.Accept);
+
         KeyBindings.Add (Key.PageDown, Command.PageDown);
         KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
 
@@ -2701,6 +2705,9 @@ public class TextView : View
                 Height = _savedHeight;
                 SetNeedsDisplay ();
             }
+
+            KeyBindings.Remove (Key.Enter);
+            KeyBindings.Add (Key.Enter, Multiline ? Command.NewLine : Command.Accept);
         }
     }
 

+ 36 - 35
UICatalog/Scenarios/FileDialogExamples.cs

@@ -2,6 +2,7 @@
 using System.IO;
 using System.IO.Abstractions;
 using System.Linq;
+using System.Text;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
@@ -33,25 +34,25 @@ public class FileDialogExamples : Scenario
         var x = 1;
         var win = new Window { Title = GetQuitKeyAndName () };
 
-        _cbMustExist = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Must Exist" };
+        _cbMustExist = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Must E_xist" };
         win.Add (_cbMustExist);
 
-        _cbUseColors = new CheckBox { CheckedState = FileDialogStyle.DefaultUseColors ? CheckState.Checked : CheckState.UnChecked, Y = y++, X = x, Text = "Use Colors" };
+        _cbUseColors = new CheckBox { CheckedState = FileDialogStyle.DefaultUseColors ? CheckState.Checked : CheckState.UnChecked, Y = y++, X = x, Text = "_Use Colors" };
         win.Add (_cbUseColors);
 
-        _cbCaseSensitive = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Case Sensitive Search" };
+        _cbCaseSensitive = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Case Sensitive Search" };
         win.Add (_cbCaseSensitive);
 
-        _cbAllowMultipleSelection = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Multiple" };
+        _cbAllowMultipleSelection = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Multiple" };
         win.Add (_cbAllowMultipleSelection);
 
-        _cbShowTreeBranchLines = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Tree Branch Lines" };
+        _cbShowTreeBranchLines = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Tree Branch _Lines" };
         win.Add (_cbShowTreeBranchLines);
 
-        _cbAlwaysTableShowHeaders = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Always Show Headers" };
+        _cbAlwaysTableShowHeaders = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Always Show _Headers" };
         win.Add (_cbAlwaysTableShowHeaders);
 
-        _cbDrivesOnlyInTree = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Only Show Drives" };
+        _cbDrivesOnlyInTree = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Only Show _Drives" };
         win.Add (_cbDrivesOnlyInTree);
 
         y = 0;
@@ -63,7 +64,7 @@ public class FileDialogExamples : Scenario
         win.Add (new Label { X = x++, Y = y++, Text = "Caption" });
 
         _rgCaption = new RadioGroup { X = x, Y = y };
-        _rgCaption.RadioLabels = new [] { "Ok", "Open", "Save" };
+        _rgCaption.RadioLabels = new [] { "_Ok", "O_pen", "_Save" };
         win.Add (_rgCaption);
 
         y = 0;
@@ -75,7 +76,7 @@ public class FileDialogExamples : Scenario
         win.Add (new Label { X = x++, Y = y++, Text = "OpenMode" });
 
         _rgOpenMode = new RadioGroup { X = x, Y = y };
-        _rgOpenMode.RadioLabels = new [] { "File", "Directory", "Mixed" };
+        _rgOpenMode.RadioLabels = new [] { "_File", "D_irectory", "_Mixed" };
         win.Add (_rgOpenMode);
 
         y = 0;
@@ -87,7 +88,7 @@ public class FileDialogExamples : Scenario
         win.Add (new Label { X = x++, Y = y++, Text = "Icons" });
 
         _rgIcons = new RadioGroup { X = x, Y = y };
-        _rgIcons.RadioLabels = new [] { "None", "Unicode", "Nerd*" };
+        _rgIcons.RadioLabels = new [] { "_None", "_Unicode", "Nerd_*" };
         win.Add (_rgIcons);
 
         win.Add (new Label { Y = Pos.AnchorEnd (2), Text = "* Requires installing Nerd fonts" });
@@ -102,7 +103,7 @@ public class FileDialogExamples : Scenario
         win.Add (new Label { X = x++, Y = y++, Text = "Allowed" });
 
         _rgAllowedTypes = new RadioGroup { X = x, Y = y };
-        _rgAllowedTypes.RadioLabels = new [] { "Any", "Csv (Recommended)", "Csv (Strict)" };
+        _rgAllowedTypes.RadioLabels = new [] { "An_y", "Cs_v (Recommended)", "Csv (S_trict)" };
         win.Add (_rgAllowedTypes);
 
         y = 5;
@@ -113,18 +114,32 @@ public class FileDialogExamples : Scenario
                 );
         win.Add (new Label { X = x++, Y = y++, Text = "Buttons" });
 
-        win.Add (new Label { X = x, Y = y++, Text = "Ok Text:" });
+        win.Add (new Label { X = x, Y = y++, Text = "O_k Text:" });
         _tbOkButton = new TextField { X = x, Y = y++, Width = 12 };
         win.Add (_tbOkButton);
-        win.Add (new Label { X = x, Y = y++, Text = "Cancel Text:" });
+        win.Add (new Label { X = x, Y = y++, Text = "_Cancel Text:" });
         _tbCancelButton = new TextField { X = x, Y = y++, Width = 12 };
         win.Add (_tbCancelButton);
-        _cbFlipButtonOrder = new CheckBox { X = x, Y = y++, Text = "Flip Order" };
+        _cbFlipButtonOrder = new CheckBox { X = x, Y = y++, Text = "Flip Ord_er" };
         win.Add (_cbFlipButtonOrder);
 
-        var btn = new Button { X = 1, Y = 9, Text = "Run Dialog" };
-
-        SetupHandler (btn);
+        var btn = new Button { X = 1, Y = 9, IsDefault = true, Text = "Run Dialog" };
+
+        win.Accepted += (s, e) =>
+                        {
+                            try
+                            {
+                                CreateDialog ();
+                            }
+                            catch (Exception ex)
+                            {
+                                MessageBox.ErrorQuery ("Error", ex.ToString (), "_Ok");
+                            }
+                            finally
+                            {
+                                e.Handled = true;
+                            }
+                        };
         win.Add (btn);
 
         Application.Run (win);
@@ -138,7 +153,7 @@ public class FileDialogExamples : Scenario
         {
             if (File.Exists (e.Dialog.Path))
             {
-                int result = MessageBox.Query ("Overwrite?", "File already exists", "Yes", "No");
+                int result = MessageBox.Query ("Overwrite?", "File already exists", "_Yes", "_No");
                 e.Cancel = result == 1;
             }
         }
@@ -149,13 +164,13 @@ public class FileDialogExamples : Scenario
         var fd = new FileDialog
         {
             OpenMode = Enum.Parse<OpenMode> (
-                                             _rgOpenMode.RadioLabels [_rgOpenMode.SelectedItem]
+                                             _rgOpenMode.RadioLabels.Select (l => TextFormatter.RemoveHotKeySpecifier(l, 0, _rgOpenMode.HotKeySpecifier)).ToArray() [_rgOpenMode.SelectedItem]
                                             ),
             MustExist = _cbMustExist.CheckedState == CheckState.Checked,
             AllowsMultipleSelection = _cbAllowMultipleSelection.CheckedState == CheckState.Checked
         };
 
-        fd.Style.OkButtonText = _rgCaption.RadioLabels [_rgCaption.SelectedItem];
+        fd.Style.OkButtonText = _rgCaption.RadioLabels.Select (l => TextFormatter.RemoveHotKeySpecifier(l, 0, _rgCaption.HotKeySpecifier)).ToArray() [_rgCaption.SelectedItem];
 
         // If Save style dialog then give them an overwrite prompt
         if (_rgCaption.SelectedItem == 2)
@@ -224,6 +239,7 @@ public class FileDialogExamples : Scenario
                               "You canceled navigation and did not pick anything",
                               "Ok"
                              );
+
         }
         else if (_cbAllowMultipleSelection.CheckedState == CheckState.Checked)
         {
@@ -243,21 +259,6 @@ public class FileDialogExamples : Scenario
         }
     }
 
-    private void SetupHandler (Button btn)
-    {
-        btn.Accepted += (s, e) =>
-                       {
-                           try
-                           {
-                               CreateDialog ();
-                           }
-                           catch (Exception ex)
-                           {
-                               MessageBox.ErrorQuery ("Error", ex.ToString (), "Ok");
-                           }
-                       };
-    }
-
     private class CaseSensitiveSearchMatcher : ISearchMatcher
     {
         private string _terms;

+ 84 - 8
UnitTests/Views/AllViewsTests.cs

@@ -24,6 +24,11 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
             return;
         }
 
+        if (view is IDesignable designable)
+        {
+            designable.EnableForDesign ();
+        }
+
         view.X = Pos.Center ();
         view.Y = Pos.Center ();
 
@@ -61,6 +66,22 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
         Assert.True (Test_All_Constructors_Of_Type (viewType));
     }
 
+
+    public bool Test_All_Constructors_Of_Type (Type type)
+    {
+        foreach (ConstructorInfo ctor in type.GetConstructors ())
+        {
+            View view = TestHelpers.CreateViewFromType (type, ctor);
+
+            if (view != null)
+            {
+                Assert.True (type.FullName == view.GetType ().FullName);
+            }
+        }
+
+        return true;
+    }
+
     //[Fact]
     //public void AllViews_HotKey_Works ()
     //{
@@ -73,18 +94,73 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
     //	}
     //}
 
-    public bool Test_All_Constructors_Of_Type (Type type)
+    [Theory]
+    [MemberData (nameof (AllViewTypes))]
+    public void AllViews_Command_Select_Raises_Selected (Type viewType)
     {
-        foreach (ConstructorInfo ctor in type.GetConstructors ())
+        var view = (View)CreateInstanceIfNotGeneric (viewType);
+
+        if (view == null)
         {
-            View view = TestHelpers.CreateViewFromType (type, ctor);
+            output.WriteLine ($"Ignoring {viewType} - It's a Generic");
 
-            if (view != null)
-            {
-                Assert.True (type.FullName == view.GetType ().FullName);
-            }
+            return;
         }
 
-        return true;
+        if (view is IDesignable designable)
+        {
+            designable.EnableForDesign ();
+        }
+
+        var selectedCount = 0;
+        view.Selected += (s, e) => selectedCount++;
+
+        var acceptedCount = 0;
+        view.Accepted += (s, e) =>
+                         {
+                             acceptedCount++;
+                         };
+
+
+        if (view.InvokeCommand(Command.Select) == true)
+        {
+            Assert.Equal(1, selectedCount);
+            Assert.Equal (0, acceptedCount);
+        }
+    }
+
+    [Theory]
+    [MemberData (nameof (AllViewTypes))]
+    public void AllViews_Command_Accept_Raises_Accepted (Type viewType)
+    {
+        var view = (View)CreateInstanceIfNotGeneric (viewType);
+
+        if (view == null)
+        {
+            output.WriteLine ($"Ignoring {viewType} - It's a Generic");
+
+            return;
+        }
+
+        if (view is IDesignable designable)
+        {
+            designable.EnableForDesign ();
+        }
+
+        var selectedCount = 0;
+        view.Selected += (s, e) => selectedCount++;
+
+        var acceptedCount = 0;
+        view.Accepted += (s, e) =>
+                         {
+                             acceptedCount++;
+                         };
+
+
+        if (view.InvokeCommand (Command.Accept) == true)
+        {
+            Assert.Equal (0, selectedCount);
+            Assert.Equal (1, acceptedCount);
+        }
     }
 }

+ 65 - 39
UnitTests/Views/LabelTests.cs

@@ -1316,10 +1316,56 @@ e
         super.Dispose ();
     }
 
+
     [Fact]
-    public void Label_CanFocus_True_Get_Focus_By_Keyboard ()
+    public void CanFocus_False_HotKey_SetsFocus_Next ()
     {
-        Label label = new () { Text = "label" };
+        View otherView = new () { Text = "otherView", CanFocus = true };
+        Label label = new () { Text = "_label" };
+        View nextView = new () { Text = "nextView", CanFocus = true };
+        Application.Navigation = new ();
+        Application.Top = new ();
+        Application.Top.Add (otherView, label, nextView);
+
+        Application.Top.SetFocus ();
+        Assert.True (otherView.HasFocus);
+
+        // No focused view accepts Tab, and there's no other view to focus, so OnKeyDown returns false
+        Assert.True (Application.OnKeyDown (label.HotKey));
+        Assert.False (otherView.HasFocus);
+        Assert.False (label.HasFocus);
+        Assert.True (nextView.HasFocus);
+
+        Application.Top.Dispose ();
+        Application.ResetState ();
+    }
+
+
+    [Fact]
+    public void CanFocus_False_MouseClick_SetsFocus_Next ()
+    {
+        View otherView = new () { X = 0, Y = 0, Width = 1, Height = 1, Id = "otherView", CanFocus = true };
+        Label label = new () { X = 0, Y = 1, Text = "_label" };
+        View nextView = new () { X = Pos.Right (label), Y = Pos.Top (label), Width = 1, Height = 1, Id = "nextView", CanFocus = true };
+        Application.Navigation = new ();
+        Application.Top = new ();
+        Application.Top.Add (otherView, label, nextView);
+
+        Application.Top.SetFocus ();
+
+        // click on label
+        Application.OnMouseEvent (new () { ScreenPosition = label.Frame.Location, Flags = MouseFlags.Button1Clicked });
+        Assert.False (label.HasFocus);
+        Assert.True (nextView.HasFocus);
+
+        Application.Top.Dispose ();
+        Application.ResetState ();
+    }
+
+    [Fact]
+    public void CanFocus_True_HotKey_SetsFocus ()
+    {
+        Label label = new () { Text = "_label" };
         View view = new () { Text = "view", CanFocus = true };
         Application.Navigation = new ();
         Application.Top = new ();
@@ -1333,21 +1379,7 @@ e
         Assert.True (view.HasFocus);
 
         // No focused view accepts Tab, and there's no other view to focus, so OnKeyDown returns false
-        Assert.False (Application.OnKeyDown (Key.Tab));
-        Assert.False (label.HasFocus);
-        Assert.True (view.HasFocus);
-
-        // Set label CanFocus to true
-        label.CanFocus = true;
-        Assert.False (label.HasFocus);
-        Assert.True (view.HasFocus);
-
-        // No focused view accepts Tab, but label can now be focused, so focus should move to it.
-        Assert.True (Application.OnKeyDown (Key.Tab));
-        Assert.True (label.HasFocus);
-        Assert.False (view.HasFocus);
-
-        Assert.True (Application.OnKeyDown (Key.Tab));
+        Assert.True (Application.OnKeyDown (label.HotKey));
         Assert.False (label.HasFocus);
         Assert.True (view.HasFocus);
 
@@ -1357,15 +1389,17 @@ e
 
 
     [Fact]
-    public void Label_CanFocus_True_Get_Focus_By_Mouse ()
+    public void CanFocus_True_MouseClick_Focuses ()
     {
+        Application.Navigation = new ();
         Label label = new ()
         {
             Text = "label",
             X = 0,
-            Y = 0
+            Y = 0,
+            CanFocus = true
         };
-        View view = new ()
+        View otherView = new ()
         {
             Text = "view",
             X = 0,
@@ -1379,34 +1413,26 @@ e
             Width = 10,
             Height = 10
         };
-        Application.Top.Add (label, view);
-
+        Application.Top.Add (label, otherView);
         Application.Top.SetFocus ();
-        Assert.Equal (view, Application.Top.MostFocused);
-        Assert.False (label.CanFocus);
-        Assert.False (label.HasFocus);
-        Assert.True (view.CanFocus);
-        Assert.True (view.HasFocus);
 
-        // label can't focus so clicking on it has no effect
-        Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked });
-        Assert.False (label.HasFocus);
-        Assert.True (view.HasFocus);
+        Assert.True (label.CanFocus);
+        Assert.True (label.HasFocus);
+        Assert.True (otherView.CanFocus);
+        Assert.False (otherView.HasFocus);
 
-        // Set label CanFocus to true
-        label.CanFocus = true;
-        Assert.False (label.HasFocus);
-        Assert.True (view.HasFocus);
+        otherView.SetFocus ();
+        Assert.True (otherView.HasFocus);
 
         // label can focus, so clicking on it set focus
-        Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked });
+        Application.OnMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Clicked });
         Assert.True (label.HasFocus);
-        Assert.False (view.HasFocus);
+        Assert.False (otherView.HasFocus);
 
         // click on view
-        Application.OnMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked });
+        Application.OnMouseEvent (new () { ScreenPosition = new (0, 1), Flags = MouseFlags.Button1Clicked });
         Assert.False (label.HasFocus);
-        Assert.True (view.HasFocus);
+        Assert.True (otherView.HasFocus);
 
         Application.Top.Dispose ();
         Application.ResetState ();

+ 80 - 67
UnitTests/Views/RadioGroupTests.cs

@@ -90,17 +90,17 @@ public class RadioGroupTests (ITestOutputHelper output)
         var selectedItemChangedCount = 0;
         rg.SelectedItemChanged += (s, e) => selectedItemChangedCount++;
 
-        var selectCount = 0;
-        rg.Selected += (s, e) => selectCount++;
+        var selectedCount = 0;
+        rg.Selected += (s, e) => selectedCount++;
 
-        var acceptCount = 0;
-        rg.Accepted += (s, e) => acceptCount++;
+        var acceptedCount = 0;
+        rg.Accepted += (s, e) => acceptedCount++;
 
         // 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 (0, selectedCount);
+        Assert.Equal (0, acceptedCount);
         Assert.Equal (Key.Empty, rg.HotKey);
 
         // With HasFocus
@@ -109,38 +109,38 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.Equal (0, rg.SelectedItem);
         Assert.Equal (0, rg.Cursor);
         Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
+        Assert.Equal (0, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
         Assert.True (Application.OnKeyDown (Key.CursorDown));
         Assert.Equal (0, rg.SelectedItem); // Cursor changed, but selection didnt
         Assert.Equal (1, rg.Cursor);
         Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
+        Assert.Equal (0, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
         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);
+        Assert.Equal (0, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
-        // Test Select (Space) when Cursor != SelectedItem
+        // Test Select (Space) when Cursor != SelectedItem - Should select cursor
         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);
+        Assert.Equal (1, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
-        // Now test Select (Space) when Cursor == SelectedItem - Should cycle
+        // 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.Equal (2, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.Equal (1, rg.SelectedItem);
@@ -166,8 +166,8 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.Equal (1, rg.SelectedItem);
         Assert.Equal (1, rg.Cursor);
         Assert.Equal (7, selectedItemChangedCount);
-        Assert.Equal (7, selectCount);
-        Assert.Equal (0, acceptCount);
+        Assert.Equal (7, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
         // Test HotKey
         //    Selected == Cursor (1) - Advance state and raise Select event - DO NOT raise Accept
@@ -178,8 +178,8 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.Equal (0, rg.SelectedItem);
         Assert.Equal (0, rg.Cursor);
         Assert.Equal (8, selectedItemChangedCount);
-        Assert.Equal (8, selectCount);
-        Assert.Equal (0, acceptCount);
+        Assert.Equal (8, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
         //     Make Selected != Cursor
         Assert.True (Application.OnKeyDown (Key.CursorDown));
@@ -191,8 +191,8 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.Equal (1, rg.SelectedItem);
         Assert.Equal (1, rg.Cursor);
         Assert.Equal (9, selectedItemChangedCount);
-        Assert.Equal (9, selectCount);
-        Assert.Equal (0, acceptCount);
+        Assert.Equal (9, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
         Application.ResetState (true);
     }
@@ -372,7 +372,6 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.NotEmpty (rg.KeyBindings.GetCommands (KeyCode.L | KeyCode.ShiftMask));
         Assert.NotEmpty (rg.KeyBindings.GetCommands (KeyCode.L | KeyCode.AltMask));
 
-        // BUGBUG: These tests only test that RG works on it's own, not if it's a subview
         Assert.True (Application.OnKeyDown (Key.T));
         Assert.Equal (2, rg.SelectedItem);
         Assert.True (Application.OnKeyDown (Key.L));
@@ -463,7 +462,7 @@ public class RadioGroupTests (ITestOutputHelper output)
         group.NewKeyDownEvent (Key.G.WithAlt);
 
         Assert.Equal (0, group.SelectedItem);
-        Assert.True (group.HasFocus);
+        Assert.False (group.HasFocus);
     }
 
     [Fact]
@@ -619,18 +618,18 @@ public class RadioGroupTests (ITestOutputHelper output)
     {
         var radioGroup = new RadioGroup
         {
-            RadioLabels = ["_1", "__2"]
+            RadioLabels = ["_1", "_2"]
         };
         Assert.True (radioGroup.CanFocus);
 
         var selectedItemChanged = 0;
         radioGroup.SelectedItemChanged += (s, e) => selectedItemChanged++;
 
-        var selectCount = 0;
-        radioGroup.Selected += (s, e) => selectCount++;
+        var selectedCount = 0;
+        radioGroup.Selected += (s, e) => selectedCount++;
 
-        var acceptCount = 0;
-        radioGroup.Accepted += (s, e) => acceptCount++;
+        var acceptedCount = 0;
+        radioGroup.Accepted += (s, e) => acceptedCount++;
 
         Assert.Equal (Orientation.Vertical, radioGroup.Orientation);
 
@@ -638,26 +637,29 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.True (radioGroup.HasFocus);
         Assert.Equal (0, radioGroup.SelectedItem);
         Assert.Equal (0, selectedItemChanged);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
+        Assert.Equal (0, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
-        Assert.False (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
+        // Click on the first item, which is already selected
+        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
         Assert.Equal (0, radioGroup.SelectedItem);
         Assert.Equal (0, selectedItemChanged);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
+        Assert.Equal (0, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
+        // Click on the second item
         Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked }));
         Assert.Equal (1, radioGroup.SelectedItem);
         Assert.Equal (1, selectedItemChanged);
-        Assert.Equal (1, selectCount);
-        Assert.Equal (0, acceptCount);
+        Assert.Equal (1, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
+        // Click on the first item
         Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
         Assert.Equal (0, radioGroup.SelectedItem);
         Assert.Equal (2, selectedItemChanged);
-        Assert.Equal (2, selectCount);
-        Assert.Equal (0, acceptCount);
+        Assert.Equal (2, selectedCount);
+        Assert.Equal (0, acceptedCount);
     }
 
     [Fact]
@@ -673,16 +675,16 @@ public class RadioGroupTests (ITestOutputHelper output)
         var selectedItemChanged = 0;
         radioGroup.SelectedItemChanged += (s, e) => selectedItemChanged++;
 
-        var selectCount = 0;
-        radioGroup.Selected += (s, e) => selectCount++;
+        var selectedCount = 0;
+        radioGroup.Selected += (s, e) => selectedCount++;
 
-        var acceptCount = 0;
-        var handleAccept = false;
+        var acceptedCount = 0;
+        var handleAccepted = false;
 
         radioGroup.Accepted += (s, e) =>
                              {
-                                 acceptCount++;
-                                 e.Handled = handleAccept;
+                                 acceptedCount++;
+                                 e.Handled = handleAccepted;
                              };
 
         Assert.True (radioGroup.DoubleClickAccepts);
@@ -692,25 +694,32 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.True (radioGroup.HasFocus);
         Assert.Equal (0, radioGroup.SelectedItem);
         Assert.Equal (0, selectedItemChanged);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
+        Assert.Equal (0, selectedCount);
+        Assert.Equal (0, acceptedCount);
 
+        // NOTE: Drivers ALWAYS generate a Button1Clicked event before Button1DoubleClicked
+        // NOTE: We need to do the same
+
+        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
         Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked }));
         Assert.Equal (0, radioGroup.SelectedItem);
         Assert.Equal (0, selectedItemChanged);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (1, acceptCount);
+        Assert.Equal (0, selectedCount);
+        Assert.Equal (1, acceptedCount);
 
+        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked }));
         Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked }));
         Assert.Equal (1, radioGroup.SelectedItem);
         Assert.Equal (1, selectedItemChanged);
-        Assert.Equal (1, selectCount);
-        Assert.Equal (1, acceptCount);
+        Assert.Equal (1, selectedCount);
+        Assert.Equal (1, acceptedCount);
+
+        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked }));
         Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1DoubleClicked }));
         Assert.Equal (1, radioGroup.SelectedItem);
         Assert.Equal (1, selectedItemChanged);
-        Assert.Equal (1, selectCount);
-        Assert.Equal (2, acceptCount);
+        Assert.Equal (1, selectedCount);
+        Assert.Equal (2, acceptedCount);
 
         View superView = new () { Id = "superView", CanFocus = true };
         superView.Add (radioGroup);
@@ -719,8 +728,8 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
         Assert.Equal (0, radioGroup.SelectedItem);
         Assert.Equal (2, selectedItemChanged);
-        Assert.Equal (2, selectCount);
-        Assert.Equal (2, acceptCount);
+        Assert.Equal (2, selectedCount);
+        Assert.Equal (2, acceptedCount);
 
         var superViewAcceptCount = 0;
 
@@ -732,24 +741,28 @@ public class RadioGroupTests (ITestOutputHelper output)
 
         Assert.Equal (0, superViewAcceptCount);
 
-        handleAccept = true;
-        Assert.False (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked }));
+        // By handling the event, we're cancelling it. So the radio group should not change.
+        handleAccepted = true;
+        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
+        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked }));
         Assert.Equal (0, radioGroup.SelectedItem);
         Assert.Equal (2, selectedItemChanged);
-        Assert.Equal (2, selectCount);
-        Assert.Equal (3, acceptCount);
-        Assert.Equal (1, superViewAcceptCount);
+        Assert.Equal (2, selectedCount);
+        Assert.Equal (3, acceptedCount);
+        Assert.Equal (0, superViewAcceptCount);
 
-        handleAccept = false;
-        Assert.False (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked }));
+        handleAccepted = false;
+        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
+        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked }));
         Assert.Equal (0, radioGroup.SelectedItem);
         Assert.Equal (2, selectedItemChanged);
-        Assert.Equal (2, selectCount);
-        Assert.Equal (4, acceptCount);
-        Assert.Equal (3, superViewAcceptCount); // Accept bubbles up to superview
+        Assert.Equal (2, selectedCount);
+        Assert.Equal (4, acceptedCount);
+        Assert.Equal (1, superViewAcceptCount); // Accept bubbles up to superview
 
         radioGroup.DoubleClickAccepts = false;
-        Assert.False (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1DoubleClicked }));
+        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked }));
+        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1DoubleClicked }));
     }
 
     #endregion Mouse Tests