Cleaned up MouseClick related stuff.

This commit is contained in:
Tig Kindel
2024-03-01 11:42:28 -07:00
parent 162dbfbd20
commit 7036f5f8f8
17 changed files with 173 additions and 274 deletions

View File

@@ -72,27 +72,6 @@ public class Responder : IDisposable
/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
public virtual bool OnLeave (View view) { return false; }
/// <summary>
/// Called when the mouse first enters the view; the view will now receives 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"/>.
/// </remarks>
/// <param name="mouseEvent"></param>
/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
public virtual bool OnMouseEnter (MouseEvent mouseEvent) { return false; }
/// <summary>
/// Called when the mouse has moved outside of the view; 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"/>.
/// </remarks>
/// <param name="mouseEvent"></param>
/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
public virtual bool OnMouseLeave (MouseEvent mouseEvent) { return false; }
/// <summary>Method invoked when the <see cref="Visible"/> property from a view is changed.</summary>
public virtual void OnVisibleChanged () { }

View File

@@ -227,6 +227,24 @@ public partial class View : Responder, ISupportInitializeNotification
/// <summary>Event fired when the <see cref="Visible"/> value is being changed.</summary>
public event EventHandler VisibleChanged;
/// <summary>
/// Cancelable event fired when the <see cref="Command.Accept"/> command is invoked. Set <see cref="CancelEventArgs.Cancel"/>
/// to cancel the event.
/// </summary>
public event EventHandler<CancelEventArgs> Accept;
/// <summary>
/// Called when the <see cref="Command.Accept"/> command is invoked. Fires the <see cref="Accept"/>
/// event.
/// </summary>
/// <returns>If <see langword="true"/> the event was canceled.</returns>
protected bool? OnAccept ()
{
var args = new CancelEventArgs ();
Accept?.Invoke (this, args);
return args.Cancel;
}
/// <inheritdoc/>
protected override void Dispose (bool disposing)
{

View File

@@ -24,31 +24,12 @@ public partial class View
if (CanFocus)
{
SetFocus ();
return true;
}
return false;
}
/// <summary>
/// Cancelable event fired when the <see cref="Command.Accept"/> command is invoked. Set <see cref="CancelEventArgs.Cancel"/>
/// to cancel the event.
/// </summary>
public event EventHandler<CancelEventArgs> Accept;
/// <summary>
/// Called when the <see cref="Command.Accept"/> command is invoked. Fires the <see cref="Accept"/>
/// event.
/// </summary>
/// <returns>If <see langword="true"/> the event was canceled.</returns>
public bool? OnAccept ()
{
var args = new CancelEventArgs ();
Accept?.Invoke (this, args);
return args.Cancel;
}
/// <summary>Invoked when the <see cref="HotKey"/> is changed.</summary>
public event EventHandler<KeyChangedEventArgs> HotKeyChanged;

View File

