Преглед изворни кода

Rebased with v2_develop - broke stuff.

Tig пре 1 година
родитељ
комит
dd8fe76cab

+ 15 - 17
Terminal.Gui/Application.cs

@@ -1456,15 +1456,6 @@ public static partial class Application
         // TODO: In PR #3273, FindDeepestView will return adornments. Update logic below to fix adornment mouse handling
         var view = View.FindDeepestView (Current, a.MouseEvent.X, a.MouseEvent.Y);
 
-        if (view is { WantContinuousButtonPressed: true })
-        {
-            WantContinuousButtonPressedView = view;
-        }
-        else
-        {
-            WantContinuousButtonPressedView = null;
-        }
-
         if (view is { })
         {
             a.MouseEvent.View = view;
@@ -1481,23 +1472,20 @@ public static partial class Application
         {
             // If the mouse is grabbed, send the event to the view that grabbed it.
             // The coordinates are relative to the Bounds of the view that grabbed the mouse.
-            Point frameLoc = MouseGrabView.ScreenToFrame (a.MouseEvent.X, a.MouseEvent.Y);
+            Point boundsLoc = MouseGrabView.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y);
 
             var viewRelativeMouseEvent = new MouseEvent
             {
-                X = frameLoc.X,
-                Y = frameLoc.Y,
+                X = boundsLoc.X,
+                Y = boundsLoc.Y,
                 Flags = a.MouseEvent.Flags,
                 ScreenPosition = new (a.MouseEvent.X, a.MouseEvent.Y),
-                View = view
+                View = MouseGrabView
             };
 
             if (MouseGrabView.Bounds.Contains (viewRelativeMouseEvent.X, viewRelativeMouseEvent.Y) is false)
             {
-                // The mouse has moved outside the bounds of the view that
-                // grabbed the mouse, so we tell the view that last got 
-                // OnMouseEnter the mouse is leaving
-                // BUGBUG: That sentence makes no sense. Either I'm missing something or this logic is flawed.
+                // The mouse has moved outside the bounds of the view that grabbed the mouse
                 _mouseEnteredView?.OnMouseLeave (a.MouseEvent);
             }
 
@@ -1508,6 +1496,16 @@ public static partial class Application
             }
         }
 
+        if (view is { WantContinuousButtonPressed: true })
+        {
+            WantContinuousButtonPressedView = view;
+        }
+        else
+        {
+            WantContinuousButtonPressedView = null;
+        }
+
+
         if (view is not Adornment)
         {
             if ((view is null || view == OverlappedTop)

+ 6 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -2,6 +2,7 @@
 // Driver.cs: Curses-based Driver
 //
 
+using System.Diagnostics;
 using System.Runtime.InteropServices;
 using Terminal.Gui.ConsoleDrivers;
 using Unix.Terminal;
@@ -798,6 +799,9 @@ internal class CursesDriver : ConsoleDriver
                    || flag.HasFlag (MouseFlags.Button4DoubleClicked);
         }
 
+        Debug.WriteLine ($"CursesDriver: ({pos.X},{pos.Y}) - {mouseFlag}");
+
+
         if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) || (IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0))
         {
             return;
@@ -806,6 +810,8 @@ internal class CursesDriver : ConsoleDriver
         _lastMouseFlags = mouseFlag;
 
         var me = new MouseEvent { Flags = mouseFlag, X = pos.X, Y = pos.Y };
+        Debug.WriteLine ($"CursesDriver: ({me.X},{me.Y}) - {me.Flags}");
+
         OnMouseEvent (new MouseEventEventArgs (me));
     }
 

+ 2 - 2
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1137,7 +1137,7 @@ internal class NetDriver : ConsoleDriver
                 break;
             case EventType.Mouse:
                 MouseEvent me = ToDriverMouse (inputEvent.MouseEvent);
-                //Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}");
+                Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}");
                 OnMouseEvent (new MouseEventEventArgs (me));
 
                 break;
@@ -1379,7 +1379,7 @@ internal class NetDriver : ConsoleDriver
 
     private MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
     {
-       // System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
+       //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
 
         MouseFlags mouseFlag = 0;
 

+ 25 - 6
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1698,7 +1698,13 @@ internal class WindowsDriver : ConsoleDriver
 
     private async Task ProcessButtonDoubleClickedAsync ()
     {
-        await Task.Delay (300);
+        // Only delay if there's not a view wanting continuous button presses
+        if (Application.WantContinuousButtonPressedView is null)
+        {
+            // QUESTION: Why 300ms?
+            await Task.Delay (300);
+        }
+
         _isButtonDoubleClicked = false;
         _isOneFingerDoubleClicked = false;
 
@@ -1707,10 +1713,16 @@ internal class WindowsDriver : ConsoleDriver
 
     private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
     {
+        // When a user presses-and-holds, start generating pressed events every `startDelay`
+        // After `iterationsUntilFast` iterations, speed them up to `fastDelay` ms
+        const int startDelay = 50;
+        const int iterationsUntilFast = 4;
+        const int fastDelay = 50;
+
+        int iterations = 0;
+        int delay = startDelay;
         while (_isButtonPressed)
         {
-            await Task.Delay (100);
-
             var me = new MouseEvent
             {
                 X = _pointMove.X,
@@ -1718,13 +1730,18 @@ internal class WindowsDriver : ConsoleDriver
                 Flags = mouseFlag
             };
 
-            View view = Application.WantContinuousButtonPressedView;
-
-            if (view is null)
+            if (Application.WantContinuousButtonPressedView is null)
             {
                 break;
             }
 
+            if (iterations++ >= iterationsUntilFast)
+            {
+                delay = fastDelay;
+            }
+            await Task.Delay (delay);
+
+            //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}");
             if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
             {
                 Application.Invoke (() => OnMouseEvent (new MouseEventEventArgs (me)));
@@ -1896,6 +1913,8 @@ internal class WindowsDriver : ConsoleDriver
             {
                 _point = null;
             }
+            _processButtonClick = true;
+
         }
         else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved
                  && !_isOneFingerDoubleClicked

+ 9 - 0
Terminal.Gui/View/Adornment/Adornment.cs

@@ -201,6 +201,10 @@ public class Adornment : View
     /// <returns><see langword="true"/> if the specified Parent's SuperView-relative coordinates are within the Adornment's Thickness. </returns>
     public override bool Contains (int x, int y)
     {
+        if (Parent is null)
+        {
+            return false;
+        }
         Rectangle frame = Frame;
         frame.Offset (Parent.Frame.Location);
 
@@ -239,6 +243,11 @@ public class Adornment : View
     /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
     protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
     {
+        if (Parent is null)
+        {
+            return false;
+        }
+
         var args = new MouseEventEventArgs (mouseEvent);
 
         if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))

+ 6 - 1
Terminal.Gui/View/Adornment/Padding.cs

@@ -52,13 +52,18 @@ public class Padding : Adornment
     /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
     protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
     {
+        if (Parent is null)
+        {
+            return false;
+        }
+
         if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
         {
             if (Parent.CanFocus && !Parent.HasFocus)
             {
                 Parent.SetFocus ();
                 Parent.SetNeedsDisplay ();
-                return true;
+                return mouseEvent.Handled = true;
             }
         }
 

+ 9 - 2
Terminal.Gui/View/ViewDrawing.cs

@@ -524,12 +524,12 @@ public partial class View
             _needsDisplayRect = new (x, y, w, h);
         }
 
-        SuperView?.SetSubViewNeedsDisplay ();
-
         Margin?.SetNeedsDisplay ();
         Border?.SetNeedsDisplay ();
         Padding?.SetNeedsDisplay ();
 
+        SuperView?.SetSubViewNeedsDisplay ();
+
         foreach (View subview in Subviews)
         {
             if (subview.Frame.IntersectsWith (region))
@@ -550,6 +550,13 @@ public partial class View
         if (SuperView is { SubViewNeedsDisplay: false })
         {
             SuperView.SetSubViewNeedsDisplay ();
+
+            return;
+        }
+
+        if (this is Adornment adornment)
+        {
+            adornment.Parent?.SetSubViewNeedsDisplay ();
         }
     }
 

+ 148 - 57
Terminal.Gui/View/ViewMouse.cs

@@ -2,46 +2,26 @@
 
 public partial class View
 {
-    /// <summary>Gets or sets a value indicating whether this <see cref="View"/> want continuous button pressed event.</summary>
+    /// <summary>
+    ///     Gets or sets whether the <see cref="View"/> will be highlighted visually while the mouse button is
+    ///     pressed.
+    /// </summary>
+    public bool HighlightOnPress { get; set; }
+
+    /// <summary>Gets or sets whether the <see cref="View"/> wants continuous button pressed events.</summary>
     public virtual bool WantContinuousButtonPressed { get; set; }
 
-    /// <summary>Gets or sets a value indicating whether this <see cref="View"/> wants mouse position reports.</summary>
-    /// <value><see langword="true"/> if want mouse position reports; otherwise, <see langword="false"/>.</value>
+    /// <summary>Gets or sets whether the <see cref="View"/> wants mouse position reports.</summary>
+    /// <value><see langword="true"/> if mouse position reports are wanted; otherwise, <see langword="false"/>.</value>
     public virtual bool WantMousePositionReports { get; set; }
 
-    /// <summary>Event fired when a mouse event occurs.</summary>
-    /// <remarks>
-    /// <para>
-    /// The coordinates are relative to <see cref="View.Bounds"/>.
-    /// </para>
-    /// </remarks>
-    public event EventHandler<MouseEventEventArgs> MouseEvent;
-
-    /// <summary>Event fired when a mouse click occurs.</summary>
-    /// <remarks>
-    /// <para>
-    /// Fired when the mouse is either clicked or double-clicked. Check
-    /// <see cref="MouseEvent.Flags"/> to see which button was clicked.
-    /// </para>
-    /// <para>
-    /// The coordinates are relative to <see cref="View.Bounds"/>.
-    /// </para>
-    /// </remarks>
-    public event EventHandler<MouseEventEventArgs> MouseClick;
-
-    /// <summary>Event fired when the mouse moves into the View's <see cref="Bounds"/>.</summary>
-    public event EventHandler<MouseEventEventArgs> MouseEnter;
-
-    /// <summary>Event fired when the mouse leaves the View's <see cref="Bounds"/>.</summary>
-    public event EventHandler<MouseEventEventArgs> MouseLeave;
-
-    // TODO: OnMouseEnter should not be public virtual, but protected.
     /// <summary>
-    ///     Called when the mouse enters the View's <see cref="Bounds"/>. The view will now receive mouse events until the mouse leaves
+    ///     Called when the mouse enters the View's <see cref="Bounds"/>. The view will now receive mouse events until the
+    ///     mouse leaves
     ///     the view. At which time, <see cref="OnMouseLeave(Gui.MouseEvent)"/> will be called.
     /// </summary>
     /// <remarks>
-    /// The coordinates are relative to <see cref="View.Bounds"/>.
+    ///     The coordinates are relative to <see cref="View.Bounds"/>.
     /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
@@ -63,13 +43,16 @@ public partial class View
         return args.Handled;
     }
 
-    // TODO: OnMouseLeave should not be public virtual, but protected.
+    /// <summary>Event fired when the mouse moves into the View's <see cref="Bounds"/>.</summary>
+    public event EventHandler<MouseEventEventArgs> MouseEnter;
+
     /// <summary>
-    ///     Called when the mouse has moved out of the View's <see cref="Bounds"/>. The view will no longer receive mouse events (until the
+    ///     Called when the mouse has moved out of the View's <see cref="Bounds"/>. The view will no longer receive mouse
+    ///     events (until the
     ///     mouse moves within the view again and <see cref="OnMouseEnter(Gui.MouseEvent)"/> is called).
     /// </summary>
     /// <remarks>
-    /// The coordinates are relative to <see cref="View.Bounds"/>.
+    ///     The coordinates are relative to <see cref="View.Bounds"/>.
     /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
@@ -91,12 +74,17 @@ public partial class View
         return args.Handled;
     }
 
-    // TODO: OnMouseEvent should not be public virtual, but protected.
+    /// <summary>Event fired when the mouse leaves the View's <see cref="Bounds"/>.</summary>
+    public event EventHandler<MouseEventEventArgs> MouseLeave;
+
+    [CanBeNull]
+    private ColorScheme _savedColorScheme;
+
     /// <summary>Called when a mouse event occurs within the view's <see cref="Bounds"/>.</summary>
     /// <remarks>
-    /// <para>
-    /// The coordinates are relative to <see cref="View.Bounds"/>.
-    /// </para>
+    ///     <para>
+    ///         The coordinates are relative to <see cref="View.Bounds"/>.
+    ///     </para>
     /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
@@ -104,7 +92,8 @@ public partial class View
     {
         if (!Enabled)
         {
-            return true;
+            // A disabled view should not eat mouse events
+            return false;
         }
 
         if (!CanBeVisible (this))
@@ -114,43 +103,132 @@ public partial class View
 
         var args = new MouseEventEventArgs (mouseEvent);
 
-        // Clicked support for all buttons and single and double click
-        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)
-            || mouseEvent.Flags.HasFlag (MouseFlags.Button2Clicked)
-            || mouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)
-            || mouseEvent.Flags.HasFlag (MouseFlags.Button4Clicked))
-        {
+        // Default behavior is to invoke Accept (via HotKey) on clicked.
+        if (
+            // !WantContinuousButtonPressed &&
+            Application.MouseGrabView != this
+            && (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)
+                || mouseEvent.Flags.HasFlag (MouseFlags.Button2Clicked)
+                || mouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)
+                || mouseEvent.Flags.HasFlag (MouseFlags.Button4Clicked)))
+        { 
             return OnMouseClick (args);
         }
 
