diff --git a/Terminal.Gui/Input/Responder.cs b/Terminal.Gui/Input/Responder.cs index 5deb475df..ea0c339ee 100644 --- a/Terminal.Gui/Input/Responder.cs +++ b/Terminal.Gui/Input/Responder.cs @@ -72,27 +72,6 @@ public class Responder : IDisposable /// true, if the event was handled, false otherwise. public virtual bool OnLeave (View view) { return false; } - /// - /// Called when the mouse first enters the view; the view will now receives mouse events until the mouse leaves - /// the view. At which time, will be called. - /// - /// - /// The coordinates are relative to . - /// - /// - /// true, if the event was handled, false otherwise. - public virtual bool OnMouseEnter (MouseEvent mouseEvent) { return false; } - - /// - /// 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 is called). - /// - /// - /// The coordinates are relative to . - /// - /// - /// true, if the event was handled, false otherwise. - public virtual bool OnMouseLeave (MouseEvent mouseEvent) { return false; } /// Method invoked when the property from a view is changed. public virtual void OnVisibleChanged () { } diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index bb435b7dc..32d9e584c 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -227,6 +227,24 @@ public partial class View : Responder, ISupportInitializeNotification /// Event fired when the value is being changed. public event EventHandler VisibleChanged; + /// + /// Cancelable event fired when the command is invoked. Set + /// to cancel the event. + /// + public event EventHandler Accept; + + /// + /// Called when the command is invoked. Fires the + /// event. + /// + /// If the event was canceled. + protected bool? OnAccept () + { + var args = new CancelEventArgs (); + Accept?.Invoke (this, args); + return args.Cancel; + } + /// protected override void Dispose (bool disposing) { diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index 095fb342d..4957719f7 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -24,31 +24,12 @@ public partial class View if (CanFocus) { SetFocus (); - return true; } return false; } - /// - /// Cancelable event fired when the command is invoked. Set - /// to cancel the event. - /// - public event EventHandler Accept; - - /// - /// Called when the command is invoked. Fires the - /// event. - /// - /// If the event was canceled. - public bool? OnAccept () - { - var args = new CancelEventArgs (); - Accept?.Invoke (this, args); - return args.Cancel; - } - /// Invoked when the is changed. public event EventHandler HotKeyChanged; diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/ViewMouse.cs index 8c54f4394..96b46adf1 100644 --- a/Terminal.Gui/View/ViewMouse.cs +++ b/Terminal.Gui/View/ViewMouse.cs @@ -9,17 +9,35 @@ public partial class View /// if want mouse position reports; otherwise, . public virtual bool WantMousePositionReports { get; set; } - /// Event fired when a mouse event is generated. + /// Event fired when a mouse click occurs. + /// + /// + /// Fired when the mouse is either clicked or double-clicked. Check + /// to see which button was clicked. + /// + /// + /// The coordinates are relative to . + /// + /// public event EventHandler MouseClick; - /// Event fired when the view receives the mouse event for the first time. + /// Event fired when the mouse moves into the View's . public event EventHandler MouseEnter; - /// Event fired when the view receives a mouse event for the last time. + /// Event fired when the mouse leaves the View's . public event EventHandler MouseLeave; - /// - public override bool OnMouseEnter (MouseEvent mouseEvent) + // TODO: OnMouseEnter should not be public virtual, but protected. + /// + /// Called when the mouse enters the View's . The view will now receive mouse events until the mouse leaves + /// the view. At which time, will be called. + /// + /// + /// The coordinates are relative to . + /// + /// + /// , if the event was handled, otherwise. + 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; } - - /// Method invoked when a mouse event is generated + // TODO: OnMouseLeave should not be public virtual, but protected. + /// + /// Called when the mouse has moved out of the View's . The view will no longer receive mouse events (until the + /// mouse moves within the view again and is called). + /// /// /// The coordinates are relative to . /// - /// true, if the event was handled, false otherwise. - /// Contains the details about the mouse event. - //public virtual bool MouseEvent (MouseEvent mouseEvent) { return false; } + /// + /// , if the event was handled, otherwise. + public virtual bool OnMouseLeave (MouseEvent mouseEvent) + { + if (!Enabled) + { + return true; + } + if (!CanBeVisible (this)) + { + return false; + } - /// Method invoked when a mouse event is generated + var args = new MouseEventEventArgs (mouseEvent); + MouseLeave?.Invoke (this, args); + + return args.Handled; + } + + // TODO: OnMouseEvent should not be public virtual, but protected. + /// Called when a mouse event occurs within the view's . + /// + /// + /// The coordinates are relative to . + /// + /// /// /// , if the event was handled, otherwise. 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; } - /// - 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); - } - /// Invokes the MouseClick event. + /// + /// + /// Called when the mouse is either clicked or double-clicked. Check + /// to see which button was clicked. + /// + /// 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; } diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index c17014ea3..87d9a735d 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -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 e) @@ -99,29 +105,6 @@ public class Button : View /// public bool NoPadding { get; set; } - /// - public override bool OnMouseEvent (MouseEvent me) - { - if (me.Flags == MouseFlags.Button1Clicked) - { - if (CanFocus && Enabled) - { - if (!HasFocus) - { - SetFocus (); - SetNeedsDisplay (); - Draw (); - } - - OnAccept (); - } - - return true; - } - - return false; - } - /// public override bool OnEnter (View view) { diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index cdf081aaa..fb8665b87 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -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 e) @@ -87,17 +94,6 @@ public class CheckBox : View } } - /// - public override bool OnMouseEvent (MouseEvent me) - { - if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus) - { - return false; - } - - return OnToggled () == true; - } - /// 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; } diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index e2d126754..e4e6b5ac9 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -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 e) @@ -62,50 +68,6 @@ public class Label : View public override bool OnEnter (View view) { Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); - return base.OnEnter (view); } - - /// Method invoked when a mouse event is generated - /// - /// true, if the event was handled, false otherwise. - 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; - } } diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 07a771581..9115d8373 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -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)); diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 96d56aa5c..a7bfccf5f 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -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 (); } - /// - 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; - } - /// public override void OnDrawContent (Rectangle contentArea) { @@ -429,6 +383,41 @@ public class ScrollView : View return false; } + /// + 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); + } + /// 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) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index b44c6a536..a14530643 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -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) diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index a2d9569d2..6a93cc2c9 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -455,9 +455,10 @@ public class TreeView : View, ITreeView where T : class /// if was fired. 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; diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs index d9ee6e80d..c597b18d9 100644 --- a/UICatalog/Scenarios/ASCIICustomButton.cs +++ b/UICatalog/Scenarios/ASCIICustomButton.cs @@ -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 PointerEnter; private void This_MouseClick (object sender, MouseEventEventArgs obj) { OnMouseEvent (obj.MouseEvent); } } diff --git a/UnitTests/Dialogs/WizardTests.cs b/UnitTests/Dialogs/WizardTests.cs index ede643c73..e2c5c19f7 100644 --- a/UnitTests/Dialogs/WizardTests.cs +++ b/UnitTests/Dialogs/WizardTests.cs @@ -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); diff --git a/UnitTests/Input/ResponderTests.cs b/UnitTests/Input/ResponderTests.cs index a2f84b8a1..2c7ba87c2 100644 --- a/UnitTests/Input/ResponderTests.cs +++ b/UnitTests/Input/ResponderTests.cs @@ -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. diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index 02967ec64..c3e9755f3 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -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); diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs index 4c5b5f9e8..72d9e9cd3 100644 --- a/UnitTests/Views/ButtonTests.cs +++ b/UnitTests/Views/ButtonTests.cs @@ -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); diff --git a/UnitTests/Views/CheckBoxTests.cs b/UnitTests/Views/CheckBoxTests.cs index bb387b0b3..c7ce63e36 100644 --- a/UnitTests/Views/CheckBoxTests.cs +++ b/UnitTests/Views/CheckBoxTests.cs @@ -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]