@@ -9,17 +9,35 @@ public partial class View
/// <value><see langword="true"/> if want mouse position reports; otherwise, <see langword="false"/>.</value>
public virtual bool WantMousePositionReports { get; set; }
/// <summary>Event fired when a mouse event is generated.</summary>
/// <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 view receives the mouse event for the first time.</summary>
/// <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 view receives a mouse event for the last time.</summary>
/// <summary>Event fired when the mouse leaves the View's <see cref="Bounds"/>.</summary>
public event EventHandler<MouseEventEventArgs> MouseLeave;
/// <inheritdoc/>
public override bool OnMouseEnter (MouseEvent mouseEvent)
// 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
/// the view. At which time, <see cref="OnMouseLeave(Gui.MouseEvent)"/> will be called.
/// </summary>
/// <remarks>
/// 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>
public virtual bool OnMouseEnter (MouseEvent mouseEvent)
{
if (!Enabled)
{
@@ -34,20 +52,44 @@ public partial class View
var args = new MouseEventEventArgs (mouseEvent);
MouseEnter?.Invoke (this, args);
return args.Handled || base.OnMouseEnter (mouseEvent);
return args.Handled;
}
/// <summary>Method invoked when a mouse event is generated</summary>
// TODO: OnMouseLeave should not be public virtual, but protected.
/// <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
/// 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"/>.
/// </remarks>
/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
/// <param name="mouseEvent">Contains the details about the mouse event.</param>
//public virtual bool MouseEvent (MouseEvent mouseEvent) { return false; }
/// <param name="mouseEvent"></param>
/// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
public virtual bool OnMouseLeave (MouseEvent mouseEvent)
{
if (!Enabled)
{
return true;
}
if (!CanBeVisible (this))
{
return false;
}
/// <summary>Method invoked when a mouse event is generated</summary>
var args = new MouseEventEventArgs (mouseEvent);
MouseLeave?.Invoke (this, args);
return args.Handled;
}
// TODO: OnMouseEvent should not be public virtual, but protected.
/// <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>
/// </remarks>
/// <param name="mouseEvent"></param>
/// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
public virtual bool OnMouseEvent (MouseEvent mouseEvent)
@@ -64,33 +106,19 @@ public partial class View
var args = new MouseEventEventArgs (mouseEvent);
//if (MouseEvent (mouseEvent))
//{
// return true;
//}
if (mouseEvent.Flags == MouseFlags.Button1Clicked)
{
if (CanFocus && !HasFocus && SuperView is { })
{
SuperView.SetFocus (this);
SetNeedsDisplay ();
}
return OnMouseClick (args);
}
if (mouseEvent.Flags == MouseFlags.Button2Clicked)
// 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))
{
return OnMouseClick (args);
}
if (mouseEvent.Flags == MouseFlags.Button3Clicked)
{
return OnMouseClick (args);
}
if (mouseEvent.Flags == MouseFlags.Button4Clicked)
if (mouseEvent.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button2DoubleClicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button3DoubleClicked)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button4DoubleClicked))
{
return OnMouseClick (args);
}
@@ -98,26 +126,13 @@ public partial class View
return false;
}
/// <inheritdoc/>
public override bool OnMouseLeave (MouseEvent mouseEvent)
{
if (!Enabled)
{
return true;
}
if (!CanBeVisible (this))
{
return false;
}
var args = new MouseEventEventArgs (mouseEvent);
MouseLeave?.Invoke (this, args);
return args.Handled || base.OnMouseLeave (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>
/// </remarks>
protected bool OnMouseClick (MouseEventEventArgs args)
{
if (!Enabled)
@@ -126,6 +141,10 @@ public partial class View
}
MouseClick?.Invoke (this, args);
if (args.Handled)
{
return true;
}
return args.Handled;
}

View File

@@ -58,6 +58,12 @@ public class Button : View
KeyBindings.Add (Key.Enter, Command.HotKey);
TitleChanged += Button_TitleChanged;
MouseClick += Button_MouseClick;
}
private void Button_MouseClick (object sender, MouseEventEventArgs e)
{
e.Handled = InvokeCommand (Command.Accept) == true;
}
private void Button_TitleChanged (object sender, StateEventArgs<string> e)
@@ -99,29 +105,6 @@ public class Button : View
/// <summary></summary>
public bool NoPadding { get; set; }
/// <inheritdoc/>
public override bool OnMouseEvent (MouseEvent me)
{
if (me.Flags == MouseFlags.Button1Clicked)
{
if (CanFocus && Enabled)
{
if (!HasFocus)
{
SetFocus ();
SetNeedsDisplay ();
Draw ();
}
OnAccept ();
}
return true;
}
return false;
}
/// <inheritdoc/>
public override bool OnEnter (View view)
{

View File

@@ -34,6 +34,13 @@ public class CheckBox : View
KeyBindings.Add (Key.Space, Command.Accept);
TitleChanged += Checkbox_TitleChanged;
MouseClick += CheckBox_MouseClick;
}
private void CheckBox_MouseClick (object? sender, MouseEventEventArgs e)
{
e.Handled = OnToggled () == true;
}
private void Checkbox_TitleChanged (object? sender, StateEventArgs<string> e)
@@ -87,17 +94,6 @@ public class CheckBox : View
}
}
/// <inheritdoc/>
public override bool OnMouseEvent (MouseEvent me)
{
if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus)
{
return false;
}
return OnToggled () == true;
}
/// <inheritdoc/>
public override bool OnEnter (View view)
{
@@ -143,11 +139,13 @@ public class CheckBox : View
return e.Cancel;
}
Checked = e.NewValue;
// By default, Command.Accept calls OnAccept, so we need to call it here to ensure that the event is fired.
if (OnAccept () == true)
{
return true;
}
SetNeedsDisplay ();
SetFocus ();
OnAccept ();
Checked = e.NewValue;
return true;
}