-        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
-            || mouseEvent.Flags.HasFlag (MouseFlags.Button2DoubleClicked)
-            || mouseEvent.Flags.HasFlag (MouseFlags.Button3DoubleClicked)
-            || mouseEvent.Flags.HasFlag (MouseFlags.Button4DoubleClicked))
+        if (!HighlightOnPress)
         {
-            return OnMouseClick (args);
+            return false;
+        }
+
+        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
+            || mouseEvent.Flags.HasFlag (MouseFlags.Button2Pressed)
+            || mouseEvent.Flags.HasFlag (MouseFlags.Button3Pressed)
+            || mouseEvent.Flags.HasFlag (MouseFlags.Button4Pressed))
+        {
+            // If WantContinuousButtonPressed is true, and this is not the first pressed event,
+            // invoke Accept (via HotKey)
+            if (WantContinuousButtonPressed && Application.MouseGrabView == this)
+            {
+                return OnMouseClick (args);
+            }
+
+            // The first time we get pressed event, grab the mouse and invert the colors
+            if (Application.MouseGrabView != this)
+            {
+                Application.GrabMouse (this);
+
+                if (HighlightOnPress && ColorScheme is { })
+                {
+                    _savedColorScheme = ColorScheme;
+                    if (CanFocus)
+                    {
+                        // TODO: Make the inverted color configurable
+                        var cs = new ColorScheme (ColorScheme)
+                        {
+                            Focus = new (ColorScheme.Normal.Foreground, ColorScheme.Focus.Background)
+                        };
+                        ColorScheme = cs;
+                    }
+                    else
+                    {
+                        var cs = new ColorScheme (ColorScheme)
+                        {
+                            Normal = new (ColorScheme.Focus.Background, ColorScheme.Normal.Foreground)
+                        };
+                        ColorScheme = cs;
+                    }
+                }
+
+                if (CanFocus)
+                {
+                    // Set the focus, but don't invoke Accept
+                    SetFocus ();
+                }
+            }
+            args.Handled = true;
         }
 
-        MouseEvent?.Invoke (this, args);
+        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released)
+            || mouseEvent.Flags.HasFlag (MouseFlags.Button2Released)
+            || mouseEvent.Flags.HasFlag (MouseFlags.Button3Released)
+            || mouseEvent.Flags.HasFlag (MouseFlags.Button4Released))
+        {
+
+            if (Application.MouseGrabView == this)
+            {
+                // When the mouse is released, if WantContinuousButtonPressed is set, invoke Accept one last time.
+                //if (WantContinuousButtonPressed)
+                {
+                    OnMouseClick (args);
+                }
+
+                Application.UngrabMouse ();
+
+                if (HighlightOnPress && _savedColorScheme is { })
+                {
+                    ColorScheme = _savedColorScheme;
+                    _savedColorScheme = null;
+                }
+            }
+            args.Handled = true;
+        }
+
+        if (args.Handled != true)
+        {
+            MouseEvent?.Invoke (this, args);
+        }
 
-        return args.Handled == true;
+        return args.Handled;
     }
 
+    /// <summary>Event fired when a mouse event occurs.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         The coordinates are relative to <see cref="View.Bounds"/>.
+    ///     </para>
+    /// </remarks>
+    public event EventHandler<MouseEventEventArgs> MouseEvent;
+
     /// <summary>Invokes the MouseClick event.</summary>
     /// <remarks>
-    /// <para>
-    /// Called when the mouse is either clicked or double-clicked. Check
-    /// <see cref="MouseEvent.Flags"/> to see which button was clicked.
-    /// </para>
+    ///     <para>
+    ///         Called when the mouse is either clicked or double-clicked. Check
+    ///         <see cref="MouseEvent.Flags"/> to see which button was clicked.
+    ///     </para>
     /// </remarks>
+    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
     protected bool OnMouseClick (MouseEventEventArgs args)
     {
         if (!Enabled)
         {
+            // QUESTION: Is this right? Should a disabled view eat mouse clicks?
+            args.Handled = true;
+
             return true;
         }
 
         MouseClick?.Invoke (this, args);
+
         if (args.Handled)
         {
             return true;
@@ -158,9 +236,22 @@ public partial class View
 
         if (!HasFocus && CanFocus)
         {
+            args.Handled = true;
             SetFocus ();
         }
 
         return args.Handled;
     }
+
+    /// <summary>Event fired when a mouse click occurs.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Fired when the mouse is either clicked or double-clicked. Check
+    ///         <see cref="MouseEvent.Flags"/> to see which button was clicked.
+    ///     </para>
+    ///     <para>
+    ///         The coordinates are relative to <see cref="View.Bounds"/>.
+    ///     </para>
+    /// </remarks>
+    public event EventHandler<MouseEventEventArgs> MouseClick;
 }

+ 19 - 2
Terminal.Gui/View/ViewSubViews.cs

@@ -31,8 +31,14 @@ public partial class View
 
     /// <summary>Adds a subview (child) to this view.</summary>
     /// <remarks>
+    /// <para>
     ///     The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. See also
     ///     <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+    /// </para>
+    /// <para>
+    ///     Subviews will be disposed when this View is disposed. In other-words, calling this method causes
+    ///     the lifecycle of the subviews to be transferred to this View.
+    /// </para>
     /// </remarks>
     public virtual void Add (View view)
     {
@@ -92,8 +98,14 @@ public partial class View
     /// <summary>Adds the specified views (children) to the view.</summary>
     /// <param name="views">Array of one or more views (can be optional parameter).</param>
     /// <remarks>
+    /// <para>
     ///     The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. See also
-    ///     <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+    ///     <seealso cref="Remove(View)"/> and <seealso cref="RemoveAll"/>.
+    /// </para>
+    /// <para>
+    ///     Subviews will be disposed when this View is disposed. In other-words, calling this method causes
+    ///     the lifecycle of the subviews to be transferred to this View.
+    /// </para>
     /// </remarks>
     public void Add (params View [] views)
     {
@@ -185,7 +197,12 @@ public partial class View
     }
 
     /// <summary>Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.</summary>
-    /// <remarks></remarks>
+    /// <remarks>
+    /// <para>
+    ///     Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the Subview's
+    ///     lifecycle to be transferred to the caller; the caller muse call <see cref="Dispose"/>.
+    /// </para>
+    /// </remarks>
     public virtual void Remove (View view)
     {
         if (view is null || _subviews is null)

+ 5 - 1
Terminal.Gui/Views/Button.cs

@@ -20,6 +20,10 @@ namespace Terminal.Gui;
 ///         no other <see cref="View"/> processes the key, the <see cref="Button"/>'s <see cref="View.Accept"/> event will
 ///         be fired.
 ///     </para>
+///     <para>
+///         Set <see cref="View.WantContinuousButtonPressed"/> to <see langword="true"/> to have the <see cref="View.Accept"/> event
+///         invoked repeatedly while the button is pressed.
+///     </para>
 /// </remarks>
 public class Button : View
 {
@@ -46,6 +50,7 @@ public class Button : View
 
         CanFocus = true;
         AutoSize = true;
+        HighlightOnPress = true;
 
         // Override default behavior of View
         AddCommand (Command.HotKey, () =>
@@ -60,7 +65,6 @@ public class Button : View
         TitleChanged += Button_TitleChanged;
         MouseClick += Button_MouseClick;
     }
-
     private void Button_MouseClick (object sender, MouseEventEventArgs e)
     {
         e.Handled = InvokeCommand (Command.HotKey) == true;

+ 1 - 0
Terminal.Gui/Views/CheckBox.cs

@@ -35,6 +35,7 @@ public class CheckBox : View
 
         TitleChanged += Checkbox_TitleChanged;
 
+        HighlightOnPress = true;
         MouseClick += CheckBox_MouseClick;
     }
 

+ 55 - 45
Terminal.Gui/Views/ColorPicker.cs

@@ -29,6 +29,49 @@ public class ColorPicker : View
     /// <summary>Initializes a new instance of <see cref="ColorPicker"/>.</summary>
     public ColorPicker () { SetInitialProperties (); }
 
+    private void SetInitialProperties ()
+    {
+        HighlightOnPress = true;
+
+        CanFocus = true;
+        AddCommands ();
+        AddKeyBindings ();
+
+        LayoutStarted += (o, a) =>
+                         {
+                             Thickness thickness = GetAdornmentsThickness ();
+                             Width = _cols * BoxWidth + thickness.Vertical;
+                             Height = _rows * BoxHeight + thickness.Horizontal;
+                         };
+//        MouseEvent += ColorPicker_MouseEvent;
+        MouseClick += ColorPicker_MouseClick;
+    }
+
+    // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
+    //private void ColorPicker_MouseEvent (object sender, MouseEventEventArgs me)
+    //{
+    //    if (me.MouseEvent.X > Bounds.Width || me.MouseEvent.Y > Bounds.Height)
+    //    {
+    //        me.Handled = true;
+
+    //        return;
+    //    }
+
+    //    me.Handled = true;
+
+    //    return;
+    //}
+
+    private void ColorPicker_MouseClick (object sender, MouseEventEventArgs me)
+    {
+        if (CanFocus)
+        {
+            Cursor = new Point (me.MouseEvent.X / _boxWidth, me.MouseEvent.Y / _boxHeight);
+            SetFocus ();
+            me.Handled = true;
+        }
+    }
+
     /// <summary>Height of a color box</summary>
     public int BoxHeight
     {
@@ -88,37 +131,6 @@ public class ColorPicker : View
     /// <summary>Fired when a color is picked.</summary>
     public event EventHandler<ColorEventArgs> ColorChanged;
 
-    ///<inheritdoc/>
-    protected internal override bool OnMouseEvent  (MouseEvent me)
-    {
-        if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus)
-        {
-            return false;
-        }
-
-        SetFocus ();
-
-        if (me.X > Bounds.Width || me.Y > Bounds.Height)
-        {
-            return true;
-        }
-
-        Cursor = new Point (me.X / _boxWidth, me.Y / _boxHeight);
-
-        return true;
-    }
-
-    /// <summary>Moves the selected item index to the next row.</summary>
-    /// <returns></returns>
-    public virtual bool MoveDown ()
-    {
-        if (Cursor.Y < _rows - 1)
-        {
-            SelectedColor += _cols;
-        }
-
-        return true;
-    }
 
     /// <summary>Moves the selected item index to the previous column.</summary>
     /// <returns></returns>
@@ -156,6 +168,18 @@ public class ColorPicker : View
         return true;
     }
 
+    /// <summary>Moves the selected item index to the next row.</summary>
+    /// <returns></returns>
+    public virtual bool MoveDown ()
+    {
+        if (Cursor.Y < _rows - 1)
+        {
+            SelectedColor += _cols;
+        }
+
+        return true;
+    }
+
     ///<inheritdoc/>
     public override void OnDrawContent (Rectangle contentArea)
     {
@@ -265,18 +289,4 @@ public class ColorPicker : View
             AddRune (p.Key.X, p.Key.Y, p.Value);
         }
     }
-
-    private void SetInitialProperties ()
-    {
-        CanFocus = true;
-        AddCommands ();
-        AddKeyBindings ();
-
-        LayoutStarted += (o, a) =>
-                         {
-                             Thickness thickness = GetAdornmentsThickness ();
-                             Width = _cols * BoxWidth + thickness.Vertical;
-                             Height = _rows * BoxHeight + thickness.Horizontal;
-                         };
-    }
 }

