diff --git a/CommunityToolkitExample/LoginView.cs b/CommunityToolkitExample/LoginView.cs index 726ab009f..2e72b45cf 100644 --- a/CommunityToolkitExample/LoginView.cs +++ b/CommunityToolkitExample/LoginView.cs @@ -19,15 +19,19 @@ internal partial class LoginView : IRecipient> { ViewModel.Password = passwordInput.Text; }; - loginButton.Accepting += (_, _) => + loginButton.Accepting += (_, e) => { if (!ViewModel.CanLogin) { return; } ViewModel.LoginCommand.Execute (null); + // Anytime Accepting is handled, make sure to set e.Cancel to false. + e.Cancel = false; }; - clearButton.Accepting += (_, _) => + clearButton.Accepting += (_, e) => { ViewModel.ClearCommand.Execute (null); + // Anytime Accepting is handled, make sure to set e.Cancel to false. + e.Cancel = false; }; Initialized += (_, _) => { ViewModel.Initialized (); }; diff --git a/Example/Example.cs b/Example/Example.cs index ede120378..e260230c5 100644 --- a/Example/Example.cs +++ b/Example/Example.cs @@ -78,6 +78,8 @@ public class ExampleWindow : Window { MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok"); } + // Anytime Accepting is handled, make sure to set e.Cancel to false. + e.Cancel = false; }; // Add the views to the Window diff --git a/NativeAot/Program.cs b/NativeAot/Program.cs index e9c8cf77f..d186e33a5 100644 --- a/NativeAot/Program.cs +++ b/NativeAot/Program.cs @@ -105,6 +105,8 @@ public class ExampleWindow : Window { MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok"); } + // Anytime Accepting is handled, make sure to set e.Cancel to false. + e.Cancel = false; }; // Add the views to the Window diff --git a/SelfContained/Program.cs b/SelfContained/Program.cs index c57f39e58..886fd334b 100644 --- a/SelfContained/Program.cs +++ b/SelfContained/Program.cs @@ -104,6 +104,8 @@ public class ExampleWindow : Window { MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok"); } + // Anytime Accepting is handled, make sure to set e.Cancel to false. + e.Cancel = false; }; // Add the views to the Window diff --git a/Terminal.Gui/FileServices/DefaultFileOperations.cs b/Terminal.Gui/FileServices/DefaultFileOperations.cs index 9ba859284..a8fad43ef 100644 --- a/Terminal.Gui/FileServices/DefaultFileOperations.cs +++ b/Terminal.Gui/FileServices/DefaultFileOperations.cs @@ -139,6 +139,8 @@ public class DefaultFileOperations : IFileOperations { confirm = true; Application.RequestStop (); + // Anytime Accepting is handled, make sure to set e.Cancel to false. + e.Cancel = false; }; var btnCancel = new Button { Text = Strings.btnCancel }; @@ -146,6 +148,8 @@ public class DefaultFileOperations : IFileOperations { confirm = false; Application.RequestStop (); + // Anytime Accepting is handled, make sure to set e.Cancel to false. + e.Cancel = false; }; var lbl = new Label { Text = Strings.fdRenamePrompt }; diff --git a/Terminal.Gui/View/View.Mouse.cs b/Terminal.Gui/View/View.Mouse.cs index 41f68a320..f7d6d57a2 100644 --- a/Terminal.Gui/View/View.Mouse.cs +++ b/Terminal.Gui/View/View.Mouse.cs @@ -559,7 +559,8 @@ public partial class View // Mouse APIs // If mouse is still in bounds, generate a click if (!WantMousePositionReports && Viewport.Contains (mouseEvent.Position)) { - return RaiseMouseClickEvent (mouseEvent); + + return RaiseMouseClickEvent (mouseEvent); } return mouseEvent.Handled = true; diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 46a1314e3..7738d2136 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -165,10 +165,10 @@ public class Button : View, IDesignable /// If : /// /// - /// - the Button will display an indicator that it is the default Button. + /// - The Button will display an indicator that it is the default Button. /// /// - /// - when clicked, if the Accepting event is not handled, will be + /// - When clicked, if the Accepting event is not handled, will be /// invoked on the SuperView. /// /// @@ -197,7 +197,7 @@ public class Button : View, IDesignable /// /// Gets or sets whether the Button will show decorations or not. If the glyphs that normally - /// brakcet the Button Title and the indicator will not be shown. + /// bracket the Button Title and the indicator will not be shown. /// public bool NoDecorations { get; set; } diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index 2a67a9bc0..2e8ccbc82 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -3,7 +3,7 @@ /// /// The Label displays text that describes the View next in the . When /// Label -/// recieves a command it will pass it to the next in +/// receives a command it will pass it to the next in /// . /// /// @@ -13,7 +13,7 @@ /// /// If is and the use clicks on the Label, /// the will be invoked on the next in -/// ." +/// . /// /// public class Label : View, IDesignable @@ -31,7 +31,6 @@ public class Label : View, IDesignable MouseClick += Label_MouseClick; } - // TODO: base raises Select, but we want to raise HotKey. This can be simplified? private void Label_MouseClick (object sender, MouseEventArgs e) { if (!CanFocus) @@ -74,12 +73,15 @@ public class Label : View, IDesignable return true; } - int me = SuperView?.Subviews.IndexOf (this) ?? -1; - - if (me != -1 && me < SuperView?.Subviews.Count - 1) + if (HotKey.IsValid) { + int me = SuperView?.Subviews.IndexOf (this) ?? -1; - return SuperView?.Subviews [me + 1].InvokeCommand (Command.HotKey) == true; + if (me != -1 && me < SuperView?.Subviews.Count - 1) + { + + return SuperView?.Subviews [me + 1].InvokeCommand (Command.HotKey) == true; + } } return false; diff --git a/UICatalog/Scenarios/Generic.cs b/UICatalog/Scenarios/Generic.cs index dc9131623..3fc926ded 100644 --- a/UICatalog/Scenarios/Generic.cs +++ b/UICatalog/Scenarios/Generic.cs @@ -20,7 +20,13 @@ public sealed class Generic : Scenario var button = new Button { Id = "button", X = Pos.Center (), Y = 1, Text = "_Press me!" }; - button.Accepting += (s, e) => MessageBox.ErrorQuery ("Error", "You pressed the button!", "_Ok"); + button.Accepting += (s, e) => + { + // Anytime Accepting is handled, make sure to set e.Cancel to false. + e.Cancel = true; + MessageBox.ErrorQuery ("Error", "You pressed the button!", "_Ok"); + }; + appWindow.Add (button); // Run - Start the application. diff --git a/UnitTests/View/ViewCommandTests.cs b/UnitTests/View/ViewCommandTests.cs index ff42af652..a13243fae 100644 --- a/UnitTests/View/ViewCommandTests.cs +++ b/UnitTests/View/ViewCommandTests.cs @@ -10,7 +10,7 @@ public class ViewCommandTests var view = new ViewEventTester (); Assert.False (view.HasFocus); - Assert.False (view.InvokeCommand (Command.Accept)); // false means it was not handled + Assert.False (view.InvokeCommand (Command.Accept)); // there's no superview, so it should return true? Assert.Equal (1, view.OnAcceptedCount); @@ -124,6 +124,148 @@ public class ViewCommandTests Assert.Equal (0, view.OnAcceptedCount); } + // See https://github.com/gui-cs/Terminal.Gui/issues/3913 + [Fact] + public void Button_IsDefault_Raises_Accepted_Correctly () + { + int A_AcceptedCount = 0; + bool A_CancelAccepting = false; + + int B_AcceptedCount = 0; + bool B_CancelAccepting = false; + + var w = new Window () + { + BorderStyle = LineStyle.None, + Width = 10, + Height = 10 + }; + + var btnA = new Button () + { + Width = 3, + IsDefault = true + }; + btnA.Accepting += (s, e) => + { + A_AcceptedCount++; + e.Cancel = A_CancelAccepting; + }; + + var btnB = new Button () + { + Width = 3, + X = Pos.Right (btnA) + }; + + btnB.Accepting += (s, e) => + { + B_AcceptedCount++; + e.Cancel = B_CancelAccepting; + }; + w.Add (btnA, btnB); + + w.LayoutSubviews (); + + Application.Begin (w); + Assert.Same (Application.Top, w); + + // Click button 2 + var btn2Frame = btnB.FrameToScreen (); + + Application.RaiseMouseEvent ( + new MouseEventArgs () + { + ScreenPosition = btn2Frame.Location, + Flags = MouseFlags.Button1Clicked + }); + + // Button A should have been accepted because B didn't cancel and A IsDefault + Assert.Equal (1, A_AcceptedCount); + Assert.Equal (1, B_AcceptedCount); + + B_CancelAccepting = true; + Application.RaiseMouseEvent ( + new MouseEventArgs () + { + ScreenPosition = btn2Frame.Location, + Flags = MouseFlags.Button1Clicked + }); + + // Button A (IsDefault) should NOT have been accepted because B canceled + Assert.Equal (1, A_AcceptedCount); + Assert.Equal (2, B_AcceptedCount); + } + + // See: https://github.com/gui-cs/Terminal.Gui/issues/3905 + [Fact] + public void Button_CanFocus_False_Raises_Accepted_Correctly () + { + int wAcceptedCount = 0; + bool wCancelAccepting = false; + var w = new Window () + { + Title = "Window", + BorderStyle = LineStyle.None, + Width = 10, + Height = 10 + }; + + w.Accepting += (s, e) => + { + wAcceptedCount++; + e.Cancel = wCancelAccepting; + }; + + int btnAcceptedCount = 0; + bool btnCancelAccepting = false; + var btn = new Button () + { + Title = "Button", + Width = 3, + IsDefault = true, + }; + btn.CanFocus = true; + + btn.Accepting += (s, e) => + { + btnAcceptedCount++; + e.Cancel = btnCancelAccepting; + }; + + w.Add (btn); + + w.LayoutSubviews (); + + Application.Begin (w); + + // Click button just like a driver would + var btnFrame = btn.FrameToScreen (); + Application.RaiseMouseEvent ( + new MouseEventArgs () + { + ScreenPosition = btnFrame.Location, + Flags = MouseFlags.Button1Pressed + }); + + Application.RaiseMouseEvent ( + new MouseEventArgs () + { + ScreenPosition = btnFrame.Location, + Flags = MouseFlags.Button1Released + }); + + Application.RaiseMouseEvent ( + new MouseEventArgs () + { + ScreenPosition = btnFrame.Location, + Flags = MouseFlags.Button1Clicked + }); + + Assert.Equal (1, btnAcceptedCount); + Assert.Equal (2, wAcceptedCount); + } + #endregion OnAccept/Accept tests #region OnSelect/Select tests @@ -140,7 +282,7 @@ public class ViewCommandTests Assert.Equal (canFocus, view.CanFocus); Assert.False (view.HasFocus); - Assert.Equal (canFocus, view.InvokeCommand (Command.Select)); + view.InvokeCommand (Command.Select); Assert.Equal (1, view.OnSelectingCount); diff --git a/UnitTests/Views/LabelTests.cs b/UnitTests/Views/LabelTests.cs index b798fbe80..061e4b7bd 100644 --- a/UnitTests/Views/LabelTests.cs +++ b/UnitTests/Views/LabelTests.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; @@ -30,11 +31,13 @@ public class LabelTests (ITestOutputHelper output) Assert.Equal ("Hello", label.TitleTextFormatter.Text); } - [Fact] - public void HotKey_Command_SetsFocus_OnNextSubview () + [Theory] + [CombinatorialData] + public void HotKey_Command_SetsFocus_OnNextSubview (bool hasHotKey) { var superView = new View { CanFocus = true }; var label = new Label (); + label.HotKey = hasHotKey ? Key.A.WithAlt : Key.Empty; var nextSubview = new View { CanFocus = true }; superView.Add (label, nextSubview); superView.BeginInit (); @@ -45,15 +48,18 @@ public class LabelTests (ITestOutputHelper output) label.InvokeCommand (Command.HotKey); Assert.False (label.HasFocus); - Assert.True (nextSubview.HasFocus); + Assert.Equal (hasHotKey, nextSubview.HasFocus); } - [Fact] - public void MouseClick_SetsFocus_OnNextSubview () + [Theory] + [CombinatorialData] + public void MouseClick_SetsFocus_OnNextSubview (bool hasHotKey) { var superView = new View { CanFocus = true, Height = 1, Width = 15 }; var focusedView = new View { CanFocus = true, Width = 1, Height = 1 }; - var label = new Label { X = 2, Title = "_x" }; + var label = new Label { X = 2 }; + label.HotKey = hasHotKey ? Key.X.WithAlt : Key.Empty; + var nextSubview = new View { CanFocus = true, X = 4, Width = 4, Height = 1 }; superView.Add (focusedView, label, nextSubview); superView.BeginInit (); @@ -65,7 +71,7 @@ public class LabelTests (ITestOutputHelper output) label.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }); Assert.False (label.HasFocus); - Assert.True (nextSubview.HasFocus); + Assert.Equal (hasHotKey, nextSubview.HasFocus); } [Fact] diff --git a/local_packages/Terminal.Gui.2.0.0.nupkg b/local_packages/Terminal.Gui.2.0.0.nupkg index a24644f0e..ee344e277 100644 Binary files a/local_packages/Terminal.Gui.2.0.0.nupkg and b/local_packages/Terminal.Gui.2.0.0.nupkg differ diff --git a/local_packages/Terminal.Gui.2.0.0.snupkg b/local_packages/Terminal.Gui.2.0.0.snupkg index cb57cf6ea..f2858ba0b 100644 Binary files a/local_packages/Terminal.Gui.2.0.0.snupkg and b/local_packages/Terminal.Gui.2.0.0.snupkg differ