Tig 10 bulan lalu
induk
melakukan
12e3346c01

+ 108 - 33
Terminal.Gui/Application/Application.Mouse.cs

@@ -1,4 +1,10 @@
 #nullable enable
+<<<<<<< Updated upstream
+=======
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+>>>>>>> Stashed changes
 namespace Terminal.Gui;
 
 public static partial class Application // Mouse handling
@@ -116,8 +122,8 @@ public static partial class Application // Mouse handling
         UnGrabbedMouse?.Invoke (view, new (view));
     }
 
-    // Used by OnMouseEvent to track the last view that was clicked on.
-    internal static View? MouseEnteredView { get; set; }
+    // Used by OnMouseEvent to suppport MouseEnter and MouseLeave events
+    internal static List<View?> ViewsUnderMouse { get; } = new ();
 
     /// <summary>Event fired when a mouse move or click occurs. Coordinates are screen relative.</summary>
     /// <remarks>
@@ -139,17 +145,32 @@ public static partial class Application // Mouse handling
             return;
         }
 
+<<<<<<< Updated upstream
         var view = View.FindDeepestView (Current, mouseEvent.Position);
+=======
+        Stack<View?> viewsUnderMouse = View.GetViewsUnderMouse (mouseEvent.Position);
+
+        View? deepestViewUnderMouse = viewsUnderMouse.TryPeek (out View? result) ? result : null;
+
+        if ((mouseEvent.Flags == MouseFlags.Button1Pressed
+             || mouseEvent.Flags == MouseFlags.Button2Pressed
+             || mouseEvent.Flags == MouseFlags.Button3Pressed
+             || mouseEvent.Flags == MouseFlags.Button4Pressed)
+            && Popover is { Visible: true } && !ApplicationNavigation.IsInHierarchy (Popover, deepestViewUnderMouse, includeAdornments: true))
+        {
+            Popover.Visible = false;
+        }
+>>>>>>> Stashed changes
 
-        if (view is { })
+        if (deepestViewUnderMouse is { })
         {
 #if DEBUG_IDISPOSABLE
-            if (view.WasDisposed)
+            if (deepestViewUnderMouse.WasDisposed)
             {
-                throw new ObjectDisposedException (view.GetType ().FullName);
+                throw new ObjectDisposedException (deepestViewUnderMouse.GetType ().FullName);
             }
 #endif
-            mouseEvent.View = view;
+            mouseEvent.View = deepestViewUnderMouse;
         }
 
         MouseEvent?.Invoke (null, mouseEvent);
@@ -177,7 +198,7 @@ public static partial class Application // Mouse handling
                 Position = frameLoc,
                 Flags = mouseEvent.Flags,
                 ScreenPosition = mouseEvent.Position,
-                View = view ?? MouseGrabView
+                View = deepestViewUnderMouse ?? MouseGrabView
             };
 
             if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false)
@@ -193,7 +214,7 @@ public static partial class Application // Mouse handling
             }
 
             // ReSharper disable once ConditionIsAlwaysTrueOrFalse
-            if (MouseGrabView is null && view is Adornment)
+            if (MouseGrabView is null && deepestViewUnderMouse is Adornment)
             {
                 // The view that grabbed the mouse has been disposed
                 return;
@@ -202,6 +223,7 @@ public static partial class Application // Mouse handling
 
         // We can combine this into the switch expression to reduce cognitive complexity even more and likely
         // avoid one or two of these checks in the process, as well.
+<<<<<<< Updated upstream
         WantContinuousButtonPressedView = view switch
                                           {
                                               { WantContinuousButtonPressed: true } => view,
@@ -224,17 +246,24 @@ public static partial class Application // Mouse handling
                 ApplicationOverlapped.MoveCurrent ((Toplevel)top);
             }
         }
+=======
+        WantContinuousButtonPressedView = deepestViewUnderMouse switch
+        {
+            { WantContinuousButtonPressed: true } => deepestViewUnderMouse,
+            _ => null
+        };
+>>>>>>> Stashed changes
 
         // May be null before the prior condition or the condition may set it as null.
         // So, the checking must be outside the prior condition.
-        if (view is null)
+        if (deepestViewUnderMouse is null)
         {
             return;
         }
 
         MouseEvent? me;
 