+ 11 - 5
Terminal.Gui/Views/DatePicker.cs

@@ -215,11 +215,14 @@ public class DatePicker : View
         _previousMonthButton = new Button
         {
             AutoSize = false,
-            X = Pos.Center () - 4,
+            X = Pos.Center () - 2,
             Y = Pos.Bottom (_calendar) - 1,
             Height = 1,
-            Width = CalculateCalendarWidth () / 2,
-            Text = GetBackButtonText ()
+            Width = 2,
+            Text = GetBackButtonText (),
+            WantContinuousButtonPressed = true,
+            NoPadding = true,
+            NoDecorations = true
         };
 
         _previousMonthButton.Accept += (sender, e) =>
@@ -235,8 +238,11 @@ public class DatePicker : View
             X = Pos.Right (_previousMonthButton) + 2,
             Y = Pos.Bottom (_calendar) - 1,
             Height = 1,
-            Width = CalculateCalendarWidth () / 2,
-            Text = GetBackButtonText ()
+            Width = 2,
+            Text = GetForwardButtonText(),
+            WantContinuousButtonPressed = true,
+            NoPadding = true,
+            NoDecorations = true
         };
 
         _nextMonthButton.Accept += (sender, e) =>

+ 59 - 60
Terminal.Gui/Views/MessageBox.cs

@@ -386,66 +386,6 @@ public static class MessageBox
         messageLabel.TextFormatter.MultiLine = !wrapMessage;
         d.Add (messageLabel);
 
-        d.Loaded += (s, e) =>
-                    {
-                        if (width != 0 || height != 0)
-                        {
-                            return;
-                        }
-
-                        // TODO: replace with Dim.Fit when implemented
-                        Rectangle maxBounds = d.SuperView?.Bounds ?? Application.Top.Bounds;
-
-                        Thickness adornmentsThickness = d.GetAdornmentsThickness ();
-
-                        if (wrapMessage)
-                        {
-                            messageLabel.TextFormatter.Size = new (
-                                                                   maxBounds.Size.Width
-                                                                   - adornmentsThickness.Horizontal,
-                                                                   maxBounds.Size.Height
-                                                                   - adornmentsThickness.Vertical
-                                                                  );
-                        }
-
-                        string msg = messageLabel.TextFormatter.Format ();
-                        Size messageSize = messageLabel.TextFormatter.FormatAndGetSize ();
-
-                        // Ensure the width fits the text + buttons
-                        int newWidth = Math.Max (
-                                                 width,
-                                                 Math.Max (
-                                                           messageSize.Width + adornmentsThickness.Horizontal,
-                                                           d.GetButtonsWidth () + d.Buttons.Length + adornmentsThickness.Horizontal
-                                                          )
-                                                );
-
-                        if (newWidth > d.Frame.Width)
-                        {
-                            d.Width = newWidth;
-                        }
-
-                        // Ensure height fits the text + vspace + buttons
-                        if (messageSize.Height == 0)
-                        {
-                            d.Height = Math.Max (height, 3 + adornmentsThickness.Vertical);
-                        }
-                        else
-                        {
-                            string lastLine = messageLabel.TextFormatter.GetLines () [^1];
-
-                            // INTENT: Instead of the check against \n or \r\n, how about just Environment.NewLine?
-                            d.Height = Math.Max (
-                                                 height,
-                                                 messageSize.Height
-                                                 + (lastLine.EndsWith ("\r\n") || lastLine.EndsWith ('\n') ? 1 : 2)
-                                                 + adornmentsThickness.Vertical
-                                                );
-                        }
-
-                        d.SetRelativeLayout (d.SuperView?.Frame ?? Application.Top.Frame);
-                    };
-
         // Setup actions
         Clicked = -1;
 
@@ -466,10 +406,69 @@ public static class MessageBox
             }
         }
 
+        d.Loaded += Dialog_Loaded;
+
         // Run the modal; do not shutdown the mainloop driver when done
         Application.Run (d);
         d.Dispose ();
 
         return Clicked;
+
+        void Dialog_Loaded (object s, EventArgs e)
+        {
+            if (width != 0 || height != 0)
+            {
+                return;
+            }
+
+            // TODO: replace with Dim.Fit when implemented
+            Rectangle maxBounds = d.SuperView?.Bounds ?? Application.Top.Bounds;
+
+            Thickness adornmentsThickness = d.GetAdornmentsThickness ();
+
+            if (wrapMessage)
+            {
+                messageLabel.TextFormatter.Size = new (
+                                                       maxBounds.Size.Width
+                                                       - adornmentsThickness.Horizontal,
+                                                       maxBounds.Size.Height
+                                                       - adornmentsThickness.Vertical);
+            }
+
+            string msg = messageLabel.TextFormatter.Format ();
+            Size messageSize = messageLabel.TextFormatter.FormatAndGetSize ();
+
+            // Ensure the width fits the text + buttons
+            int newWidth = Math.Max (
+                                     width,
+                                     Math.Max (
+                                               messageSize.Width + adornmentsThickness.Horizontal,
+                                               d.GetButtonsWidth () + d.Buttons.Length + adornmentsThickness.Horizontal));
+
+            if (newWidth > d.Frame.Width)
+            {
+                d.Width = newWidth;
+            }
+
+            // Ensure height fits the text + vspace + buttons
+            if (messageSize.Height == 0)
+            {
+                d.Height = Math.Max (height, 3 + adornmentsThickness.Vertical);
+            }
+            else
+            {
+                string lastLine = messageLabel.TextFormatter.GetLines () [^1];
+
+                // INTENT: Instead of the check against \n or \r\n, how about just Environment.NewLine?
+                d.Height = Math.Max (
+                                     height,
+                                     messageSize.Height
+                                     + (lastLine.EndsWith ("\r\n") || lastLine.EndsWith ('\n') ? 1 : 2)
+                                     + adornmentsThickness.Vertical);
+            }
+
+            d.SetRelativeLayout (d.SuperView?.Frame ?? Application.Top.Frame);
+            d.LayoutSubviews ();
+        }
     }
 }

+ 35 - 40
Terminal.Gui/Views/RadioGroup.cs

@@ -76,6 +76,41 @@ public class RadioGroup : View
         KeyBindings.Add (Key.Space, Command.Accept);
 
         LayoutStarted += RadioGroup_LayoutStarted;
+
+        HighlightOnPress = true;
+
+        MouseClick += RadioGroup_MouseClick;
+    }
+
+    // TODO: Fix InvertColorsOnPress - only highlight the selected item
+
+    private void RadioGroup_MouseClick (object sender, MouseEventEventArgs e)
+    {
+        SetFocus ();
+
+        int boundsX = e.MouseEvent.X;
+        int boundsY = e.MouseEvent.Y;
+
+        int pos = _orientation == Orientation.Horizontal ? boundsX : boundsY;
+
+        int rCount = _orientation == Orientation.Horizontal
+                         ? _horizontal.Last ().pos + _horizontal.Last ().length
+                         : _radioLabels.Count;
+
+        if (pos < rCount)
+        {
+            int c = _orientation == Orientation.Horizontal
+                        ? _horizontal.FindIndex (x => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX)
+                        : boundsY;
+
+            if (c > -1)
+            {
+                _cursor = SelectedItem = c;
+                SetNeedsDisplay ();
+            }
+        }
+
+        e.Handled = true;
     }
 
     /// <summary>
@@ -160,46 +195,6 @@ public class RadioGroup : View
         }
     }
 
