Prototype

This commit is contained in:
Tig
2024-09-20 11:53:14 -06:00
parent 097a51976a
commit 12e3346c01
6 changed files with 187 additions and 79 deletions

View File

@@ -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);
if (view is { })
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 (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)
{
MouseEnteredView.NewMouseLeaveEvent (me);
view.NewMouseEnterEvent (me);
MouseEnteredView = view;
}
// Mouse Enter/Leave events
if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
// 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)
{
return;
}
if (view is null)
{
continue;
}
WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null;
//Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
while (view.NewMouseEvent (me) is not true && MouseGrabView is not { })
{
if (view is Adornment adornmentView)
{
view = adornmentView.Parent!.SuperView;
Point frameLoc = adornmentView.ScreenToFrame (mouseEvent.Position);
if (adornmentView.Parent is { } && !adornmentView.Contains (frameLoc))
{
ViewsUnderMouse.Remove (view);
view.NewMouseLeaveEvent (me);
}
}
else
{
view = view.SuperView;
Point viewportLocation = view.ScreenToViewport (mouseEvent.Position);
if (!view.Contains (viewportLocation))
{
ViewsUnderMouse.Remove (view);
view.NewMouseLeaveEvent (me);
}
}
}
// 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)
{
if (view is null)
{
continue;
}
if (view is null)
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 = deepestViewUnderMouse.WantContinuousButtonPressed ? deepestViewUnderMouse : null;
//Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
if (deepestViewUnderMouse.Id == "mouseDemo")
{
}
while (deepestViewUnderMouse.NewMouseEvent (me) is not true && MouseGrabView is not { })
{
if (deepestViewUnderMouse is Adornment adornmentView)
{
deepestViewUnderMouse = adornmentView.Parent!.SuperView;
}
else
{
deepestViewUnderMouse = deepestViewUnderMouse.SuperView;
}
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
};
}

View File

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

View File

@@ -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);
}

View File

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

View File

@@ -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;

View File

@@ -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);
};
}
}
}