-        if (view is Adornment adornment)
+        if (deepestViewUnderMouse is Adornment adornment)
         {
             Point frameLoc = adornment.ScreenToFrame (mouseEvent.Position);
 
@@ -243,19 +272,19 @@ public static partial class Application // Mouse handling
                 Position = frameLoc,
                 Flags = mouseEvent.Flags,
                 ScreenPosition = mouseEvent.Position,
-                View = view
+                View = deepestViewUnderMouse
             };
         }
-        else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.Position))
+        else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.Position))
         {
-            Point viewportLocation = view.ScreenToViewport (mouseEvent.Position);
+            Point viewportLocation = deepestViewUnderMouse.ScreenToViewport (mouseEvent.Position);
 
             me = new ()
             {
                 Position = viewportLocation,
                 Flags = mouseEvent.Flags,
                 ScreenPosition = mouseEvent.Position,
-                View = view
+                View = deepestViewUnderMouse
             };
         }
         else
@@ -263,51 +292,97 @@ public static partial class Application // Mouse handling
             return;
         }
 
-        if (MouseEnteredView is null)
-        {
-            MouseEnteredView = view;
-            view.NewMouseEnterEvent (me);
-        }
-        else if (MouseEnteredView != view)
+        // Mouse Enter/Leave events
+
+        // Tell any views that are no longer under the mouse that the mouse has left and remove them from list
+        List<View?> viewsToLeave = ViewsUnderMouse.Where (v => v is { } && !viewsUnderMouse.Contains (v)).ToList ();
+        foreach (View? view in viewsToLeave)
         {
-            MouseEnteredView.NewMouseLeaveEvent (me);
-            view.NewMouseEnterEvent (me);
-            MouseEnteredView = view;
+            if (view is null)
+            {
+                continue;
+            }
+
+            if (view is Adornment adornmentView)
+            {
+                Point frameLoc = adornmentView.ScreenToFrame (mouseEvent.Position);
+                if (adornmentView.Parent is { } && !adornmentView.Contains (frameLoc))
+                {
+                    ViewsUnderMouse.Remove (view);
+                    view.NewMouseLeaveEvent (me);
+                }
+            }
+            else
+            {
+                Point viewportLocation = view.ScreenToViewport (mouseEvent.Position);
+                if (!view.Contains (viewportLocation))
+                {
+                    ViewsUnderMouse.Remove (view);
+                    view.NewMouseLeaveEvent (me);
+                }
+
+            }
         }
 
-        if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
+        // Tell any views that are now under the mouse (viewsUnderMouse) that the mouse has entered and add them to the list
+        foreach (View? view in viewsUnderMouse)
         {
-            return;
+            if (view is null)
+            {
+                continue;
+            }
+
+            Point viewportLocation = view.ScreenToViewport (mouseEvent.Position);
+
+            if (view is Adornment adornmentView)
+            {
+
+                if (adornmentView.Parent is { } && !adornmentView.Contains (viewportLocation))
+                {
+                    ViewsUnderMouse.Add (view);
+                    view.NewMouseEnterEvent (me);
+                }
+            }
+            else if (view.Contains (viewportLocation))
+            {
+                ViewsUnderMouse.Add (view);
+                view.NewMouseEnterEvent (me);
+            }
         }
 
-        WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null;
+
+        WantContinuousButtonPressedView = deepestViewUnderMouse.WantContinuousButtonPressed ? deepestViewUnderMouse : null;
 
         //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
+        if (deepestViewUnderMouse.Id == "mouseDemo")
+        {
+
+        }
 
-        while (view.NewMouseEvent (me) is not true && MouseGrabView is not { })
+        while (deepestViewUnderMouse.NewMouseEvent (me) is not true && MouseGrabView is not { })
         {
-            if (view is Adornment adornmentView)
+            if (deepestViewUnderMouse is Adornment adornmentView)
             {
-                view = adornmentView.Parent!.SuperView;
+                deepestViewUnderMouse = adornmentView.Parent!.SuperView;
             }
             else
             {
-                view = view.SuperView;
+                deepestViewUnderMouse = deepestViewUnderMouse.SuperView;
             }
 
-            if (view is null)
+            if (deepestViewUnderMouse is null)
             {
                 break;
             }
 
-            Point boundsPoint = view.ScreenToViewport (mouseEvent.Position);
+            Point boundsPoint = deepestViewUnderMouse.ScreenToViewport (mouseEvent.Position);
 
             me = new ()
             {
                 Position = boundsPoint,
                 Flags = mouseEvent.Flags,
                 ScreenPosition = mouseEvent.Position,
-                View = view
+                View = deepestViewUnderMouse
             };
         }
 