-    /// <inheritdoc/>
-    protected internal override bool OnMouseEvent  (MouseEvent me)
-    {
-        if (!me.Flags.HasFlag (MouseFlags.Button1Clicked))
-        {
-            return false;
-        }
-
-        if (!CanFocus)
-        {
-            return false;
-        }
-
-        SetFocus ();
-
-        int boundsX = me.X;
-        int boundsY = me.Y;
-
-        int pos = _orientation == Orientation.Horizontal ? boundsX : boundsY;
-
-        int rCount = _orientation == Orientation.Horizontal
-                         ? _horizontal.Last ().pos + _horizontal.Last ().length
-                         : _radioLabels.Count;
-
-        if (pos < rCount)
-        {
-            int c = _orientation == Orientation.Horizontal
-                        ? _horizontal.FindIndex (x => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX)
-                        : boundsY;
-
-            if (c > -1)
-            {
-                _cursor = SelectedItem = c;
-                SetNeedsDisplay ();
-            }
-        }
-
-        return true;
-    }
-
     /// <inheritdoc/>
     public override void OnDrawContent (Rectangle contentArea)
     {

+ 6 - 0
Terminal.Gui/Views/ScrollView.cs

@@ -410,6 +410,12 @@ public class ScrollView : View
     /// <inheritdoc/>
     protected internal override bool OnMouseEvent  (MouseEvent me)
     {
+        if (!Enabled)
+        {
+            // A disabled view should not eat mouse events
+            return false;
+        }
+
         if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator)
         {
             ScrollDown (1);

+ 172 - 64
UICatalog/Scenarios/Buttons.cs

@@ -9,21 +9,25 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Layout")]
 public class Buttons : Scenario
 {
-    public override void Setup ()
+    public override void Main ()
     {
+        Window main = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+        };
         // Add a label & text field so we can demo IsDefault
         var editLabel = new Label { X = 0, Y = 0, TabStop = true, Text = "TextField (to demo IsDefault):" };
-        Win.Add (editLabel);
+        main.Add (editLabel);
 
         // Add a TextField using Absolute layout. 
         var edit = new TextField { X = 31, Width = 15, HotKey = Key.Y.WithAlt };
-        Win.Add (edit);
+        main.Add (edit);
 
         // This is the default button (IsDefault = true); if user presses ENTER in the TextField
         // the scenario will quit
         var defaultButton = new Button { X = Pos.Center (), Y = Pos.AnchorEnd (1), IsDefault = true, Text = "_Quit" };
         defaultButton.Accept += (s, e) => Application.RequestStop ();
-        Win.Add (defaultButton);
+        main.Add (defaultButton);
 
         var swapButton = new Button { X = 50, Text = "S_wap Default (Absolute Layout)" };
 
@@ -32,7 +36,7 @@ public class Buttons : Scenario
                                   defaultButton.IsDefault = !defaultButton.IsDefault;
                                   swapButton.IsDefault = !swapButton.IsDefault;
                               };
-        Win.Add (swapButton);
+        main.Add (swapButton);
 
         static void DoMessage (Button button, string txt)
         {
@@ -44,7 +48,7 @@ public class Buttons : Scenario
         }
 
         var colorButtonsLabel = new Label { X = 0, Y = Pos.Bottom (editLabel) + 1, Text = "Color Buttons:" };
-        Win.Add (colorButtonsLabel);
+        main.Add (colorButtonsLabel);
 
         View prev = colorButtonsLabel;
 
@@ -57,13 +61,11 @@ public class Buttons : Scenario
             {
                 ColorScheme = colorScheme.Value,
                 X = Pos.Right (prev) + 2,
-
-                //X = x,
                 Y = Pos.Y (colorButtonsLabel),
                 Text = $"_{colorScheme.Key}"
             };
             DoMessage (colorButton, colorButton.Text);
-            Win.Add (colorButton);
+            main.Add (colorButton);
             prev = colorButton;
 
             // BUGBUG: AutoSize is true and the X doesn't change
@@ -72,7 +74,7 @@ public class Buttons : Scenario
 
         Button button;
 
-        Win.Add (
+        main.Add (
                  button = new Button
                  {
                      X = 2,
@@ -84,16 +86,16 @@ public class Buttons : Scenario
         DoMessage (button, button.Text);
 
         // Note the 'N' in 'Newline' will be the hotkey
-        Win.Add (
+        main.Add (
                  button = new Button { X = 2, Y = Pos.Bottom (button) + 1, Text = "a Newline\nin the button" }
                 );
         button.Accept += (s, e) => MessageBox.Query ("Message", "Question?", "Yes", "No");
 
         var textChanger = new Button { X = 2, Y = Pos.Bottom (button) + 1, Text = "Te_xt Changer" };
-        Win.Add (textChanger);
+        main.Add (textChanger);
         textChanger.Accept += (s, e) => textChanger.Text += "!";
 
-        Win.Add (
+        main.Add (
                  button = new Button
                  {
                      X = Pos.Right (textChanger) + 2,
@@ -106,13 +108,13 @@ public class Buttons : Scenario
         {
             X = 2, Y = Pos.Bottom (button) + 1, ColorScheme = Colors.ColorSchemes ["Error"], Text = "Remove this button"
         };
-        Win.Add (removeButton);
+        main.Add (removeButton);
 
         // This in interesting test case because `moveBtn` and below are laid out relative to this one!
         removeButton.Accept += (s, e) =>
                                 {
                                     // Now this throw a InvalidOperationException on the TopologicalSort method as is expected.
-                                    //Win.Remove (removeButton);
+                                    //main.Remove (removeButton);
 
                                     removeButton.Visible = false;
                                 };
@@ -125,7 +127,7 @@ public class Buttons : Scenario
             Height = 5,
             Title = "Computed Layout"
         };
-        Win.Add (computedFrame);
+        main.Add (computedFrame);
 
         // Demonstrates how changing the View.Frame property can move Views
         var moveBtn = new Button
@@ -176,7 +178,7 @@ public class Buttons : Scenario
             Height = 5,
             Title = "Absolute Layout"
         };
-        Win.Add (absoluteFrame);
+        main.Add (absoluteFrame);
 
         // Demonstrates how changing the View.Frame property can move Views
         var moveBtnA = new Button { ColorScheme = Colors.ColorSchemes ["Error"], Text = "Move This Button via Frame" };
@@ -213,7 +215,7 @@ public class Buttons : Scenario
         {
             X = 2, Y = Pos.Bottom (computedFrame) + 1, Text = "Text Alignment (changes the four buttons above): "
         };
-        Win.Add (label);
+        main.Add (label);
 
         var radioGroup = new RadioGroup
         {
@@ -222,7 +224,7 @@ public class Buttons : Scenario
             SelectedItem = 2,
             RadioLabels = new [] { "Left", "Right", "Centered", "Justified" }
         };
-        Win.Add (radioGroup);
+        main.Add (radioGroup);
 
         // Demo changing hotkey
         string MoveHotkey (string txt)
@@ -269,7 +271,7 @@ public class Buttons : Scenario
             Text = mhkb
         };
         moveHotKeyBtn.Accept += (s, e) => { moveHotKeyBtn.Text = MoveHotkey (moveHotKeyBtn.Text); };
-        Win.Add (moveHotKeyBtn);
+        main.Add (moveHotKeyBtn);
 
         var muhkb = " ~  s  gui.cs   master ↑10 = Сохранить";
 
@@ -284,51 +286,157 @@ public class Buttons : Scenario
             Text = muhkb
         };
         moveUnicodeHotKeyBtn.Accept += (s, e) => { moveUnicodeHotKeyBtn.Text = MoveHotkey (moveUnicodeHotKeyBtn.Text); };
-        Win.Add (moveUnicodeHotKeyBtn);
+        main.Add (moveUnicodeHotKeyBtn);
 
         radioGroup.SelectedItemChanged += (s, args) =>
-                                          {
-                                              switch (args.SelectedItem)
-                                              {
-                                                  case 0:
-                                                      moveBtn.TextAlignment = TextAlignment.Left;
-                                                      sizeBtn.TextAlignment = TextAlignment.Left;
-                                                      moveBtnA.TextAlignment = TextAlignment.Left;
-                                                      sizeBtnA.TextAlignment = TextAlignment.Left;
-                                                      moveHotKeyBtn.TextAlignment = TextAlignment.Left;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left;
-
-                                                      break;
-                                                  case 1:
-                                                      moveBtn.TextAlignment = TextAlignment.Right;
-                                                      sizeBtn.TextAlignment = TextAlignment.Right;
-                                                      moveBtnA.TextAlignment = TextAlignment.Right;
-                                                      sizeBtnA.TextAlignment = TextAlignment.Right;
-                                                      moveHotKeyBtn.TextAlignment = TextAlignment.Right;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right;
-
-                                                      break;
-                                                  case 2:
-                                                      moveBtn.TextAlignment = TextAlignment.Centered;
-                                                      sizeBtn.TextAlignment = TextAlignment.Centered;
-                                                      moveBtnA.TextAlignment = TextAlignment.Centered;
-                                                      sizeBtnA.TextAlignment = TextAlignment.Centered;
-                                                      moveHotKeyBtn.TextAlignment = TextAlignment.Centered;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered;
-
-                                                      break;
-                                                  case 3:
-                                                      moveBtn.TextAlignment = TextAlignment.Justified;
-                                                      sizeBtn.TextAlignment = TextAlignment.Justified;
-                                                      moveBtnA.TextAlignment = TextAlignment.Justified;
-                                                      sizeBtnA.TextAlignment = TextAlignment.Justified;
-                                                      moveHotKeyBtn.TextAlignment = TextAlignment.Justified;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified;
-
-                                                      break;
-                                              }
-                                          };
-
-        Top.Ready += (s, e) => radioGroup.Refresh ();
+        {
+            switch (args.SelectedItem)
+            {
+                case 0:
+                    moveBtn.TextAlignment = TextAlignment.Left;
+                    sizeBtn.TextAlignment = TextAlignment.Left;
+                    moveBtnA.TextAlignment = TextAlignment.Left;
+                    sizeBtnA.TextAlignment = TextAlignment.Left;
+                    moveHotKeyBtn.TextAlignment = TextAlignment.Left;
+                    moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left;
+
+                    break;
+                case 1:
+                    moveBtn.TextAlignment = TextAlignment.Right;
+                    sizeBtn.TextAlignment = TextAlignment.Right;
+                    moveBtnA.TextAlignment = TextAlignment.Right;
+                    sizeBtnA.TextAlignment = TextAlignment.Right;
+                    moveHotKeyBtn.TextAlignment = TextAlignment.Right;
+                    moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right;
+
+                    break;
+                case 2:
+                    moveBtn.TextAlignment = TextAlignment.Centered;
+                    sizeBtn.TextAlignment = TextAlignment.Centered;
+                    moveBtnA.TextAlignment = TextAlignment.Centered;
+                    sizeBtnA.TextAlignment = TextAlignment.Centered;
+                    moveHotKeyBtn.TextAlignment = TextAlignment.Centered;
+                    moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered;
+
+                    break;
+                case 3:
+                    moveBtn.TextAlignment = TextAlignment.Justified;
+                    sizeBtn.TextAlignment = TextAlignment.Justified;
+                    moveBtnA.TextAlignment = TextAlignment.Justified;
+                    sizeBtnA.TextAlignment = TextAlignment.Justified;
+                    moveHotKeyBtn.TextAlignment = TextAlignment.Justified;
+                    moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified;
+
+                    break;
+            }
+        };
+
+        label = new Label ()
+        {
+            X = 0,
+            Y = Pos.Bottom (moveUnicodeHotKeyBtn) + 1,
+            Title = "_Numeric Up/Down (press-and-hold):",
+        };
+        var downButton = new Button ()
+        {
+            CanFocus = false,
+            AutoSize = false,
+            X = Pos.Right(label)+1,
+            Y = Pos.Top (label),
+            Height = 1,
+            Width = 1,
+            NoPadding = true,
+            NoDecorations = true,
+            Title = $"{CM.Glyphs.DownArrow}",
+            WantContinuousButtonPressed = true,
+        };
+
+        var numericEdit = new TextField ()
+        {
+            Text = "1966",
+            X = Pos.Right (downButton),
+            Y = Pos.Top (downButton),
+            Width = 5,
+            Height = 1,
+        };
+        var upButton = new Button ()
+        {
+            CanFocus = false,
+            AutoSize = false,
+            X = Pos.Right (numericEdit),
+            Y = Pos.Top (numericEdit),
+            Height = 1,
+            Width = 1,
+            NoPadding = true,
+            NoDecorations = true,
+            Title = $"{CM.Glyphs.UpArrow}",
+            WantContinuousButtonPressed = true,
+        };
+        downButton.Accept += (s, e) =>
+                             {
+                                 numericEdit.Text = $"{int.Parse(numericEdit.Text) - 1}";
+                             };
+        upButton.Accept += (s, e) =>
+                           {
+                               numericEdit.Text = $"{int.Parse (numericEdit.Text) + 1}";
+                           };
+
+        main.Add (label, downButton, numericEdit, upButton);
+
+        label = new Label ()
+        {
+            X = 0,
+            Y = Pos.Bottom (label) + 1,
+            Title = "_No Repeat:",
+        };
+        int noRepeatAcceptCount = 0;
+        var noRepeatButton = new Button ()
+        {
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Title = $"Accept Cou_nt: {noRepeatAcceptCount}",
+            WantContinuousButtonPressed = false,
+        };
+        noRepeatButton.Accept += (s, e) =>
+                                 {
+                                     noRepeatButton.Title = $"Accept Cou_nt: {++noRepeatAcceptCount}";
+                                 };
+        main.Add(label, noRepeatButton);
+
+        label = new Label ()
+        {
+            X = 0,
+            Y = Pos.Bottom (label) + 1,
+            Title = "_Repeat (press-and-hold):",
+        };
+        int acceptCount = 0;
+        var repeatButton = new Button ()
+        {
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Title = $"Accept Co_unt: {acceptCount}",
+            WantContinuousButtonPressed = true,
+        };
+        repeatButton.Accept += (s, e) =>
+                               {
+                                   repeatButton.Title = $"Accept Co_unt: {++acceptCount}";
+                               };
+
+        var enableCB = new CheckBox ()
+        {
+            X = Pos.Right (repeatButton) + 1,
+            Y = Pos.Top (repeatButton),
+            Title = "Enabled",
+            Checked = true,
+        };
+        enableCB.Toggled += (s, e) =>
+                            {
+                                repeatButton.Enabled = !repeatButton.Enabled;
+                            };
+        main.Add(label, repeatButton, enableCB);
+
+        main.Ready += (s, e) => radioGroup.Refresh ();
+        Application.Run (main);
+        main.Dispose ();
     }
 }

+ 0 - 3
UICatalog/Scenarios/CharacterMap.cs

@@ -1,6 +1,3 @@
-#define DRAW_CONTENT
-
-//#define BASE_DRAW_CONTENT
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;

+ 66 - 16
UICatalog/Scenarios/Mouse.cs

@@ -1,4 +1,7 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.Linq;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
@@ -15,19 +18,53 @@ public class Mouse : Scenario
             Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
         };
 
+
+        var filterSlider = new Slider<MouseFlags> ()
+        {
+            Title = "_Filter",
+            X = 0,
+            Y = 0,
+            AutoSize = true,
+            BorderStyle = LineStyle.Single,
+            Type = SliderType.Multiple,
+            Orientation = Orientation.Vertical,
+        };
+        filterSlider.Options = Enum.GetValues (typeof (MouseFlags))
+                                   .Cast<MouseFlags> ()
+                                   .Where (value => !value.ToString ().Contains ("None") && 
+                                                    !value.ToString().Contains("All"))
+                                   .Select (value => new SliderOption<MouseFlags>
+                                   {
+                                       Legend = value.ToString (),
+                                       Data = value,
+                                   })
+                                   .ToList ();
+        for (int i = 0; i < filterSlider.Options.Count; i++)
+        {
+            filterSlider.SetOption (i);
+        }
+        win.Add (filterSlider);
+
+        var clearButton = new Button ()
+        {
+            Title = "_Clear Logs",
+            X = 1,
+            Y = Pos.Bottom (filterSlider) + 1,
+        };
+        win.Add (clearButton);
         Label ml;
         var count = 0;
-        ml = new Label { X = 1, Y = 1, Text = "Mouse: " };
+        ml = new Label { X = Pos.Right(filterSlider), Y = 0, Text = "Mouse: " };
 
         win.Add (ml);
 
         CheckBox cbWantContinuousPresses = new CheckBox ()
         {
-            X = 0,
-            Y = Pos.Bottom(ml) + 1,
-            Title = "_Want Continuous Button Presses",
+            X = Pos.Right (filterSlider),
+            Y = Pos.Bottom (ml) + 1,
+            Title = "_Want Continuous Button Pressed",
         };
-        cbWantContinuousPresses.Toggled += (s,e) =>
+        cbWantContinuousPresses.Toggled += (s, e) =>
         {
             win.WantContinuousButtonPressed = !win.WantContinuousButtonPressed;
         };
@@ -36,10 +73,10 @@ public class Mouse : Scenario
 
         var demo = new MouseDemo ()
         {
-            X = 0,
+            X = Pos.Right (filterSlider),
             Y = Pos.Bottom (cbWantContinuousPresses) + 1,
             Width = 20,
-            Height = 5,
+            Height = 3,
             Text = "Enter/Leave Demo",
             TextAlignment = TextAlignment.Centered,
             VerticalTextAlignment = VerticalTextAlignment.Middle,
@@ -50,15 +87,16 @@ public class Mouse : Scenario
         var label = new Label ()
         {
             Text = "_App Events:",
-            X = 0,
+            X = Pos.Right (filterSlider),
             Y = Pos.Bottom (demo),
         };
+
         List<string> appLogList = new ();
         var appLog = new ListView
         {
             X = Pos.Left (label),
             Y = Pos.Bottom (label),
-            Width = Dim.Percent(49),
+            Width = 50,
             Height = Dim.Fill (),
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
             Source = new ListWrapper (appLogList)
@@ -67,22 +105,27 @@ public class Mouse : Scenario
 
         Application.MouseEvent += (sender, a) =>
                                   {
-                                      ml.Text = $"MouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags} {count}";
-                                      appLogList.Add ($"({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags} {count++}");
-                                      appLog.MoveDown ();
+                                      var i = filterSlider.Options.FindIndex (o => o.Data == a.MouseEvent.Flags);
+                                      if (filterSlider.GetSetOptions().Contains(i))
+                                      {
+                                          ml.Text = $"MouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags} {count}";
+                                          appLogList.Add ($"({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags} {count++}");
+                                          appLog.MoveDown ();
+                                          }
                                   };
 
 
+
         label = new Label ()
         {
             Text = "_Window Events:",
-            X = Pos.Percent(50),
-            Y = Pos.Bottom (demo),
+            X = Pos.Right (appLog)+1,
+                          Y = Pos.Top (label),
         };
         List<string> winLogList = new ();
         var winLog = new ListView
         {
-            X = Pos.Left(label),
+            X = Pos.Left (label),
             Y = Pos.Bottom (label),
             Width = Dim.Percent (50),
             Height = Dim.Fill (),
@@ -90,6 +133,13 @@ public class Mouse : Scenario
             Source = new ListWrapper (winLogList)
         };
         win.Add (label, winLog);
+
+        clearButton.Accept += (s, e) =>
+                              {
+                                  appLogList.Clear ();
+                                  winLogList.Clear ();
+                              };
+
         win.MouseEvent += (sender, a) =>
                           {
                               winLogList.Add ($"MouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags} {count++}");

+ 82 - 0
UnitTests/TestHelpers.cs

@@ -74,6 +74,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
 
         if (AutoInit)
         {
+            // TODO: This Dispose call is here until all unit tests that don't correctly dispose Toplevel's they create are fixed.
             Application.Top?.Dispose ();
             Application.Shutdown ();
 #if DEBUG_IDISPOSABLE
@@ -728,6 +729,87 @@ internal partial class TestHelpers
         return view;
     }
 
+    public static List<Type> GetAllViewClasses ()
+    {
+        return typeof (View).Assembly.GetTypes ()
+                            .Where (
+                                    myType => myType.IsClass
+                                              && !myType.IsAbstract
+                                              && myType.IsPublic
+                                              && myType.IsSubclassOf (typeof (View))
+                                   )
+                            .ToList ();
+    }
+
+    public static View CreateViewFromType (Type type, ConstructorInfo ctor)
+    {
+        View viewType = null;
+
+        if (type.IsGenericType && type.IsTypeDefinition)
+        {
+            List<Type> gTypes = new ();
+
+            foreach (Type args in type.GetGenericArguments ())
+            {
+                gTypes.Add (typeof (object));
+            }
+
+            type = type.MakeGenericType (gTypes.ToArray ());
+
+            Assert.IsType (type, (View)Activator.CreateInstance (type));
+        }
+        else
+        {
+            ParameterInfo [] paramsInfo = ctor.GetParameters ();
+            Type paramType;
+            List<object> pTypes = new ();
+
+            if (type.IsGenericType)
+            {
+                foreach (Type args in type.GetGenericArguments ())
+                {
+                    paramType = args.GetType ();
+
+                    if (args.Name == "T")
+                    {
+                        pTypes.Add (typeof (object));
+                    }
+                    else
+                    {
+                        AddArguments (paramType, pTypes);
+                    }
+                }
+            }
+
+            foreach (ParameterInfo p in paramsInfo)
+            {
+                paramType = p.ParameterType;
+
+                if (p.HasDefaultValue)
+                {
+                    pTypes.Add (p.DefaultValue);
+                }
+                else
+                {
+                    AddArguments (paramType, pTypes);
+                }
+            }
+
+            if (type.IsGenericType && !type.IsTypeDefinition)
+            {
+                viewType = (View)Activator.CreateInstance (type);
+                Assert.IsType (type, viewType);
+            }
+            else
+            {
+                viewType = (View)ctor.Invoke (pTypes.ToArray ());
+                Assert.IsType (type, viewType);
+            }
+        }
+
+        return viewType;
+    }
+
     [GeneratedRegex ("^\\s+", RegexOptions.Multiline)]
     private static partial Regex LeadingWhitespaceRegEx ();
 

+ 350 - 2
UnitTests/View/MouseTests.cs

@@ -75,8 +75,6 @@ public class MouseTests (ITestOutputHelper output)
         Assert.Equal (new Point (4, 4), testView.Frame.Location);
         Application.OnMouseEvent (new (new () { X = xy, Y = xy, Flags = MouseFlags.Button1Pressed }));
 
-        Assert.False (Application.MouseGrabView is { } && (Application.MouseGrabView != testView.Margin && Application.MouseGrabView != testView.Border));
-
         Application.OnMouseEvent (new (new () { X = xy + 1, Y = xy + 1, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }));
 
         Assert.Equal (expectedMoved, new Point (5, 5) == testView.Frame.Location);
@@ -94,4 +92,354 @@ public class MouseTests (ITestOutputHelper output)
         view.OnMouseEvent (new MouseEvent () { Flags = mouseFlags });
         Assert.Equal (mouseFlagsFromEvent, expectedMouseFlagsFromEvent);
     }
+
+    public static TheoryData<View, string> AllViews => TestHelpers.GetAllViewsTheoryData ();
+
+
+    [Theory]
+    [MemberData (nameof (AllViews))]
+
+    public void AllViews_Enter_Leave_Events (View view, string viewName)
+    {
+        if (view == null)
+        {
+            output.WriteLine ($"Ignoring {viewName} - It's a Generic");
+            return;
+        }
+
+        if (!view.CanFocus)
+        {
+            output.WriteLine ($"Ignoring {viewName} - It can't focus.");
+
+            return;
+        }
+
+        if (view is Toplevel && ((Toplevel)view).Modal)
+        {
+            output.WriteLine ($"Ignoring {viewName} - It's a Modal Toplevel");
+
+            return;
+        }
+
+        Application.Init (new FakeDriver ());
+
+        Toplevel top = new ()
+        {
+            Height = 10,
+            Width = 10
+        };
+
+        View otherView = new ()
+        {
+            X = 0, Y = 0,
+            Height = 1,
+            Width = 1,
+            CanFocus = true,
+        };
+
+        view.AutoSize = false;
+        view.X = Pos.Right (otherView);
+        view.Y = 0;
+        view.Width = 10;
+        view.Height = 1;
+
+        var nEnter = 0;
+        var nLeave = 0;
+
+        view.Enter += (s, e) => nEnter++;
+        view.Leave += (s, e) => nLeave++;
+
+        top.Add (view, otherView);
+        Application.Begin (top);
+
+        // Start with the focus on our test view
+        view.SetFocus ();
+
+        Assert.Equal (1, nEnter);
+        Assert.Equal (0, nLeave);
+
+        // Use keyboard to navigate to next view (otherView). 
+        if (view is TextView)
+        {
+            top.NewKeyDownEvent (Key.Tab.WithCtrl);
+        }
+        else if (view is DatePicker)
+        {
+            for (var i = 0; i < 4; i++)
+            {
+                top.NewKeyDownEvent (Key.Tab.WithCtrl);
+            }
+        }
+        else
+        {
+            top.NewKeyDownEvent (Key.Tab);
+        }
+
+        Assert.Equal (1, nEnter);
+        Assert.Equal (1, nLeave);
+
+        top.NewKeyDownEvent (Key.Tab);
+
+        Assert.Equal (2, nEnter);
+        Assert.Equal (1, nLeave);
+
+        top.Dispose ();
+        Application.Shutdown ();
+    }
+
+
+    [Theory]
+    [MemberData (nameof (AllViews))]
+
+    public void AllViews_Enter_Leave_Events_Visible_False (View view, string viewName)
+    {
+        if (view == null)
+        {
+            output.WriteLine ($"Ignoring {viewName} - It's a Generic");
+            return;
+        }
+
+        if (!view.CanFocus)
+        {
+            output.WriteLine ($"Ignoring {viewName} - It can't focus.");
+
+            return;
+        }
+
+        if (view is Toplevel && ((Toplevel)view).Modal)
+        {
+            output.WriteLine ($"Ignoring {viewName} - It's a Modal Toplevel");
+
+            return;
+        }
+
+        Application.Init (new FakeDriver ());
+
+        Toplevel top = new ()
+        {
+            Height = 10,
+            Width = 10
+        };
+
+        View otherView = new ()
+        {
+            X = 0, Y = 0,
+            Height = 1,
+            Width = 1,
+            CanFocus = true,
+        };
+
+        view.Visible = false;
+        view.AutoSize = false;
+        view.X = Pos.Right (otherView);
+        view.Y = 0;
+        view.Width = 10;
+        view.Height = 1;
+
+        var nEnter = 0;
+        var nLeave = 0;
+
+        view.Enter += (s, e) => nEnter++;
+        view.Leave += (s, e) => nLeave++;
+
+        top.Add (view, otherView);
+        Application.Begin (top);
+
+        // Start with the focus on our test view
+        view.SetFocus ();
+
+        Assert.Equal (0, nEnter);
+        Assert.Equal (0, nLeave);
+
+        // Use keyboard to navigate to next view (otherView). 
+        if (view is TextView)
+        {
+            top.NewKeyDownEvent (Key.Tab.WithCtrl);
+        }
+        else if (view is DatePicker)
+        {
+            for (var i = 0; i < 4; i++)
+            {
+                top.NewKeyDownEvent (Key.Tab.WithCtrl);
+            }
+        }
+        else
+        {
+            top.NewKeyDownEvent (Key.Tab);
+        }
+
+        Assert.Equal (0, nEnter);
+        Assert.Equal (0, nLeave);
+
+        top.NewKeyDownEvent (Key.Tab);
+
+        Assert.Equal (0, nEnter);
+        Assert.Equal (0, nLeave);
+
+        top.Dispose ();
+        Application.Shutdown ();
+    }
+
+    [Theory]
+    [MemberData (nameof (AllViews))]
+    public void AllViews_OnMouseEvent_Enabled_False_Does_Not_Set_Handled (View view, string viewName)
+    {
+        if (view == null)
+        {
+            output.WriteLine ($"Ignoring {viewName} - It's a Generic");
+            return;
+        }
+
+        view.Enabled = false;
+        var me = new MouseEvent ();
+        view.OnMouseEvent (me);
+        Assert.False (me.Handled);
+        view.Dispose ();
+    }
+
+    [Theory]
+    [MemberData (nameof (AllViews))]
+    public void AllViews_OnMouseClick_Enabled_False_Does_Not_Set_Handled (View view, string viewName)
+    {
+        if (view == null)
+        {
+            output.WriteLine ($"Ignoring {viewName} - It's a Generic");
+            return;
+        }
+
+        view.Enabled = false;
+        var me = new MouseEvent ()
+        {
+            Flags = MouseFlags.Button1Clicked
+        };
+        view.OnMouseEvent (me);
+        Assert.False (me.Handled);
+        view.Dispose ();
+    }
+
+    [Theory]
+    [InlineData (MouseFlags.Button1Pressed, MouseFlags.Button1Released, MouseFlags.Button1Clicked)]
+    [InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released, MouseFlags.Button2Clicked)]
+    [InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released, MouseFlags.Button3Clicked)]
+    [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)]
+    public void WantContinuousButtonPressed_False_Button1Press_Release_DoesNotClick (MouseFlags pressed, MouseFlags released, MouseFlags clicked)
+    {
+        var me = new MouseEvent ()
+        {
+            Flags = pressed
+        };
+
+        var view = new View ()
+        {
+            WantContinuousButtonPressed = false
+        };
+
+        var clickedCount = 0;
+
+        view.MouseClick += (s, e) => clickedCount++;
+
+
+        view.OnMouseEvent (me);
+        Assert.Equal (0, clickedCount);
+        me.Handled = false;
+
+        view.OnMouseEvent (me);
+        Assert.Equal (0, clickedCount);
+        me.Handled = false;
+
+        me.Flags = released;
+        view.OnMouseEvent (me);
+        Assert.Equal (0, clickedCount);
+        me.Handled = false;
+
+        me.Flags =clicked;
+        view.OnMouseEvent (me);
+        Assert.Equal (1, clickedCount);
+
+        view.Dispose ();
+    }
+
+    [Theory]
+    [InlineData (MouseFlags.Button1Pressed, MouseFlags.Button1Released, MouseFlags.Button1Clicked)]
+    [InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released, MouseFlags.Button2Clicked)]
+    [InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released, MouseFlags.Button3Clicked)]
+    [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)]
+    public void WantContinuousButtonPressed_True_Button1Press_Release_Clicks_Repeatedly (MouseFlags pressed, MouseFlags released, MouseFlags clicked)
+    {
+        var me = new MouseEvent ()
+        {
+            Flags = pressed
+        };
+
+        var view = new View ()
+        {
+            WantContinuousButtonPressed = true
+        };
+
+        var clickedCount = 0;
+
+        view.MouseClick += (s, e) => clickedCount++;
+
+        view.OnMouseEvent (me);
+        Assert.Equal (0, clickedCount);
+        me.Handled = false;
+
+        view.OnMouseEvent (me);
+        Assert.Equal (1, clickedCount);
+        me.Handled = false;
+
+        me.Flags = released;
+        view.OnMouseEvent (me);
+        Assert.Equal (2, clickedCount);
+        me.Handled = false;
+
+        me.Flags = clicked;
+        view.OnMouseEvent (me);
+        Assert.Equal (2, clickedCount);
+
+        view.Dispose ();
+    }
+
+    [Fact]
+    public void HighlightOnPress_False_No_Highlights ()
+    {
+        var view = new View ()
+        {
+            HighlightOnPress = false
+        };
+        view.ColorScheme = new ColorScheme (new Attribute (ColorName.Red, ColorName.Blue));
+        ColorScheme originalColorScheme = view.ColorScheme;
+
+        var me = new MouseEvent ()
+        {
+            Flags = MouseFlags.Button1Pressed
+        };
+
+        view.OnMouseEvent (me);
+        Assert.Equal (originalColorScheme, view.ColorScheme);
+
+        view.Dispose ();
+    }
+
+
+    [Fact]
+    public void HighlightOnPress_False_Highlights ()
+    {
+        var view = new View ()
+        {
+            HighlightOnPress = true
+        };
+        view.ColorScheme = new ColorScheme (new Attribute (ColorName.Red, ColorName.Blue));
+        ColorScheme originalColorScheme = view.ColorScheme;
+
+        var me = new MouseEvent ()
+        {
+            Flags = MouseFlags.Button1Pressed
+        };
+
+        view.OnMouseEvent (me);
+        Assert.NotEqual (originalColorScheme, view.ColorScheme);
+
+        view.Dispose ();
+    }
 }

