Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #3312. Mouse API makes it way too hard to track button pressed. #3393

Draft
wants to merge 19 commits into
base: v2_develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Terminal.Gui/Application/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ internal static void ResetState (bool ignoreDisposed = false)

// Mouse
_mouseEnteredView = null;
_lastViewButtonPressed = null;
_canProcessClickedEvent = true;
_isMouseDown = false;
WantContinuousButtonPressedView = null;
MouseEvent = null;
GrabbedMouse = null;
Expand Down
47 changes: 43 additions & 4 deletions Terminal.Gui/Application/ApplicationMouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@

// Used by OnMouseEvent to track the last view that was clicked on.
internal static View? _mouseEnteredView;
internal static View? _lastViewButtonPressed;
internal static bool _canProcessClickedEvent = true;
internal static bool? _isMouseDown;

/// <summary>Event fired when a mouse move or click occurs. Coordinates are screen relative.</summary>
/// <remarks>
Expand Down Expand Up @@ -142,6 +145,38 @@
mouseEvent.View = view;
}

if (_lastViewButtonPressed is null && mouseEvent.Flags is MouseFlags.Button1Pressed or MouseFlags.Button2Pressed or MouseFlags.Button3Pressed or MouseFlags.Button4Pressed)
{
_lastViewButtonPressed = view;
_isMouseDown = true;
}
else if (_lastViewButtonPressed is { } && mouseEvent.Flags is MouseFlags.Button1Released or MouseFlags.Button2Released or MouseFlags.Button3Released or MouseFlags.Button4Released)
{
if (_lastViewButtonPressed != view)
{
_canProcessClickedEvent = false;
}

_lastViewButtonPressed = null;
_isMouseDown = false;
}
else if (!_canProcessClickedEvent && mouseEvent.Flags is MouseFlags.Button1Clicked or MouseFlags.Button2Clicked or MouseFlags.Button3Clicked or MouseFlags.Button4Clicked)
{
_canProcessClickedEvent = true;
_isMouseDown = null;

return;
}
else if (!mouseEvent.Flags.HasFlag (MouseFlags.ReportMousePosition))
{
_lastViewButtonPressed = null;
_isMouseDown = null;
}
else
{
_isMouseDown = null;
}

MouseEvent?.Invoke (null, mouseEvent);

if (mouseEvent.Handled)
Expand All @@ -160,7 +195,8 @@
Position = frameLoc,
Flags = mouseEvent.Flags,
ScreenPosition = mouseEvent.Position,
View = MouseGrabView
View = MouseGrabView,
IsMouseDown = _isMouseDown
};

if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false)
Expand All @@ -170,7 +206,8 @@
}

//System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true)
if ((MouseGrabView?.WantMousePositionReports == true || MouseGrabView?.WantContinuousButtonPressed == true)
&& MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true)
{
return;
}
Expand Down Expand Up @@ -221,7 +258,8 @@
Position = frameLoc,
Flags = mouseEvent.Flags,
ScreenPosition = mouseEvent.Position,
View = view
View = view,
IsMouseDown = _isMouseDown
};
}
else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.Position))
Expand All @@ -233,7 +271,8 @@
Position = viewportLocation,
Flags = mouseEvent.Flags,
ScreenPosition = mouseEvent.Position,
View = view
View = view,
IsMouseDown = _isMouseDown
};
}

Expand Down Expand Up @@ -272,7 +311,7 @@

if (view is Adornment adornmentView)
{
view = adornmentView.Parent.SuperView;

Check warning on line 314 in Terminal.Gui/Application/ApplicationMouse.cs

View workflow job for this annotation

GitHub Actions / build_and_test (ubuntu-latest)

Dereference of a possibly null reference.

Check warning on line 314 in Terminal.Gui/Application/ApplicationMouse.cs

View workflow job for this annotation

GitHub Actions / build_and_test (windows-latest)

Dereference of a possibly null reference.

Check warning on line 314 in Terminal.Gui/Application/ApplicationMouse.cs

View workflow job for this annotation

GitHub Actions / build_and_test (macos-latest)

Dereference of a possibly null reference.
}
else
{
Expand Down
5 changes: 5 additions & 0 deletions Terminal.Gui/Input/Mouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ public class MouseEvent
/// </remarks>
public Point ScreenPosition { get; set; }

/// <summary>
/// Indicates if the current mouse event has first pressed <see langword="true"/>, latest released <see langword="false"/> or none <see langword="null"/>.
/// </summary>
public bool? IsMouseDown { get; set; }
BDisp marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Indicates if the current mouse event has been processed. Set this value to <see langword="true"/> to indicate the mouse
/// event was handled.
Expand Down
1 change: 1 addition & 0 deletions Terminal.Gui/View/Adornment/Border.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public Border (View parent) : base (parent)

HighlightStyle |= HighlightStyle.Pressed;
Highlight += Border_Highlight;
WantMousePositionReports = true;
}