+ 1 - 1
Terminal.Gui/Application/Application.cs

@@ -198,7 +198,7 @@ public static partial class Application
         IsInitialized = false;
 
         // Mouse
-        MouseEnteredView = null;
+        ViewsUnderMouse.Clear ();
         WantContinuousButtonPressedView = null;
         MouseEvent = null;
         GrabbedMouse = null;

+ 19 - 18
Terminal.Gui/View/Adornment/Adornment.cs

@@ -205,6 +205,7 @@ public class Adornment : View
     #region Mouse Support
 
 
+    // TODO: It's stoopid that this override changes the defn of the input coords from base. 
     /// <summary>
     /// Indicates whether the specified Parent's SuperView-relative coordinates are within the Adornment's Thickness.
     /// </summary>
@@ -229,15 +230,15 @@ public class Adornment : View
     /// <inheritdoc/>
     protected internal override bool? OnMouseEnter (MouseEvent mouseEvent)
     {
-        // Invert Normal
-        if (Diagnostics.HasFlag (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
-        {
-            var cs = new ColorScheme (ColorScheme)
-            {
-                Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
-            };
-            ColorScheme = cs;
-        }
+        //// Invert Normal
+        //if (Diagnostics.HasFlag (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
+        //{
+        //    var cs = new ColorScheme (ColorScheme)
+        //    {
+        //        Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
+        //    };
+        //    ColorScheme = cs;
+        //}
 
         return base.OnMouseEnter (mouseEvent);
     }
@@ -245,15 +246,15 @@ public class Adornment : View
     /// <inheritdoc/>   
     protected internal override bool OnMouseLeave (MouseEvent mouseEvent)
     {
-        // Invert Normal
-        if (Diagnostics.FastHasFlags (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
-        {
-            var cs = new ColorScheme (ColorScheme)
-            {
-                Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
-            };
-            ColorScheme = cs;
-        }
+        //// Invert Normal
+        //if (Diagnostics.FastHasFlags (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
+        //{
+        //    var cs = new ColorScheme (ColorScheme)
+        //    {
+        //        Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
+        //    };
+        //    ColorScheme = cs;
+        //}
 
         return base.OnMouseLeave (mouseEvent);
     }

+ 25 - 2
Terminal.Gui/View/View.Layout.cs

@@ -1,5 +1,6 @@
 #nullable enable
 using System.Diagnostics;
+using Microsoft.CodeAnalysis;
 
 namespace Terminal.Gui;
 
@@ -28,6 +29,23 @@ public partial class View // Layout APIs
     // CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
     internal static View? FindDeepestView (View? start, in Point location)
     {
+<<<<<<< Updated upstream
+=======
+        return GetViewsUnderMouse (location).TryPeek (out View? result) ? result : null;
+    }
+
+    internal static Stack<View?> GetViewsUnderMouse (in Point location)
+    {
+        Stack<View> viewsUnderMouse = new ();
+
+        View? start = Application.Top;
+
+        if (Application.Popover?.Visible == true)
+        {
+            start = Application.Popover;
+        }
+
+>>>>>>> Stashed changes
         Point currentLocation = location;
 
         while (start is { Visible: true } && start.Contains (currentLocation))
@@ -51,6 +69,8 @@ public partial class View // Layout APIs
 
             if (found is { })
             {
+                //viewsUnderMouse.Push (found);
+
                 start = found;
                 viewportOffset = found.Parent?.Frame.Location ?? Point.Empty;
             }
@@ -70,6 +90,7 @@ public partial class View // Layout APIs
                     currentLocation.Y = startOffsetY + start.Viewport.Y;
 
                     // start is the deepest subview under the mouse; stop searching the subviews
+                    viewsUnderMouse.Push (start);
                     break;
                 }
             }
@@ -77,14 +98,16 @@ public partial class View // Layout APIs
             if (subview is null)
             {
                 // No subview was found that's under the mouse, so we're done
-                return start;
+                viewsUnderMouse.Push (start);
+                return viewsUnderMouse;
             }
 
             // We found a subview of start that's under the mouse, continue...
             start = subview;
+            //viewsUnderMouse.Push (subview);
         }
 
-        return null;
+        return viewsUnderMouse;
     }
 
     // BUGBUG: This method interferes with Dialog/MessageBox default min/max size.

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

@@ -80,6 +80,11 @@ public partial class View // Mouse APIs
             return false;
         }
 