+ 5 - 85
UnitTests/Views/AllViewsTests.cs

@@ -58,14 +58,14 @@ public class AllViewsTests (ITestOutputHelper output)
     [Fact]
     public void AllViews_Enter_Leave_Events ()
     {
-        foreach (Type type in GetAllViewClasses ())
+        foreach (Type type in TestHelpers.GetAllViewClasses ())
         {
             output.WriteLine ($"Testing {type.Name}");
 
             Application.Init (new FakeDriver ());
 
             Toplevel top = new ();
-            View vType = CreateViewFromType (type, type.GetConstructor (Array.Empty<Type> ()));
+            View vType = TestHelpers.CreateViewFromType (type, type.GetConstructor (Array.Empty<Type> ()));
 
             if (vType == null)
             {
@@ -139,12 +139,13 @@ public class AllViewsTests (ITestOutputHelper output)
         }
     }
 
+
     [Fact]
     public void AllViews_Tests_All_Constructors ()
     {
         Application.Init (new FakeDriver ());
 
-        foreach (Type type in GetAllViewClasses ())
+        foreach (Type type in TestHelpers.GetAllViewClasses ())
         {
             Assert.True (Test_All_Constructors_Of_Type (type));
         }
@@ -152,18 +153,6 @@ public class AllViewsTests (ITestOutputHelper output)
         Application.Shutdown ();
     }
 
-    public static List<Type> GetAllViewClasses ()
-    {
-        return typeof (View).Assembly.GetTypes ()
-                            .Where (
-                                    myType => myType.IsClass
-                                              && !myType.IsAbstract
-                                              && myType.IsPublic
-                                              && myType.IsSubclassOf (typeof (View))
-                                   )
-                            .ToList ();
-    }
-
     //[Fact]
     //public void AllViews_HotKey_Works ()
     //{
@@ -180,7 +169,7 @@ public class AllViewsTests (ITestOutputHelper output)
     {
         foreach (ConstructorInfo ctor in type.GetConstructors ())
         {
-            View view = CreateViewFromType (type, ctor);
+            View view = TestHelpers.CreateViewFromType (type, ctor);
 
             if (view != null)
             {
@@ -243,73 +232,4 @@ public class AllViewsTests (ITestOutputHelper output)
             pTypes.Add (null);
         }
     }
-
-    private static View CreateViewFromType (Type type, ConstructorInfo ctor)
-    {
-        View viewType = null;
-
-        if (type.IsGenericType && type.IsTypeDefinition)
-        {
-            List<Type> gTypes = new ();
-
-            foreach (Type args in type.GetGenericArguments ())
-            {
-                gTypes.Add (typeof (object));
-            }
-
-            type = type.MakeGenericType (gTypes.ToArray ());
-
-            Assert.IsType (type, (View)Activator.CreateInstance (type));
-        }
-        else
-        {
-            ParameterInfo [] paramsInfo = ctor.GetParameters ();
-            Type paramType;
-            List<object> pTypes = new ();
-
-            if (type.IsGenericType)
-            {
-                foreach (Type args in type.GetGenericArguments ())
-                {
-                    paramType = args.GetType ();
-
-                    if (args.Name == "T")
-                    {
-                        pTypes.Add (typeof (object));
-                    }
-                    else
-                    {
-                        AddArguments (paramType, pTypes);
-                    }
-                }
-            }
-
-            foreach (ParameterInfo p in paramsInfo)
-            {
-                paramType = p.ParameterType;
-
-                if (p.HasDefaultValue)
-                {
-                    pTypes.Add (p.DefaultValue);
-                }
-                else
-                {
-                    AddArguments (paramType, pTypes);
-                }
-            }
-
-            if (type.IsGenericType && !type.IsTypeDefinition)
-            {
-                viewType = (View)Activator.CreateInstance (type);
-                Assert.IsType (type, viewType);
-            }
-            else
-            {
-                viewType = (View)ctor.Invoke (pTypes.ToArray ());
-                Assert.IsType (type, viewType);
-            }
-        }
-
-        return viewType;
-    }
 }

+ 88 - 142
UnitTests/Views/ButtonTests.cs

@@ -3,11 +3,8 @@ using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewsTests;
 
-public class ButtonTests
+public class ButtonTests (ITestOutputHelper output)
 {
-    private readonly ITestOutputHelper _output;
-    public ButtonTests (ITestOutputHelper output) { _output = output; }
-
     // Test that Title and Text are the same
     [Fact]
     public void Text_Mirrors_Title ()
@@ -15,10 +12,11 @@ public class ButtonTests
         var view = new Button ();
         view.Title = "Hello";
         Assert.Equal ("Hello", view.Title);
-        Assert.Equal ($"Hello", view.TitleTextFormatter.Text);
+        Assert.Equal ("Hello", view.TitleTextFormatter.Text);
 
         Assert.Equal ("Hello", view.Text);
         Assert.Equal ($"{CM.Glyphs.LeftBracket} Hello {CM.Glyphs.RightBracket}", view.TextFormatter.Text);
+        view.Dispose ();
     }
 
     [Fact]
@@ -30,7 +28,8 @@ public class ButtonTests
         Assert.Equal ($"{CM.Glyphs.LeftBracket} Hello {CM.Glyphs.RightBracket}", view.TextFormatter.Text);
 
         Assert.Equal ("Hello", view.Title);
-        Assert.Equal ($"Hello", view.TitleTextFormatter.Text);
+        Assert.Equal ("Hello", view.TitleTextFormatter.Text);
+        view.Dispose ();
     }
 
     // BUGBUG: This test is NOT a unit test and needs to be broken apart into 