#if SUBVIEW_BASED_BORDER
Expand Down
201 changes: 189 additions & 12 deletions Terminal.Gui/View/ViewMouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ public partial class View
/// </remarks>
public event EventHandler<MouseEventEventArgs> MouseClick;

/// <summary>
/// Event fired when the user presses and releases the mouse button twice in quick succession without
/// moving the mouse outside the view.
/// </summary>
/// <remarks>
/// <para>
/// The coordinates are relative to <see cref="View.Viewport"/>.
/// </para>
/// </remarks>
public event EventHandler<MouseEventEventArgs> MouseDoubleClick;

/// <summary>Event fired when the user first presses the button down over a view.</summary>
/// <remarks>
/// <para>
/// The coordinates are relative to <see cref="Viewport"/>.
/// </para>
/// </remarks>
public event EventHandler<MouseEventEventArgs> MouseDown;

/// <summary>Event fired when the mouse moves into the View's <see cref="Viewport"/>.</summary>
public event EventHandler<MouseEventEventArgs> MouseEnter;

Expand All @@ -45,6 +64,39 @@ public partial class View
/// <summary>Event fired when the mouse leaves the View's <see cref="Viewport"/>.</summary>
public event EventHandler<MouseEventEventArgs> MouseLeave;

/// <summary>
/// Event fired when the user moves the mouse over a view, or if mouse was grabbed by the view.
/// Flags will indicate if a button is down.
/// </summary>
/// <remarks>
/// <para>
/// The coordinates are relative to <see cref="View.Viewport"/>.
/// </para>
/// </remarks>
public event EventHandler<MouseEventEventArgs> MouseMove;

/// <summary>
/// Event fired when the user presses and releases the mouse button thrice in quick succession without
/// moving the mouse outside the view.
/// </summary>
/// <remarks>
/// <para>
/// The coordinates are relative to <see cref="View.Viewport"/>.
/// </para>
/// </remarks>
public event EventHandler<MouseEventEventArgs> MouseTripleClick;

/// <summary>
/// Event fired when the user lets go of the mouse button. Only received if the mouse is over the view,
/// or it was grabbed by the view.
/// </summary>
/// <remarks>
/// <para>
/// The coordinates are relative to <see cref="View.Viewport"/>.
/// </para>
/// </remarks>
public event EventHandler<MouseEventEventArgs> MouseUp;

/// <summary>
/// Processes a <see cref="MouseEvent"/>. This method is called by <see cref="Application.OnMouseEvent"/> when a mouse
/// event occurs.
Expand Down Expand Up @@ -87,6 +139,30 @@ public partial class View
return mouseEvent.Handled = true;
}

if (mouseEvent.IsMouseDown == true)
{
if (OnMouseDown (new (mouseEvent)))
{
return true;
}
}

if (mouseEvent.Flags.HasFlag (MouseFlags.ReportMousePosition))
{
if (OnMouseMove (new (mouseEvent)))
{
return true;
}
}

if (mouseEvent.IsMouseDown == false)
{
if (OnMouseUp (new (mouseEvent)))
{
return true;
}
}

if (HighlightStyle != HighlightStyle.None || WantContinuousButtonPressed)
{
if (HandlePressed (mouseEvent))
Expand All @@ -109,20 +185,30 @@ public partial class View
|| mouseEvent.Flags.HasFlag (MouseFlags.Button2Clicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button4Clicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
)
{
// If it's a click, and we didn't handle it, then we'll call OnMouseClick
// We get here if the view did not handle the mouse event via OnMouseEvent/MouseEvent and
// it did not handle the press/release/clicked events via HandlePress/HandleRelease/HandleClicked
return OnMouseClick (new (mouseEvent));
}

if (mouseEvent.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button2DoubleClicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button3DoubleClicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button4DoubleClicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button1TripleClicked)
)
{
return OnMouseDoubleClick (new (mouseEvent));
}

if (mouseEvent.Flags.HasFlag (MouseFlags.Button1TripleClicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button2TripleClicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button3TripleClicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button4TripleClicked)
)
{
// If it's a click, and we didn't handle it, then we'll call OnMouseClick
// We get here if the view did not handle the mouse event via OnMouseEvent/MouseEvent and
// it did not handle the press/release/clicked events via HandlePress/HandleRelease/HandleClicked
return OnMouseClick (new (mouseEvent));
return OnMouseTripleClick (new (mouseEvent));
}

return false;
Expand All @@ -135,6 +221,39 @@ public partial class View
/// <value><see langword="true"/> if mouse position reports are wanted; otherwise, <see langword="false"/>.</value>
public virtual bool WantMousePositionReports { get; set; }

/// <summary>
/// Called when the user presses and releases the mouse button twice in quick succession without
/// moving the mouse outside the view.
/// </summary>
/// <remarks>
/// <para>
/// The coordinates are relative to <see cref="View.Viewport"/>.
/// </para>
/// </remarks>
/// <param name="args"></param>
/// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
protected internal virtual bool OnMouseDoubleClick (MouseEventEventArgs args)
{
MouseDoubleClick?.Invoke (this, args);

return args.Handled;
}