+        if (!WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
+        {
+            return false;
+        }
+
         if (OnMouseEvent (mouseEvent))
         {
             // Technically mouseEvent.Handled should already be true if implementers of OnMouseEvent
@@ -328,23 +333,24 @@ public partial class View // Mouse APIs
     }
 
     /// <summary>
-    ///     Called by <see cref="Application.OnMouseEvent"/> when the mouse enters <see cref="Viewport"/>. The view will
-    ///     then receive mouse events until <see cref="NewMouseLeaveEvent"/> is called indicating the mouse has left
-    ///     the view.
+    ///     Called by <see cref="Application.OnMouseEvent"/> when the mouse enters <see cref="Viewport"/>. <see cref="MouseLeave"/> will
+    ///     be raised when the mouse is no longer over the <see cref="Viewport"/>. If another View occludes the current one, the
+    ///     that View will also receive a MouseEnter event.
     /// </summary>
     /// <remarks>
     ///     <para>
-    ///         A view must be both enabled and visible to receive mouse events.
+    ///         A view must be visible to receive Enter/Leave events.
     ///     </para>
     ///     <para>
-    ///         This method calls <see cref="OnMouseEnter"/> to fire the event.
+    ///         This method calls <see cref="OnMouseEnter"/> to raise the <see cref="MouseEnter"/> event.
     ///     </para>
     ///     <para>
     ///         See <see cref="SetHighlight"/> for more information.
     ///     </para>
     /// </remarks>
     /// <param name="mouseEvent"></param>
-    /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise.</returns>
+    /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise. Handling the event
+    /// prevents Views higher in the visible hierarchy from recieving Enter/Leave events.</returns>
     internal bool? NewMouseEnterEvent (MouseEvent mouseEvent)
     {
         if (!Enabled)
@@ -375,29 +381,23 @@ public partial class View // Mouse APIs
     }
 
     /// <summary>
-    ///     Called by <see cref="Application.OnMouseEvent"/> when the mouse leaves <see cref="Viewport"/>. The view will
-    ///     then no longer receive mouse events.
+    ///     Called by <see cref="Application.OnMouseEvent"/> when the mouse leaves <see cref="Viewport"/>.
     /// </summary>
     /// <remarks>
     ///     <para>
-    ///         A view must be both enabled and visible to receive mouse events.
+    ///         A view must be visible to receive Enter/Leave events.
     ///     </para>
     ///     <para>
-    ///         This method calls <see cref="OnMouseLeave"/> to fire the event.
+    ///         This method calls <see cref="OnMouseLeave"/> to raise the <see cref="MouseLeave"/> event.
     ///     </para>
     ///     <para>
     ///         See <see cref="SetHighlight"/> for more information.
     ///     </para>
     /// </remarks>
     /// <param name="mouseEvent"></param>
-    /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise.</returns>
+    /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise. </returns>
     internal bool? NewMouseLeaveEvent (MouseEvent mouseEvent)
     {
-        if (!Enabled)
-        {
-            return true;
-        }
-
         if (!CanBeVisible (this))
         {
             return false;

+ 12 - 3
UICatalog/Scenarios/Mouse.cs

@@ -187,13 +187,18 @@ public class Mouse : Scenario
         Application.Shutdown ();
     }
 
-    public class MouseDemo : View
+    public class MouseDemo : Shortcut
     {
         private bool _button1PressedOnEnter;
 
         public MouseDemo ()
         {
             CanFocus = true;
+            Id = "mouseDemo";
+            Title = "Hi";
+            Key = Key.A.WithAlt;
+            HelpText = "Help!";
+            WantMousePositionReports = true;
 
             MouseEvent += (s, e) =>
                           {
@@ -214,10 +219,14 @@ public class Mouse : Scenario
 
             MouseLeave += (s, e) =>
                           {
-                              ColorScheme = Colors.ColorSchemes ["Dialog"];
+                              ColorScheme = Colors.ColorSchemes ["Menu"];
                               _button1PressedOnEnter = false;
                           };
-            MouseEnter += (s, e) => { _button1PressedOnEnter = e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed); };
+            MouseEnter += (s, e) =>
+                          {
+                              ColorScheme = Colors.ColorSchemes ["Error"];
+                              _button1PressedOnEnter = e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed);
+                          };
         }
     }
 }