View File

@@ -25,6 +25,12 @@ public class Label : View
KeyBindings.Add (Key.Space, Command.Accept);
TitleChanged += Label_TitleChanged;
MouseClick += Label_MouseClick;
}
private void Label_MouseClick (object sender, MouseEventEventArgs e)
{
e.Handled = InvokeCommand (Command.Accept) == true;
}
private void Label_TitleChanged (object sender, StateEventArgs<string> e)
@@ -62,50 +68,6 @@ public class Label : View
public override bool OnEnter (View view)
{
Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
return base.OnEnter (view);
}
/// <summary>Method invoked when a mouse event is generated</summary>
/// <param name="mouseEvent"></param>
/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
public override bool OnMouseEvent (MouseEvent mouseEvent)
{
var args = new MouseEventEventArgs (mouseEvent);
if (OnMouseClick (args))
{
return true;
}
//if (MouseEvent (mouseEvent))
//{
// return true;
//}
if (mouseEvent.Flags == MouseFlags.Button1Clicked)
{
if (!CanFocus)
{
FocusNext ();
}
if (!HasFocus && SuperView is { })
{
if (!SuperView.HasFocus)
{
SuperView.SetFocus ();
}
SetFocus ();
SetNeedsDisplay ();
}
OnAccept ();
return true;
}
return false;
}
}

View File

@@ -704,9 +704,10 @@ public class ListView : View
object value = _source.ToList () [_selected];
// By default, Command.Accept calls OnAccept, so we need to call it here to ensure that the event is fired.
if (OnAccept () == true)
{
return false;
return true;
}
OpenSelectedItem?.Invoke (this, new ListViewItemEventArgs (_selected, value));

View File