@@ -105,7 +104,7 @@ public class ButtonTests
         tab.Add (ckbMatchWholeWord);
 
         var tabView = new TabView { Width = Dim.Fill (), Height = Dim.Fill () };
-        tabView.AddTab (new Tab { DisplayText = "Find", View = tab }, true);
+        tabView.AddTab (new () { DisplayText = "Find", View = tab }, true);
 
         var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () };
 
@@ -119,11 +118,11 @@ public class ButtonTests
         Application.Begin (top);
         ((FakeDriver)Application.Driver).SetBufferSize (54, 11);
 
-        Assert.Equal (new Rectangle (0, 0, 54, 11), win.Frame);
-        Assert.Equal (new Rectangle (0, 0, 52, 9), tabView.Frame);
-        Assert.Equal (new Rectangle (0, 0, 50, 7), tab.Frame);
-        Assert.Equal (new Rectangle (0, 1, 8, 1), view.Frame);
-        Assert.Equal (new Rectangle (9, 1, 20, 1), txtToFind.Frame);
+        Assert.Equal (new (0, 0, 54, 11), win.Frame);
+        Assert.Equal (new (0, 0, 52, 9), tabView.Frame);
+        Assert.Equal (new (0, 0, 50, 7), tab.Frame);
+        Assert.Equal (new (0, 1, 8, 1), view.Frame);
+        Assert.Equal (new (9, 1, 20, 1), txtToFind.Frame);
 
         Assert.Equal (0, txtToFind.ScrollOffset);
         Assert.Equal (16, txtToFind.CursorPosition);