/// <summary>Called when the user first presses the button down over a view's <see cref="Viewport"/>.</summary>
/// <remarks>
/// <para>
/// The coordinates are relative to <see cref="Viewport"/>.
/// </para>
/// </remarks>
/// <param name="args"></param>
/// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
protected internal virtual bool OnMouseDown (MouseEventEventArgs args)
{
MouseDown?.Invoke (this, args);

return args.Handled;
}

/// <summary>
/// Called by <see cref="NewMouseEvent"/> when the mouse enters <see cref="Viewport"/>. The view will
/// then receive mouse events until <see cref="OnMouseLeave"/> is called indicating the mouse has left
Expand Down Expand Up @@ -207,6 +326,60 @@ protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent)
return args.Handled;
}

/// <summary>
/// Called when the user moves the mouse over a view, or if mouse was grabbed by the view.
/// Flags will indicate if a button is down.
/// </summary>
/// <remarks>
/// <para>
/// The coordinates are relative to <see cref="View.Viewport"/>.
/// </para>
/// </remarks>
/// <param name="args"></param>
/// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
protected internal virtual bool OnMouseMove (MouseEventEventArgs args)
{
MouseMove?.Invoke (this, args);

return args.Handled;
}

/// <summary>
/// Called when the user presses and releases the mouse button thrice in quick succession without
/// moving the mouse outside the view.
/// </summary>
/// <remarks>
/// <para>
/// The coordinates are relative to <see cref="View.Viewport"/>.
/// </para>
/// </remarks>
/// <param name="args"></param>
/// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
protected internal virtual bool OnMouseTripleClick (MouseEventEventArgs args)
{
MouseTripleClick?.Invoke (this, args);

return args.Handled;
}

/// <summary>
/// Called when the user lets go of the mouse button. Only received if the mouse is over the view,
/// or it was grabbed by the view.
/// </summary>
/// <remarks>
/// <para>
/// The coordinates are relative to <see cref="View.Viewport"/>.
/// </para>
/// </remarks>
/// <param name="args"></param>
/// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
protected internal virtual bool OnMouseUp (MouseEventEventArgs args)
{
MouseUp?.Invoke (this, args);

return args.Handled;
}

/// <summary>
/// Called when the view is to be highlighted.
/// </summary>
Expand All @@ -231,11 +404,13 @@ protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent)
return args.Cancel;
}

/// <summary>Invokes the MouseClick event.</summary>
/// <summary>
/// Called when the user presses down and then releases the mouse over a view (they could move off in between).
/// If they press and release multiple times in quick succession this event will be called for each up action.
/// </summary>
/// <remarks>
/// <para>
/// Called when the mouse is either clicked or double-clicked. Check
/// <see cref="MouseEvent.Flags"/> to see which button was clicked.
/// The coordinates are relative to <see cref="View.Viewport"/>.
/// </para>
/// </remarks>
/// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
Expand Down Expand Up @@ -442,7 +617,7 @@ internal bool SetHighlight (HighlightStyle newHighlightStyle)

// Enable override via virtual method and/or event
HighlightStyle copy = HighlightStyle;
var args = new CancelEventArgs<HighlightStyle> (ref copy, ref newHighlightStyle);
CancelEventArgs<HighlightStyle> args = new CancelEventArgs<HighlightStyle> (ref copy, ref newHighlightStyle);

if (OnHighlight (args) == true)
{
Expand Down Expand Up @@ -477,7 +652,8 @@ internal bool SetHighlight (HighlightStyle newHighlightStyle)
var cs = new ColorScheme (ColorScheme)
{
// Highlight the foreground focus color
Focus = new (ColorScheme.Focus.Foreground.GetHighlightColor (), ColorScheme.Focus.Background.GetHighlightColor ())
Focus = new (ColorScheme.Focus.Foreground.GetHighlightColor (), ColorScheme.Focus.Background.GetHighlightColor ()),
HotFocus = new (ColorScheme.HotFocus.Foreground.GetHighlightColor (), ColorScheme.HotFocus.Background.GetHighlightColor ())
};
ColorScheme = cs;
}
Expand All @@ -486,7 +662,8 @@ internal bool SetHighlight (HighlightStyle newHighlightStyle)
var cs = new ColorScheme (ColorScheme)
{
// Invert Focus color foreground/background. We can do this because we know the view is not going to be focused.
Normal = new (ColorScheme.Focus.Background, ColorScheme.Normal.Foreground)
Normal = new (ColorScheme.Focus.Background, ColorScheme.Normal.Foreground),
HotNormal = new (ColorScheme.HotFocus.Background, ColorScheme.HotNormal.Foreground)
};
ColorScheme = cs;
}
Expand Down
Loading
Loading