@@ -126,10 +126,11 @@ public class ScrollView : View
}
SetContentOffset (_contentOffset);
_contentView.Frame = new (ContentOffset, ContentSize);
_contentView.Frame = new Rectangle (ContentOffset, ContentSize);
// PERF: How about calls to Point.Offset instead?
_vertical.ChangedPosition += delegate { ContentOffset = new (ContentOffset.X, _vertical.Position); };
_horizontal.ChangedPosition += delegate { ContentOffset = new (_horizontal.Position, ContentOffset.Y); };
_vertical.ChangedPosition += delegate { ContentOffset = new Point (ContentOffset.X, _vertical.Position); };
_horizontal.ChangedPosition += delegate { ContentOffset = new Point (_horizontal.Position, ContentOffset.Y); };
};
}
@@ -188,7 +189,7 @@ public class ScrollView : View
if (_contentSize != value)
{
_contentSize = value;
_contentView.Frame = new (_contentOffset, value);
_contentView.Frame = new Rectangle (_contentOffset, value);
_vertical.Size = _contentSize.Height;
_horizontal.Size = _contentSize.Width;
SetNeedsDisplay ();
@@ -333,53 +334,6 @@ public class ScrollView : View
SetNeedsLayout ();
}
/// <inheritdoc/>
public override bool OnMouseEvent (MouseEvent me)
{
if (me.Flags != MouseFlags.WheeledDown
&& me.Flags != MouseFlags.WheeledUp
&& me.Flags != MouseFlags.WheeledRight
&& me.Flags != MouseFlags.WheeledLeft
&&
// me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked &&
!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
{
return false;
}
if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator)
{
ScrollDown (1);
}
else if (me.Flags == MouseFlags.WheeledUp && ShowVerticalScrollIndicator)
{
ScrollUp (1);
}
else if (me.Flags == MouseFlags.WheeledRight && _showHorizontalScrollIndicator)
{
ScrollRight (1);
}
else if (me.Flags == MouseFlags.WheeledLeft && ShowVerticalScrollIndicator)
{
ScrollLeft (1);
}
else if (me.X == _vertical.Frame.X && ShowVerticalScrollIndicator)
{
_vertical.OnMouseEvent (me);
}
else if (me.Y == _horizontal.Frame.Y && ShowHorizontalScrollIndicator)
{
_horizontal.OnMouseEvent (me);
}
else if (IsOverridden (me.View, "MouseEvent"))
{
Application.UngrabMouse ();
}
return true;
}
/// <inheritdoc/>
public override void OnDrawContent (Rectangle contentArea)
{
@@ -429,6 +383,41 @@ public class ScrollView : View
return false;
}
/// <inheritdoc/>
public override bool OnMouseEvent (MouseEvent me)
{
if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator)
{
ScrollDown (1);
}
else if (me.Flags == MouseFlags.WheeledUp && ShowVerticalScrollIndicator)
{
ScrollUp (1);
}
else if (me.Flags == MouseFlags.WheeledRight && _showHorizontalScrollIndicator)
{
ScrollRight (1);
}
else if (me.Flags == MouseFlags.WheeledLeft && ShowVerticalScrollIndicator)
{
ScrollLeft (1);
}
else if (me.X == _vertical.Frame.X && ShowVerticalScrollIndicator)
{
_vertical.OnMouseEvent (me);
}
else if (me.Y == _horizontal.Frame.Y && ShowHorizontalScrollIndicator)
{
_horizontal.OnMouseEvent (me);
}
else if (IsOverridden (me.View, "OnMouseEvent"))
{
Application.UngrabMouse ();
}
return base.OnMouseEvent(me);
}
/// <inheritdoc/>
public override void PositionCursor ()
{
@@ -591,8 +580,8 @@ public class ScrollView : View
private void SetContentOffset (Point offset)
{
// INTENT: Unclear intent. How about a call to Offset?
_contentOffset = new (-Math.Abs (offset.X), -Math.Abs (offset.Y));
_contentView.Frame = new (_contentOffset, _contentSize);
_contentOffset = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y));
_contentView.Frame = new Rectangle (_contentOffset, _contentSize);
int p = Math.Max (0, -_contentOffset.Y);
if (_vertical.Position != p)

View File

@@ -796,7 +796,7 @@ public class TableView : View
&& me.Flags != MouseFlags.WheeledLeft
&& me.Flags != MouseFlags.WheeledRight)
{
return base.OnMouseEvent (me);
return false;
}
if (!HasFocus && CanFocus)

View File

@@ -455,9 +455,10 @@ public class TreeView<T> : View, ITreeView where T : class
/// <returns><see langword="true"/> if <see cref="ObjectActivated"/> was fired.</returns>
public bool? ActivateSelectedObjectIfAny ()
{
// By default, Command.Accept calls OnAccept, so we need to call it here to ensure that the event is fired.
if (OnAccept () == true)
{
return false;
return true;
}
T o = SelectedObject;

View File

@@ -126,31 +126,6 @@ public class ASCIICustomButtonTest : Scenario
return base.OnLeave (view);
}
public override bool OnMouseEvent (MouseEvent mouseEvent)
{
Debug.WriteLine ($"{mouseEvent.Flags}");
if (mouseEvent.Flags == MouseFlags.Button1Clicked)
{
if (!HasFocus && SuperView != null)
{
if (!SuperView.HasFocus)
{
SuperView.SetFocus ();
}
SetFocus ();
SetNeedsDisplay ();
}
OnAccept ();
return true;
}
return base.OnMouseEvent (mouseEvent);
}
public event Action<ASCIICustomButton> PointerEnter;
private void This_MouseClick (object sender, MouseEventEventArgs obj) { OnMouseEvent (obj.MouseEvent); }
}