@@ -131,19 +130,12 @@ public class ButtonTests
         Assert.Equal (new (30, 1, 20, 1), btnFindNext.Frame);
         Assert.Equal (new (30, 2, 20, 1), btnFindPrevious.Frame);
         Assert.Equal (new (30, 4, 20, 1), btnCancel.Frame);
-//        Assert.Equal (new (0, 3, 12, 1), ckbMatchCase.Frame);
-//        Assert.Equal (new (0, 4, 18, 1), ckbMatchWholeWord.Frame);
+
+        //        Assert.Equal (new (0, 3, 12, 1), ckbMatchCase.Frame);
+        //        Assert.Equal (new (0, 4, 18, 1), ckbMatchWholeWord.Frame);
 
         var btn1 =
-            $"{
-                CM.Glyphs.LeftBracket
-            }{
-                CM.Glyphs.LeftDefaultIndicator
-            } Find Next {
-                CM.Glyphs.RightDefaultIndicator
-            }{
-                CM.Glyphs.RightBracket
-            }";
+            $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Find Next {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}";
         var btn2 = $"{CM.Glyphs.LeftBracket} Find Previous {CM.Glyphs.RightBracket}";
         var btn3 = $"{CM.Glyphs.LeftBracket} Cancel {CM.Glyphs.RightBracket}";
 
@@ -153,25 +145,16 @@ public class ButtonTests
 ││Find│                                              │
 ││    ╰─────────────────────────────────────────────╮│
 ││                                                  ││
-││   Find: Testing buttons.       {
-    btn1
-}   ││
-││                               {
-    btn2
-}  ││
-││{
-    CM.Glyphs.Checked
-} Match case                                      ││
-││{
-    CM.Glyphs.UnChecked
-} Match whole word                 {
-    btn3
-}     ││
+││   Find: Testing buttons.       {btn1}   ││
+││                               {btn2}  ││
+││{CM.Glyphs.Checked} Match case                                      ││
+││{CM.Glyphs.UnChecked} Match whole word                 {btn3}     ││
 │└──────────────────────────────────────────────────┘│
 └────────────────────────────────────────────────────┘
 ";
 
-        TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+        view.Dispose ();
     }
 
     [Fact]
@@ -197,14 +180,12 @@ public class ButtonTests
         var expected = @$"
 ┌────────────────────────────┐
 │                            │
-│            {
-    btnTxt
-}│
+│            {btnTxt}│
 │                            │
 └────────────────────────────┘
 ";
 
-        TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
         Assert.True (btn.AutoSize);
         btn.Text = "Say Hello 你 changed";
@@ -215,14 +196,13 @@ public class ButtonTests
         expected = @$"
 ┌────────────────────────────┐
 │                            │
-│    {
-    btnTxt
-}│
+│    {btnTxt}│
 │                            │
 └────────────────────────────┘
 ";
 
-        TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+        top.Dispose ();
     }
 
     [Fact]
@@ -244,16 +224,12 @@ public class ButtonTests
         var expected = @$"
 ┌────────────────────────────┐
 │                            │
-│      {
-    CM.Glyphs.LeftBracket
-} Say Hello 你 {
-    CM.Glyphs.RightBracket
-}      │
+│      {CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}      │
 │                            │
 └────────────────────────────┘
 ";
 
-        TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
         Assert.True (btn.AutoSize);
         btn.Text = "Say Hello 你 changed";
@@ -263,16 +239,13 @@ public class ButtonTests
         expected = @$"
 ┌────────────────────────────┐
 │                            │
-│  {
-    CM.Glyphs.LeftBracket
-} Say Hello 你 changed {
-    CM.Glyphs.RightBracket
-}  │
+│  {CM.Glyphs.LeftBracket} Say Hello 你 changed {CM.Glyphs.RightBracket}  │
 │                            │
 └────────────────────────────┘
 ";
 
-        TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+        top.Dispose ();
     }
 
     [Fact]
@@ -284,7 +257,7 @@ public class ButtonTests
         var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () };
         win.Add (btn);
         var top = new Toplevel ();
-       top.Add (win);
+        top.Add (win);
 
         Assert.True (btn.AutoSize);
 
@@ -298,16 +271,13 @@ public class ButtonTests
         var expected = @$"
 ┌────────────────────────────┐
 │                            │
-│      {
-    CM.Glyphs.LeftBracket
-} Say Hello 你 {
-    CM.Glyphs.RightBracket
-}      │
+│      {CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}      │
 │                            │
 └────────────────────────────┘
 ";
 
-        TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+        top.Dispose ();
     }
 
     [Fact]
@@ -347,22 +317,11 @@ public class ButtonTests
 
         TestHelpers.AssertDriverContentsWithFrameAre (
                                                       @$"
-    {
-        CM.Glyphs.LeftBracket
-    } {
-        btn1.Text
-    } {
-        CM.Glyphs.RightBracket
-    }
-   {
-       CM.Glyphs.LeftBracket
-   } {
-       btn2.Text
-   } {
-       CM.Glyphs.RightBracket
-   }",
-                                                      _output
+    {CM.Glyphs.LeftBracket} {btn1.Text} {CM.Glyphs.RightBracket}
+   {CM.Glyphs.LeftBracket} {btn2.Text} {CM.Glyphs.RightBracket}",
+                                                      output
                                                      );
+        top.Dispose ();
     }
 
     [Fact]
@@ -394,10 +353,10 @@ public class ButtonTests
         Assert.Same (btn, sender);
         Assert.Equal (KeyCode.Y, args.OldKey);
         Assert.Equal (KeyCode.R, args.NewKey);
+        btn.Dispose ();
     }
 
     [Fact]
-    [AutoInitShutdown]
     public void Button_HotKeyChanged_EventFires_WithNone ()
     {
         var btn = new Button ();
@@ -415,6 +374,7 @@ public class ButtonTests
         Assert.Same (btn, sender);
         Assert.Equal (KeyCode.Null, args.OldKey);
         Assert.Equal (KeyCode.R, args.NewKey);
+        btn.Dispose ();
     }
 
     [Fact]
@@ -431,15 +391,15 @@ public class ButtonTests
         Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
         Assert.Equal ('_', btn.HotKeySpecifier.Value);
         Assert.True (btn.CanFocus);
-        Assert.Equal (new Rectangle (0, 0, 4, 1), btn.Bounds);
-        Assert.Equal (new Rectangle (0, 0, 4, 1), btn.Frame);
+        Assert.Equal (new (0, 0, 4, 1), btn.Bounds);
+        Assert.Equal (new (0, 0, 4, 1), btn.Frame);
         Assert.Equal ($"{CM.Glyphs.LeftBracket}  {CM.Glyphs.RightBracket}", btn.TextFormatter.Text);
         Assert.False (btn.IsDefault);
         Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
         Assert.Equal ('_', btn.HotKeySpecifier.Value);
         Assert.True (btn.CanFocus);
-        Assert.Equal (new Rectangle (0, 0, 4, 1), btn.Bounds);
-        Assert.Equal (new Rectangle (0, 0, 4, 1), btn.Frame);
+        Assert.Equal (new (0, 0, 4, 1), btn.Bounds);
+        Assert.Equal (new (0, 0, 4, 1), btn.Frame);
 
         Assert.Equal (string.Empty, btn.Title);
         Assert.Equal (KeyCode.Null, btn.HotKey);
@@ -447,15 +407,12 @@ public class ButtonTests
         btn.Draw ();
 
         var expected = @$"
-{
-    CM.Glyphs.LeftBracket
-}  {
-    CM.Glyphs.RightBracket
-}
+{CM.Glyphs.LeftBracket}  {CM.Glyphs.RightBracket}
 ";
-        TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+        btn.Dispose ();
 
-        btn = new Button { Text = "_Test", IsDefault = true };
+        btn = new () { Text = "_Test", IsDefault = true };
         btn.BeginInit ();
         btn.EndInit ();
         Assert.Equal ('_', btn.HotKeySpecifier.Value);
@@ -463,40 +420,26 @@ public class ButtonTests
         Assert.Equal ("_Test", btn.Text);
 
         Assert.Equal (
-                      $"{
-                          CM.Glyphs.LeftBracket
-                      }{
-                          CM.Glyphs.LeftDefaultIndicator
-                      } Test {
-                          CM.Glyphs.RightDefaultIndicator
-                      }{
-                          CM.Glyphs.RightBracket
-                      }",
+                      $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Test {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}",
                       btn.TextFormatter.Format ()
                      );
         Assert.True (btn.IsDefault);
         Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
         Assert.True (btn.CanFocus);
-        Assert.Equal (new Rectangle (0, 0, 10, 1), btn.Bounds);
-        Assert.Equal (new Rectangle (0, 0, 10, 1), btn.Frame);
+        Assert.Equal (new (0, 0, 10, 1), btn.Bounds);
+        Assert.Equal (new (0, 0, 10, 1), btn.Frame);
         Assert.Equal (KeyCode.T, btn.HotKey);
 
-        btn = new Button { X = 1, Y = 2, Text = "_abc", IsDefault = true };
+        btn.Dispose ();
+
+        btn = new () { X = 1, Y = 2, Text = "_abc", IsDefault = true };
         btn.BeginInit ();
         btn.EndInit ();
         Assert.Equal ("_abc", btn.Text);
         Assert.Equal (Key.A, btn.HotKey);
 
         Assert.Equal (
-                      $"{
-                          CM.Glyphs.LeftBracket
-                      }{
-                          CM.Glyphs.LeftDefaultIndicator
-                      } abc {
-                          CM.Glyphs.RightDefaultIndicator
-                      }{
-                          CM.Glyphs.RightBracket
-                      }",
+                      $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} abc {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}",
                       btn.TextFormatter.Format ()
                      );
         Assert.True (btn.IsDefault);
@@ -508,20 +451,13 @@ public class ButtonTests
         btn.Draw ();
 
         expected = @$"
- {
-     CM.Glyphs.LeftBracket
- }{
-     CM.Glyphs.LeftDefaultIndicator
- } abc {
-     CM.Glyphs.RightDefaultIndicator
- }{
-     CM.Glyphs.RightBracket
- }
+ {CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} abc {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}
 ";
-        TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
-        Assert.Equal (new Rectangle (0, 0, 9, 1), btn.Bounds);
-        Assert.Equal (new Rectangle (1, 2, 9, 1), btn.Frame);
+        Assert.Equal (new (0, 0, 9, 1), btn.Bounds);
+        Assert.Equal (new (1, 2, 9, 1), btn.Frame);
+        btn.Dispose ();
     }
 
     [Fact]
@@ -547,6 +483,7 @@ public class ButtonTests
         btn.HotKey = KeyCode.E;
         Assert.True (btn.NewKeyDownEvent (Key.E.WithAlt));
         Assert.True (clicked);
+        top.Dispose ();
     }
 
     /// <summary>
@@ -571,7 +508,7 @@ public class ButtonTests
         Application.Begin (top);
 
         // default keybinding is Space which results in keypress
-        Application.OnKeyDown (new Key ((KeyCode)' '));
+        Application.OnKeyDown (new ((KeyCode)' '));
         Assert.Equal (1, pressed);
 
         // remove the default keybinding (Space)
@@ -579,7 +516,7 @@ public class ButtonTests
         btn.KeyBindings.Clear (Command.Accept);
 
         // After clearing the default keystroke the Space button no longer does anything for the Button
-        Application.OnKeyDown (new Key ((KeyCode)' '));
+        Application.OnKeyDown (new ((KeyCode)' '));
         Assert.Equal (1, pressed);
 
         // Set a new binding of b for the click (Accept) event
@@ -601,6 +538,7 @@ public class ButtonTests
         // now pressing Shift-Alt-B should NOT call the button click event
         Application.OnKeyDown (Key.B.WithAlt.WithShift);
         Assert.Equal (2, pressed);
+        top.Dispose ();
     }
 
     [Fact]
@@ -659,7 +597,7 @@ public class ButtonTests
         Assert.True (clicked);
         clicked = false;
 
-        Assert.True (btn.NewKeyDownEvent (new Key ((KeyCode)'T')));
+        Assert.True (btn.NewKeyDownEvent (new ((KeyCode)'T')));
         Assert.True (clicked);
         clicked = false;
 
@@ -668,6 +606,8 @@ public class ButtonTests
         Assert.True (btn.NewKeyDownEvent (btn.HotKey));
         Assert.True (clicked);
         clicked = false;
+
+        top.Dispose ();
     }
 
     [Fact]
@@ -680,8 +620,10 @@ public class ButtonTests
         button.InvokeCommand (Command.HotKey);
 
         Assert.True (accepted);
+        button.Dispose ();
 
         return;
+
         void ButtonOnAccept (object sender, CancelEventArgs e) { accepted = true; }
     }
 
@@ -693,17 +635,21 @@ public class ButtonTests
 
         button.Accept += ButtonAccept;
 
-        var ret = button.InvokeCommand (Command.Accept);
+        bool? ret = button.InvokeCommand (Command.Accept);
         Assert.True (ret);
         Assert.True (acceptInvoked);
 
+        button.Dispose ();
+
         return;
+
         void ButtonAccept (object sender, CancelEventArgs e)
         {
             acceptInvoked = true;
             e.Cancel = true;
         }
     }
+
     [Fact]
     public void Setting_Empty_Text_Sets_HoKey_To_KeyNull ()
     {
@@ -726,6 +672,7 @@ public class ButtonTests
         btn.Text = "Te_st";
         Assert.Equal ("Te_st", btn.Text);
         Assert.Equal (KeyCode.S, btn.HotKey);
+        super.Dispose ();
     }
 
     [Fact]
@@ -740,6 +687,7 @@ public class ButtonTests
 
         // with cast
         Assert.Equal ("heyb", ((Button)b).Text);
+        b.Dispose ();
     }
 
     [Fact]
@@ -760,21 +708,20 @@ public class ButtonTests
         Assert.True (btn.IsInitialized);
         Assert.Equal ("Say Hello 你", btn.Text);
         Assert.Equal ($"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}", btn.TextFormatter.Text);
-        Assert.Equal (new Rectangle (0, 0, 16, 1), btn.Bounds);
+        Assert.Equal (new (0, 0, 16, 1), btn.Bounds);
         var btnTxt = $"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}";
 
         var expected = @$"
 ┌────────────────────────────┐
 │                            │
-│      {
-    btnTxt
-}      │
+│      {btnTxt}      │
 │                            │
 └────────────────────────────┘
 ";
 
-        Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
-        Assert.Equal (new Rectangle (0, 0, 30, 5), pos);
+        Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+        Assert.Equal (new (0, 0, 30, 5), pos);
+        top.Dispose ();
     }
 
     [Fact]
@@ -795,20 +742,19 @@ public class ButtonTests
         Assert.True (btn.IsInitialized);
         Assert.Equal ("Say Hello 你", btn.Text);
         Assert.Equal ($"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}", btn.TextFormatter.Text);
-        Assert.Equal (new Rectangle (0, 0, 16, 1), btn.Bounds);
+        Assert.Equal (new (0, 0, 16, 1), btn.Bounds);
         var btnTxt = $"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}";
 
         var expected = @$"
 ┌────────────────────────────┐
 │                            │
-│      {
-    btnTxt
-}      │
+│      {btnTxt}      │
 │                            │
 └────────────────────────────┘
 ";
 
-        Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
-        Assert.Equal (new Rectangle (0, 0, 30, 5), pos);
+        Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+        Assert.Equal (new (0, 0, 30, 5), pos);
+        top.Dispose ();
     }
-}
+}