View File

@@ -38,7 +38,7 @@ public class WizardTests
var firstIteration = true;
Application.RunIteration (ref runstate, ref firstIteration);
wizard.NextFinishButton.OnAccept ();
wizard.NextFinishButton.InvokeCommand (Command.Accept);
Application.RunIteration (ref runstate, ref firstIteration);
Application.End (runstate);
Assert.True (finishedFired);
@@ -64,13 +64,13 @@ public class WizardTests
Application.RunIteration (ref runstate, ref firstIteration);
Assert.Equal (step1.Title, wizard.CurrentStep.Title);
wizard.NextFinishButton.OnAccept ();
wizard.NextFinishButton.InvokeCommand (Command.Accept);
Assert.False (finishedFired);
Assert.False (closedFired);
Assert.Equal (step2.Title, wizard.CurrentStep.Title);
Assert.Equal (wizard.GetLastStep ().Title, wizard.CurrentStep.Title);
wizard.NextFinishButton.OnAccept ();
wizard.NextFinishButton.InvokeCommand (Command.Accept);
Application.End (runstate);
Assert.True (finishedFired);
Assert.True (closedFired);
@@ -99,7 +99,7 @@ public class WizardTests
Assert.Equal (step2.Title, wizard.CurrentStep.Title);
Assert.Equal (wizard.GetLastStep ().Title, wizard.CurrentStep.Title);
wizard.NextFinishButton.OnAccept ();
wizard.NextFinishButton.InvokeCommand (Command.Accept);
Application.End (runstate);
Assert.True (finishedFired);
Assert.True (closedFired);

View File

@@ -191,13 +191,6 @@ public class ResponderTests
"OnDrawContent"
)
);
Assert.True (
Responder.IsOverridden (
new Button { Text = "Button overrides OnMouseEvent" },
"OnMouseEvent"
)
);
#if DEBUG_IDISPOSABLE
// HACK: Force clean up of Responders to avoid having to Dispose all the Views created above.

View File

@@ -1161,7 +1161,7 @@ At 0,0
view.Accept += ViewOnAccept;
view.OnAccept ();
view.InvokeCommand (Command.Accept);
Assert.True (accepted);
return;
@@ -1176,7 +1176,7 @@ At 0,0
view.Accept += ViewOnAccept;
var ret = view.OnAccept ();
var ret = view.InvokeCommand (Command.Accept);
Assert.True (ret);
Assert.True (acceptInvoked);

View File

@@ -693,7 +693,7 @@ public class ButtonTests
button.Accept += ButtonAccept;
var ret = button.OnAccept ();
var ret = button.InvokeCommand (Command.Accept);
Assert.True (ret);
Assert.True (acceptInvoked);

View File

@@ -328,7 +328,7 @@ public class CheckBoxTests
ckb.Accept += ViewOnAccept;
var ret = ckb.OnAccept ();
var ret = ckb.InvokeCommand (Command.Accept);
Assert.True (ret);
Assert.True (acceptInvoked);
@@ -567,13 +567,13 @@ public class CheckBoxTests
var cb = new CheckBox ();
var accepted = false;
cb.Accept += ButtonOnAccept;
cb.Accept += CheckBoxOnAccept;
cb.InvokeCommand (Command.HotKey);
Assert.True (accepted);
return;
void ButtonOnAccept (object sender, CancelEventArgs e) { accepted = true; }
void CheckBoxOnAccept (object sender, CancelEventArgs e) { accepted = true; }
}
[Theory]