From f2d27a5d084d6cda326ca5e5590a7746c5516647 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 6 Oct 2024 15:41:07 -0600 Subject: [PATCH] Working on ComboBox --- Terminal.Gui/Input/Command.cs | 2 +- Terminal.Gui/Input/CommandContext.cs | 9 +- Terminal.Gui/Input/CommandEventArgs.cs | 15 +++ Terminal.Gui/View/View.Command.cs | 35 ++++-- Terminal.Gui/View/View.Mouse.cs | 11 +- Terminal.Gui/Views/Button.cs | 15 ++- Terminal.Gui/Views/CheckBox.cs | 17 +-- Terminal.Gui/Views/ComboBox.cs | 20 +++- Terminal.Gui/Views/ListView.cs | 8 +- Terminal.Gui/Views/Menu/MenuBar.cs | 2 +- Terminal.Gui/Views/RadioGroup.cs | 8 +- Terminal.Gui/Views/Shortcut.cs | 135 +++++++++++---------- Terminal.Gui/Views/TableView/TableView.cs | 4 +- Terminal.Gui/Views/TextField.cs | 2 + UICatalog/Scenarios/Shortcuts.cs | 10 +- UnitTests/View/Mouse/MouseTests.cs | 62 +++++++++- UnitTests/View/ViewCommandTests.cs | 136 +++++++++++----------- UnitTests/Views/ButtonTests.cs | 46 ++++++-- UnitTests/Views/CheckBoxTests.cs | 4 +- UnitTests/Views/ComboBoxTests.cs | 2 +- UnitTests/Views/ShortcutTests.cs | 45 +++---- UnitTests/Views/TextFieldTests.cs | 54 +++++++++ UnitTests/Views/TreeTableSourceTests.cs | 2 +- 23 files changed, 429 insertions(+), 215 deletions(-) create mode 100644 Terminal.Gui/Input/CommandEventArgs.cs diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs index c8386c3c9..cab5713bd 100644 --- a/Terminal.Gui/Input/Command.cs +++ b/Terminal.Gui/Input/Command.cs @@ -313,4 +313,4 @@ public enum Command Edit, #endregion -} +} \ No newline at end of file diff --git a/Terminal.Gui/Input/CommandContext.cs b/Terminal.Gui/Input/CommandContext.cs index 5b6c23557..b2e26a2cf 100644 --- a/Terminal.Gui/Input/CommandContext.cs +++ b/Terminal.Gui/Input/CommandContext.cs @@ -20,11 +20,13 @@ public record struct CommandContext /// /// /// - public CommandContext (Command command, Key? key, KeyBinding? keyBinding = null) + /// + public CommandContext (Command command, Key? key, KeyBinding? keyBinding = null, object? data = null) { Command = command; Key = key; KeyBinding = keyBinding; + Data = data; } /// @@ -41,4 +43,9 @@ public record struct CommandContext /// The KeyBinding that was used to invoke the , if any. /// public KeyBinding? KeyBinding { get; set; } + + /// + /// Arbitrary data. + /// + public object? Data { get; set; } } diff --git a/Terminal.Gui/Input/CommandEventArgs.cs b/Terminal.Gui/Input/CommandEventArgs.cs new file mode 100644 index 000000000..f12d21be8 --- /dev/null +++ b/Terminal.Gui/Input/CommandEventArgs.cs @@ -0,0 +1,15 @@ +#nullable enable +using System.ComponentModel; + +namespace Terminal.Gui; + +/// +/// Event arguments for events. +/// +public class CommandEventArgs : CancelEventArgs +{ + /// + /// The context for the command. + /// + public CommandContext Context { get; init; } +} diff --git a/Terminal.Gui/View/View.Command.cs b/Terminal.Gui/View/View.Command.cs index 79ef02bfe..9e1e0f03c 100644 --- a/Terminal.Gui/View/View.Command.cs +++ b/Terminal.Gui/View/View.Command.cs @@ -30,17 +30,21 @@ public partial class View // Command APIs }); // Space or single-click - Raise Selected - AddCommand (Command.Select, () => + AddCommand (Command.Select, (ctx) => { - bool? cancelled = RaiseSelected (); - if (cancelled is null or false && CanFocus) + if (RaiseSelected (ctx) is true) + { + return true; + } + + if (CanFocus) { SetFocus (); return true; } - return cancelled is true; + return false; }); } @@ -118,13 +122,13 @@ public partial class View // Command APIs /// If the event was canceled. If the event was raised but not canceled. /// If no event was raised. /// - protected bool? RaiseSelected () + protected bool? RaiseSelected (CommandContext ctx) { - HandledEventArgs args = new (); + CommandEventArgs args = new () { Context = ctx }; // Best practice is to invoke the virtual method first. // This allows derived classes to handle the event and potentially cancel it. - if (OnSelected (args) || args.Handled) + if (OnSelected (args) || args.Cancel) { return true; } @@ -132,7 +136,7 @@ public partial class View // Command APIs // If the event is not canceled by the virtual method, raise the event to notify any external subscribers. Selected?.Invoke (this, args); - return Selected is null ? null : args.Handled; + return Selected is null ? null : args.Cancel; } /// @@ -141,14 +145,14 @@ public partial class View // Command APIs /// /// /// to stop processing. - protected virtual bool OnSelected (HandledEventArgs args) { return false; } + protected virtual bool OnSelected (CommandEventArgs args) { return false; } /// /// Cancelable event raised when the user has selected the View or otherwise changed the state of the View. Set /// /// to cancel the event. /// - public event EventHandler? Selected; + public event EventHandler? Selected; // TODO: What does this event really do? "Called when the user has pressed the View's hot key or otherwise invoked the View's hot key command.???" @@ -291,10 +295,19 @@ public partial class View // Command APIs if (CommandImplementations.TryGetValue (command, out Func? implementation)) { var context = new CommandContext (command, key, keyBinding); // Create the context here - return implementation (context); } return null; } + + public bool? InvokeCommand (Command command, CommandContext ctx) + { + if (CommandImplementations.TryGetValue (command, out Func? implementation)) + { + return implementation (ctx); + } + + return null; + } } diff --git a/Terminal.Gui/View/View.Mouse.cs b/Terminal.Gui/View/View.Mouse.cs index a8812a1f5..764212238 100644 --- a/Terminal.Gui/View/View.Mouse.cs +++ b/Terminal.Gui/View/View.Mouse.cs @@ -372,7 +372,7 @@ public partial class View // Mouse APIs // Always invoke Select command on MouseClick // By default, this will raise Selected/OnSelected - Subclasses can override this via AddCommand (Command.Select ...). - args.Handled = InvokeCommand (Command.Select, null, new KeyBinding ([Command.Select], scope: KeyBindingScope.Focused, boundView: this, context: args.MouseEvent)) == true; + args.Handled = InvokeCommand (Command.Select, ctx: new (Command.Select, key: null, data: args.MouseEvent)) == true; return args.Handled; } @@ -399,18 +399,13 @@ public partial class View // Mouse APIs if (SetPressedHighlight (HighlightStyle.None)) { - // BUGBUG: If we return true here we never generate a mouse click! return true; } // If mouse is still in bounds, generate a click - if (!WantContinuousButtonPressed && Viewport.Contains (mouseEvent.Position)) + if (Viewport.Contains (mouseEvent.Position)) { - var meea = new MouseEventEventArgs (mouseEvent); - - // We can ignore the return value of OnMouseClick; if the click is handled - // meea.Handled and meea.MouseEvent.Handled will be true - OnMouseClick (meea); + return OnMouseClick (new (mouseEvent)); } return mouseEvent.Handled = true; diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 681f0d7d2..477372580 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -70,10 +70,14 @@ public class Button : View, IDesignable // Override default behavior of View AddCommand ( Command.HotKey, - () => + (ctx) => { bool cachedIsDefault = IsDefault; // Supports "Swap Default" in Buttons scenario where IsDefault changes + if (RaiseSelected (ctx) is true) + { + return true; + } bool? handled = RaiseAccepted (); if (handled == true) @@ -132,7 +136,14 @@ public class Button : View, IDesignable } } - private void Button_MouseClick (object sender, MouseEventEventArgs e) { e.Handled = InvokeCommand (Command.HotKey) == true; } + private void Button_MouseClick (object sender, MouseEventEventArgs e) + { + if (e.Handled) + { + return; + } + e.Handled = InvokeCommand (Command.HotKey) == true; + } private void Button_TitleChanged (object sender, EventArgs e) { diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index b91ddb46d..c496690be 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -23,13 +23,13 @@ public class CheckBox : View CanFocus = true; // Select (Space key and single-click) - Advance state and raise Select event - AddCommand (Command.Select, () => + AddCommand (Command.Select, (ctx) => { bool? cancelled = AdvanceCheckState (); if (cancelled is null or false) { - if (RaiseSelected () == true) + if (RaiseSelected (ctx) == true) { return true; } @@ -42,13 +42,13 @@ public class CheckBox : View AddCommand (Command.Accept, () => RaiseAccepted ()); // Hotkey - Advance state and raise Select event - DO NOT raise Accept - AddCommand (Command.HotKey, () => + AddCommand (Command.HotKey, (ctx) => { bool? cancelled = AdvanceCheckState (); if (cancelled is null or false) { - if (RaiseSelected () == true) + if (RaiseSelected (ctx) == true) { return true; } @@ -60,15 +60,6 @@ public class CheckBox : View TitleChanged += Checkbox_TitleChanged; HighlightStyle = DefaultHighlightStyle; - MouseClick += CheckBox_MouseClick; - } - - private void CheckBox_MouseClick (object? sender, MouseEventEventArgs e) - { - if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) - { - e.Handled = InvokeCommand (Command.Select) is true; - } } private void Checkbox_TitleChanged (object? sender, EventArgs e) diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index a4dcb1a6a..db8b8946d 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -6,6 +6,7 @@ // using System.Collections.ObjectModel; +using System.ComponentModel; namespace Terminal.Gui; @@ -32,6 +33,7 @@ public class ComboBox : View, IDesignable _listview = new ComboListView (this, HideDropdownListOnClick) { CanFocus = true, TabStop = TabBehavior.NoStop }; _search.TextChanged += Search_Changed; + _search.Accepted += Search_Accept; _listview.Y = Pos.Bottom (_search); _listview.OpenSelectedItem += (sender, a) => Selected (); @@ -383,7 +385,10 @@ public class ComboBox : View, IDesignable { if (HasItems ()) { - Selected (); + if (Selected ()) + { + //return false; + } return RaiseAccepted () == true; } @@ -653,6 +658,12 @@ public class ComboBox : View, IDesignable SetSearchSet (); } + // Tell TextField to handle Accepted Command (Enter) + void Search_Accept (object sender, HandledEventArgs e) + { + e.Handled = true; + } + private void Search_Changed (object sender, EventArgs e) { if (_source is null) @@ -712,7 +723,7 @@ public class ComboBox : View, IDesignable } } - private void Selected () + private bool Selected () { IsShow = false; _listview.TabStop = TabBehavior.NoStop; @@ -723,7 +734,7 @@ public class ComboBox : View, IDesignable HideList (); IsShow = false; - return; + return false; } SetValue (_listview.SelectedItem > -1 ? _searchSet [_listview.SelectedItem] : _text); @@ -733,6 +744,8 @@ public class ComboBox : View, IDesignable Reset (true); HideList (); IsShow = false; + + return true; } private void SetSearchSet () @@ -788,6 +801,7 @@ public class ComboBox : View, IDesignable _listview.Clear (); _listview.Height = CalculateHeight (); SuperView?.MoveSubviewToStart (this); + _listview.SetFocus (); } private bool UnixEmulation () diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index a1de6f06a..b5ebdd0d0 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -154,11 +154,11 @@ public class ListView : View, IDesignable }); // Select (Space key and single-click) - If markable, change mark and raise Select event - AddCommand (Command.Select, () => + AddCommand (Command.Select, (ctx) => { if (_allowsMarking) { - if (RaiseSelected () == true) + if (RaiseSelected (ctx) == true) { return true; } @@ -174,12 +174,12 @@ public class ListView : View, IDesignable // Hotkey - If none set, select and raise Select event. SetFocus. - DO NOT raise Accept - AddCommand (Command.HotKey, () => + AddCommand (Command.HotKey, (ctx) => { if (SelectedItem == -1) { SelectedItem = 0; - if (RaiseSelected () == true) + if (RaiseSelected (ctx) == true) { return true; diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index c453b4b19..3b3ddfef2 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -137,7 +137,7 @@ public class MenuBar : View, IDesignable }); AddCommand (Command.Select, ctx => { - if (ctx.KeyBinding?.Context is MouseEvent) + if (ctx.Data is MouseEvent) { // HACK: Work around the fact that View.MouseClick always invokes Select return false; diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index 34dca89e8..f5c6d4d1a 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -20,7 +20,7 @@ public class RadioGroup : View, IDesignable, IOrientation // Select (Space key or mouse click) - The default implementation sets focus. RadioGroup does not. AddCommand ( Command.Select, - () => + (ctx) => { bool cursorChanged = false; if (SelectedItem == Cursor) @@ -40,7 +40,7 @@ public class RadioGroup : View, IDesignable, IOrientation if (cursorChanged || selectedItemChanged) { - if (RaiseSelected () == true) + if (RaiseSelected (ctx) == true) { return true; } @@ -83,7 +83,7 @@ public class RadioGroup : View, IDesignable, IOrientation if (selectedItemChanged) { // Doesn't matter if it's handled - RaiseSelected (); + RaiseSelected (ctx); return true; } @@ -93,7 +93,7 @@ public class RadioGroup : View, IDesignable, IOrientation if (SelectedItem == -1 && ChangeSelectedItem (0)) { - if (RaiseSelected () == true) + if (RaiseSelected (ctx) == true) { return true; } diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 6324d84b7..65275fc43 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Diagnostics; namespace Terminal.Gui; @@ -91,7 +92,7 @@ public class Shortcut : View, IOrientation, IDesignable public Shortcut (Key key, string commandText, Action action, string helpText = null) { Id = "_shortcut"; - // Disabled for now due to bugs in highlight handling and mouse clicks - HighlightStyle = HighlightStyle.Pressed; + // Disabled for now due to bs in highlight handling and mouse clicks - HighlightStyle = HighlightStyle.Pressed; CanFocus = true; Width = GetWidthDimAuto (); Height = Dim.Auto (DimAutoStyle.Content, 1); @@ -115,11 +116,28 @@ public class Shortcut : View, IOrientation, IDesignable return true; } - return RaiseSelected (); + return RaiseSelected (ctx); }); // Select (Space key or click) - - AddCommand (Command.Select, ctx => RaiseSelected ()); + AddCommand (Command.Select, ctx => + { + if (ctx.Data != this) + { + ctx.Data = this; + CommandView.InvokeCommand (Command.Select, ctx); + } + + if (RaiseSelected (ctx) is true) + { + return true; + } + + // The default HotKey handler sets Focus + SetFocus (); + + return DispatchAcceptCommand (ctx); + }); TitleChanged += Shortcut_TitleChanged; // This needs to be set before CommandView is set @@ -138,15 +156,15 @@ public class Shortcut : View, IOrientation, IDesignable KeyView.CanFocus = false; Add (KeyView); - // If the user clicks anywhere on the Shortcut... + //// If the user clicks anywhere on the Shortcut... MouseClick += Shortcut_MouseClick; - // If the user clicks on HelpView or KeyView - HelpView.MouseClick += HelpOrKeyView_MouseClick; - KeyView.MouseClick += HelpOrKeyView_MouseClick; + //// If the user clicks on HelpView or KeyView + //HelpView.MouseClick += HelpOrKeyView_MouseClick; + //KeyView.MouseClick += HelpOrKeyView_MouseClick; - HelpView.Selected += HelpAndKeyViewOnSelect; - KeyView.Selected += HelpAndKeyViewOnSelect; + HelpView.Selected += HelpAndKeyViewOnSelected; + KeyView.Selected += HelpAndKeyViewOnSelected; LayoutStarted += OnLayoutStarted; Initialized += OnInitialized; @@ -193,9 +211,14 @@ public class Shortcut : View, IOrientation, IDesignable } } - private void HelpAndKeyViewOnSelect (object sender, HandledEventArgs e) + private void Shortcut_MouseClick (object sender, MouseEventEventArgs e) { - e.Handled = InvokeCommand (Command.Accept) == true; + //e.Handled = true; + } + + private void HelpAndKeyViewOnSelected (object sender, CommandEventArgs e) + { + //e.Handled = InvokeCommand (Command.Select) == true; } [CanBeNull] @@ -386,29 +409,30 @@ public class Shortcut : View, IOrientation, IDesignable } } - private void Shortcut_MouseClick (object sender, MouseEventEventArgs e) - { - // When the Shortcut is clicked, we want to invoke the Command and Set focus - var view = sender as View; + //private void Shortcut_MouseClick (object sender, MouseEventEventArgs e) + //{ + // // When the Shortcut is clicked, we want to invoke the Command and Set focus + // var view = sender as View; - if (!e.Handled) - { - // If the subview (likely CommandView) didn't handle the mouse click, invoke the Select command. - // e.Handled = CommandView.InvokeCommand (Command.Select) == true; - e.Handled = InvokeCommand (Command.HotKey) == true; - } + // if (!e.Handled) + // { + // // If the subview (likely CommandView) didn't handle the mouse click, invoke the Select command. + // // e.Handled = CommandView.InvokeCommand (Command.Select) == true; + // e.Handled = InvokeCommand (Command.HotKey) == true; + // } - //if (CanFocus) - //{ - // SetFocus (); - //} - } + // //if (CanFocus) + // //{ + // // SetFocus (); + // //} + //} - private void HelpOrKeyView_MouseClick (object sender, MouseEventEventArgs e) - { - // Always eat - //e.Handled = true; - } + //private void HelpOrKeyView_MouseClick (object sender, MouseEventEventArgs e) + //{ + // // Always eat + // e.Handled = true; + // InvokeCommand (Command.HotKey); + //} #region IOrientation members @@ -505,7 +529,8 @@ public class Shortcut : View, IOrientation, IDesignable if (_commandView is { }) { - _commandView.Accepted -= CommandViewOnSelected; + _commandView.Selected -= CommandViewOnSelected; + _commandView.Accepted -= CommandViewOnAccepted; Remove (_commandView); _commandView?.Dispose (); } @@ -531,33 +556,27 @@ public class Shortcut : View, IOrientation, IDesignable Title = _commandView.Text; _commandView.Selected += CommandViewOnSelected; - - void CommandViewOnSelected (object sender, HandledEventArgs e) + void CommandViewOnSelected (object sender, CommandEventArgs e) { - // Always eat CommandView.Select - SetFocus (); + if (e.Context.Data != this) + { + // Forward command to ourselves + InvokeCommand (Command.Select, new CommandContext (Command.Select, null, null, this)); + e.Cancel = true; + } + else + { + e.Cancel = true; + } + } + + _commandView.Accepted += CommandViewOnAccepted; + void CommandViewOnAccepted (object sender, HandledEventArgs e) + { + // Always eat CommandView.Accept e.Handled = true; } - _commandView.MouseClick += CommandViewOnMouseClick; - - void CommandViewOnMouseClick (object sender, MouseEventEventArgs e) - { - if (!e.Handled) - { - // If the subview (likely CommandView) didn't handle the mouse click, invoke the Accept Command. - InvokeCommand (Command.Accept); - - // Always eat the mouseclick - e.Handled = true; - } - - // Always Setfocus and invoke Select - SetFocus (); - InvokeCommand (Command.Select); - } - - SetCommandViewDefaultLayout (); SetHelpViewDefaultLayout (); SetKeyViewDefaultLayout (); @@ -800,13 +819,11 @@ public class Shortcut : View, IOrientation, IDesignable { // Invoke Select on the command view to cause it to change state if it wants to // If this causes CommandView to raise Accept, we eat it - CommandView.InvokeCommand (Command.Select); - var cancel = false; - cancel = RaiseAccepted () == true; + cancel = RaiseAccepted () is true; - if (cancel is true) + if (cancel) { return true; } diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index f330be744..baeb79c64 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -242,11 +242,11 @@ public class TableView : View AddCommand ( Command.Select, // was Command.ToggleChecked - () => + (ctx) => { if (ToggleCurrentCellSelection () is true) { - return RaiseSelected () is true; + return RaiseSelected (ctx) is true; } return false; diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 62b33d0c8..e5bd50363 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -404,6 +404,8 @@ public class TextField : View ContextMenu.KeyChanged += ContextMenu_KeyChanged; KeyBindings.Add (ContextMenu.Key, KeyBindingScope.HotKey, Command.Context); + + KeyBindings.Remove (Key.Space); } /// diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 1564cca9e..2f7c4c93c 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -356,15 +356,23 @@ public class Shortcuts : Scenario { shortcut.Selected += (o, args) => { + if (args.Cancel) + { + return; + } eventSource.Add ($"{shortcut!.Id}.Select: {shortcut!.CommandView.Text} {shortcut!.CommandView.GetType ().Name}"); eventLog.MoveDown (); - //args.Handled = true; }; shortcut.CommandView.Selected += (o, args) => { + if (args.Cancel) + { + return; + } eventSource.Add ($"{shortcut!.Id}.CommandView.Select: {shortcut!.CommandView.Text} {shortcut!.CommandView.GetType ().Name}"); eventLog.MoveDown (); + args.Cancel = true; }; shortcut.Accepted += (o, args) => diff --git a/UnitTests/View/Mouse/MouseTests.cs b/UnitTests/View/Mouse/MouseTests.cs index 7440f4e98..7ce54ed4d 100644 --- a/UnitTests/View/Mouse/MouseTests.cs +++ b/UnitTests/View/Mouse/MouseTests.cs @@ -33,6 +33,38 @@ public class MouseTests (ITestOutputHelper output) : TestsAllViews Assert.Equal (expectedHasFocus, testView.HasFocus); } + + [Theory] + [InlineData (false, false, 1)] + [InlineData (true, false, 1)] + [InlineData (true, true, 1)] + public void MouseClick_Raises_Selected (bool canFocus, bool setFocus, int expectedSelectedCount) + { + var superView = new View { CanFocus = true, Height = 1, Width = 15 }; + var focusedView = new View { CanFocus = true, Width = 1, Height = 1 }; + var testView = new View { CanFocus = canFocus, X = 4, Width = 4, Height = 1 }; + superView.Add (focusedView, testView); + + focusedView.SetFocus (); + + Assert.True (superView.HasFocus); + Assert.True (focusedView.HasFocus); + Assert.False (testView.HasFocus); + + if (setFocus) + { + testView.SetFocus (); + } + + int selectedCount = 0; + testView.Selected += (sender, args) => selectedCount++; + + testView.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }); + Assert.True (superView.HasFocus); + Assert.Equal (expectedSelectedCount, selectedCount); + } + + // TODO: Add more tests that ensure the above test works with positive adornments // Test drag to move @@ -207,7 +239,7 @@ public class MouseTests (ITestOutputHelper output) : TestsAllViews [InlineData (MouseFlags.Button2Clicked)] [InlineData (MouseFlags.Button3Clicked)] [InlineData (MouseFlags.Button4Clicked)] - public void WantContinuousButtonPressed_True_Button_Clicked_Clicks (MouseFlags clicked) + public void WantContinuousButtonPressed_True_Button_Clicked_Raises_MouseClick (MouseFlags clicked) { var me = new MouseEvent (); @@ -229,6 +261,34 @@ public class MouseTests (ITestOutputHelper output) : TestsAllViews view.Dispose (); } + + [Theory] + [InlineData (MouseFlags.Button1Clicked)] + [InlineData (MouseFlags.Button2Clicked)] + [InlineData (MouseFlags.Button3Clicked)] + [InlineData (MouseFlags.Button4Clicked)] + public void WantContinuousButtonPressed_True_Button_Clicked_Raises_Selected (MouseFlags clicked) + { + var me = new MouseEvent (); + + var view = new View () + { + Width = 1, + Height = 1, + WantContinuousButtonPressed = true + }; + + var selectedCount = 0; + + view.Selected += (s, e) => selectedCount++; + + me.Flags = clicked; + view.NewMouseEvent (me); + Assert.Equal (1, selectedCount); + + view.Dispose (); + } + [Theory] [InlineData (MouseFlags.Button1Pressed, MouseFlags.Button1Released)] [InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released)] diff --git a/UnitTests/View/ViewCommandTests.cs b/UnitTests/View/ViewCommandTests.cs index caea1b273..b89655665 100644 --- a/UnitTests/View/ViewCommandTests.cs +++ b/UnitTests/View/ViewCommandTests.cs @@ -15,9 +15,9 @@ public class ViewCommandTests (ITestOutputHelper output) Assert.False (view.InvokeCommand (Command.Accept)); // false means it was not handled - Assert.Equal (1, view.OnAcceptCount); + Assert.Equal (1, view.OnAcceptedCount); - Assert.Equal (1, view.AcceptCount); + Assert.Equal (1, view.AcceptedCount); Assert.False (view.HasFocus); } @@ -28,12 +28,12 @@ public class ViewCommandTests (ITestOutputHelper output) var view = new ViewEventTester (); Assert.False (view.HasFocus); - view.HandleOnAccept = true; + view.HandleOnAccepted = true; Assert.True (view.InvokeCommand (Command.Accept)); - Assert.Equal (1, view.OnAcceptCount); + Assert.Equal (1, view.OnAcceptedCount); - Assert.Equal (0, view.AcceptCount); + Assert.Equal (0, view.AcceptedCount); } [Fact] @@ -82,40 +82,40 @@ public class ViewCommandTests (ITestOutputHelper output) view.Add (subview); subview.InvokeCommand (Command.Accept); - Assert.Equal (1, subview.OnAcceptCount); - Assert.Equal (1, view.OnAcceptCount); + Assert.Equal (1, subview.OnAcceptedCount); + Assert.Equal (1, view.OnAcceptedCount); - subview.HandleOnAccept = true; + subview.HandleOnAccepted = true; subview.InvokeCommand (Command.Accept); - Assert.Equal (2, subview.OnAcceptCount); - Assert.Equal (1, view.OnAcceptCount); + Assert.Equal (2, subview.OnAcceptedCount); + Assert.Equal (1, view.OnAcceptedCount); - subview.HandleOnAccept = false; - subview.HandleAccept = true; + subview.HandleOnAccepted = false; + subview.HandleAccepted = true; subview.InvokeCommand (Command.Accept); - Assert.Equal (3, subview.OnAcceptCount); - Assert.Equal (1, view.OnAcceptCount); + Assert.Equal (3, subview.OnAcceptedCount); + Assert.Equal (1, view.OnAcceptedCount); // Add a super view to test deeper hierarchy var superView = new ViewEventTester () { Id = "superView" }; superView.Add (view); subview.InvokeCommand (Command.Accept); - Assert.Equal (4, subview.OnAcceptCount); - Assert.Equal (1, view.OnAcceptCount); - Assert.Equal (0, superView.OnAcceptCount); + Assert.Equal (4, subview.OnAcceptedCount); + Assert.Equal (1, view.OnAcceptedCount); + Assert.Equal (0, superView.OnAcceptedCount); - subview.HandleAccept = false; + subview.HandleAccepted = false; subview.InvokeCommand (Command.Accept); - Assert.Equal (5, subview.OnAcceptCount); - Assert.Equal (2, view.OnAcceptCount); - Assert.Equal (1, superView.OnAcceptCount); + Assert.Equal (5, subview.OnAcceptedCount); + Assert.Equal (2, view.OnAcceptedCount); + Assert.Equal (1, superView.OnAcceptedCount); - view.HandleAccept = true; + view.HandleAccepted = true; subview.InvokeCommand (Command.Accept); - Assert.Equal (6, subview.OnAcceptCount); - Assert.Equal (3, view.OnAcceptCount); - Assert.Equal (1, superView.OnAcceptCount); + Assert.Equal (6, subview.OnAcceptedCount); + Assert.Equal (3, view.OnAcceptedCount); + Assert.Equal (1, superView.OnAcceptedCount); } [Fact] @@ -124,7 +124,7 @@ public class ViewCommandTests (ITestOutputHelper output) var view = new ViewEventTester (); view.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked, Position = Point.Empty, View = view }); - Assert.Equal (0, view.OnAcceptCount); + Assert.Equal (0, view.OnAcceptedCount); } #endregion OnAccept/Accept tests @@ -145,9 +145,9 @@ public class ViewCommandTests (ITestOutputHelper output) Assert.Equal (canFocus, view.InvokeCommand (Command.Select)); - Assert.Equal (1, view.OnSelectCount); + Assert.Equal (1, view.OnSelectedCount); - Assert.Equal (1, view.SelectCount); + Assert.Equal (1, view.SelectedCount); Assert.Equal (canFocus, view.HasFocus); } @@ -158,49 +158,49 @@ public class ViewCommandTests (ITestOutputHelper output) var view = new ViewEventTester (); Assert.False (view.HasFocus); - view.HandleOnSelect = true; + view.HandleOnSelected = true; Assert.True (view.InvokeCommand (Command.Select)); - Assert.Equal (1, view.OnSelectCount); + Assert.Equal (1, view.OnSelectedCount); - Assert.Equal (0, view.SelectCount); + Assert.Equal (0, view.SelectedCount); } [Fact] - public void Select_Handle_Event_OnSelect_Returns_True () + public void Select_Handle_Event_OnSelected_Returns_True () { var view = new View (); - var SelectInvoked = false; + var SelectedInvoked = false; view.Selected += ViewOnSelect; bool? ret = view.InvokeCommand (Command.Select); Assert.True (ret); - Assert.True (SelectInvoked); + Assert.True (SelectedInvoked); return; - void ViewOnSelect (object sender, HandledEventArgs e) + void ViewOnSelect (object sender, CommandEventArgs e) { - SelectInvoked = true; - e.Handled = true; + SelectedInvoked = true; + e.Cancel = true; } } [Fact] - public void Select_Command_Invokes_Select_Event () + public void Select_Command_Invokes_Selected_Event () { var view = new View (); - var Selected = false; + var selected = false; - view.Selected += ViewOnSelect; + view.Selected += ViewOnSelected; view.InvokeCommand (Command.Select); - Assert.True (Selected); + Assert.True (selected); return; - void ViewOnSelect (object sender, HandledEventArgs e) { Selected = true; } + void ViewOnSelected (object sender, CommandEventArgs e) { selected = true; } } [Fact] @@ -209,7 +209,7 @@ public class ViewCommandTests (ITestOutputHelper output) var view = new ViewEventTester (); view.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked, Position = Point.Empty, View = view }); - Assert.Equal (1, view.OnSelectCount); + Assert.Equal (1, view.OnSelectedCount); } #endregion OnSelect/Select tests @@ -237,66 +237,66 @@ public class ViewCommandTests (ITestOutputHelper output) Accepted += (s, a) => { - a.Handled = HandleAccept; - AcceptCount++; + a.Handled = HandleAccepted; + AcceptedCount++; }; HotKeyHandled += (s, a) => { - a.Handled = HandleHotKeyCommand; - HotKeyCommandCount++; + a.Handled = HandleHotKeyHandled; + HotKeyHandledCount++; }; Selected += (s, a) => { - a.Handled = HandleSelect; - SelectCount++; + a.Cancel = HandleSelected; + SelectedCount++; }; } - public int OnAcceptCount { get; set; } - public int AcceptCount { get; set; } - public bool HandleOnAccept { get; set; } + public int OnAcceptedCount { get; set; } + public int AcceptedCount { get; set; } + public bool HandleOnAccepted { get; set; } /// protected override bool OnAccepted (HandledEventArgs args) { - OnAcceptCount++; + OnAcceptedCount++; - return HandleOnAccept; + return HandleOnAccepted; } - public bool HandleAccept { get; set; } + public bool HandleAccepted { get; set; } - public int OnHotKeyCommandCount { get; set; } - public int HotKeyCommandCount { get; set; } - public bool HandleOnHotKeyCommand { get; set; } + public int OnHotKeyHandledCount { get; set; } + public int HotKeyHandledCount { get; set; } + public bool HandleOnHotKeyHandled { get; set; } /// protected override bool OnHotKeyHandled (HandledEventArgs args) { - OnHotKeyCommandCount++; + OnHotKeyHandledCount++; - return HandleOnHotKeyCommand; + return HandleOnHotKeyHandled; } - public bool HandleHotKeyCommand { get; set; } + public bool HandleHotKeyHandled { get; set; } - public int OnSelectCount { get; set; } - public int SelectCount { get; set; } - public bool HandleOnSelect { get; set; } + public int OnSelectedCount { get; set; } + public int SelectedCount { get; set; } + public bool HandleOnSelected { get; set; } /// - protected override bool OnSelected (HandledEventArgs args) + protected override bool OnSelected (CommandEventArgs args) { - OnSelectCount++; + OnSelectedCount++; - return HandleOnSelect; + return HandleOnSelected; } - public bool HandleSelect { get; set; } + public bool HandleSelected { get; set; } } } diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs index 4c6ca5e83..62b5a6293 100644 --- a/UnitTests/Views/ButtonTests.cs +++ b/UnitTests/Views/ButtonTests.cs @@ -608,21 +608,33 @@ public class ButtonTests (ITestOutputHelper output) WantContinuousButtonPressed = true }; - var acceptCount = 0; + var selectedCount = 0; - button.Accepted += (s, e) => acceptCount++; + button.Selected += (s, e) => selectedCount++; + var acceptedCount = 0; + button.Accepted += (s, e) => + { + acceptedCount++; + e.Handled = true; + }; + me = new MouseEvent (); me.Flags = pressed; button.NewMouseEvent (me); - Assert.Equal (1, acceptCount); + Assert.Equal (0, selectedCount); + Assert.Equal (0, acceptedCount); + me = new MouseEvent (); me.Flags = released; button.NewMouseEvent (me); - Assert.Equal (1, acceptCount); + Assert.Equal (0, selectedCount); + Assert.Equal (0, acceptedCount); + me = new MouseEvent (); me.Flags = clicked; button.NewMouseEvent (me); - Assert.Equal (1, acceptCount); + Assert.Equal (1, selectedCount); + Assert.Equal (1, acceptedCount); button.Dispose (); } @@ -632,7 +644,7 @@ public class ButtonTests (ITestOutputHelper output) [InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released)] [InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released)] [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released)] - public void WantContinuousButtonPressed_True_ButtonPressRelease_Accepts (MouseFlags pressed, MouseFlags released) + public void WantContinuousButtonPressed_True_ButtonPressRelease_Does_Not_Raise_Selected_Or_Accepted (MouseFlags pressed, MouseFlags released) { var me = new MouseEvent (); @@ -643,17 +655,31 @@ public class ButtonTests (ITestOutputHelper output) WantContinuousButtonPressed = true }; - var acceptCount = 0; + var acceptedCount = 0; - button.Accepted += (s, e) => acceptCount++; + button.Accepted += (s, e) => + { + acceptedCount++; + e.Handled = true; + }; + + var selectedCount = 0; + + button.Selected += (s, e) => + { + selectedCount++; + e.Cancel = true; + }; me.Flags = pressed; button.NewMouseEvent (me); - Assert.Equal (1, acceptCount); + Assert.Equal (0, acceptedCount); + Assert.Equal (0, selectedCount); me.Flags = released; button.NewMouseEvent (me); - Assert.Equal (1, acceptCount); + Assert.Equal (0, acceptedCount); + Assert.Equal (0, selectedCount); button.Dispose (); } diff --git a/UnitTests/Views/CheckBoxTests.cs b/UnitTests/Views/CheckBoxTests.cs index da4ec69fa..5b2d5a720 100644 --- a/UnitTests/Views/CheckBoxTests.cs +++ b/UnitTests/Views/CheckBoxTests.cs @@ -576,10 +576,10 @@ public class CheckBoxTests (ITestOutputHelper output) return; - void OnSelected (object sender, HandledEventArgs e) + void OnSelected (object sender, CommandEventArgs e) { checkedInvoked = true; - e.Handled = true; + e.Cancel = true; } } diff --git a/UnitTests/Views/ComboBoxTests.cs b/UnitTests/Views/ComboBoxTests.cs index 0a8b1891b..b03eb58f2 100644 --- a/UnitTests/Views/ComboBoxTests.cs +++ b/UnitTests/Views/ComboBoxTests.cs @@ -837,7 +837,7 @@ Three ", Assert.False (opened); cb.Text = "Tw"; - Assert.True (Application.OnKeyDown (Key.Enter)); + Assert.False (Application.OnKeyDown (Key.Enter)); Assert.True (opened); Assert.Equal ("Tw", cb.Text); Assert.False (cb.IsShow); diff --git a/UnitTests/Views/ShortcutTests.cs b/UnitTests/Views/ShortcutTests.cs index 5ae5268ef..65112c52c 100644 --- a/UnitTests/Views/ShortcutTests.cs +++ b/UnitTests/Views/ShortcutTests.cs @@ -409,10 +409,9 @@ public class ShortcutTests [InlineData (7, 1)] [InlineData (8, 1)] [InlineData (9, 0)] - [AutoInitShutdown] - public void MouseClick_Fires_Accept (int x, int expectedAccept) + public void MouseClick_Raises_Accepted (int x, int expectedAccepted) { - var current = new Toplevel (); + Application.Top = new Toplevel (); var shortcut = new Shortcut { @@ -420,9 +419,9 @@ public class ShortcutTests Text = "0", Title = "C" }; - current.Add (shortcut); - - Application.Begin (current); + Application.Top.Add (shortcut); + Application.Top.SetRelativeLayout (new (100, 100)); + Application.Top.LayoutSubviews (); var accepted = 0; shortcut.Accepted += (s, e) => accepted++; @@ -434,9 +433,10 @@ public class ShortcutTests Flags = MouseFlags.Button1Clicked }); - Assert.Equal (expectedAccept, accepted); + Assert.Equal (expectedAccepted, accepted); - current.Dispose (); + Application.Top.Dispose (); + Application.ResetState (ignoreDisposed: true); } @@ -447,7 +447,7 @@ public class ShortcutTests [InlineData (-1, 0, 0, 0, 0)] [InlineData (0, 0, 1, 1, 1)] // mouseX = 0 is on the CommandView.Margin, so Shortcut will get MouseClick [InlineData (1, 0, 1, 1, 1)] // mouseX = 1 is on the CommandView, so CommandView will get MouseClick - [InlineData (2, 1, 1, 1, 1)] // mouseX = 2 is on the CommandView.Margin, so Shortcut will get MouseClick + [InlineData (2, 0, 1, 1, 1)] // mouseX = 2 is on the CommandView.Margin, so Shortcut will get MouseClick [InlineData (3, 0, 1, 1, 1)] [InlineData (4, 0, 1, 1, 1)] [InlineData (5, 0, 1, 1, 1)] @@ -465,7 +465,7 @@ public class ShortcutTests //[InlineData (7, 1, 0)] //[InlineData (8, 1, 0)] //[InlineData (9, 0, 0)] - public void MouseClick_Default_CommandView_Raises_Accept_Select_Correctly (int mouseX, int expectedCommandViewAccept, int expectedCommandViewSelect, + public void MouseClick_Default_CommandView_Raises_Accepted_Select_Correctly (int mouseX, int expectedCommandViewAccept, int expectedCommandViewSelect, int expectedShortcutAccept, int expectedShortcutSelect) { Application.Top = new Toplevel (); @@ -526,7 +526,7 @@ public class ShortcutTests // " C 0 A " [InlineData (-1, 0, 0)] [InlineData (0, 1, 0)] - [InlineData (1, 1, 1)] + [InlineData (1, 1, 0)] [InlineData (2, 1, 0)] [InlineData (3, 1, 0)] [InlineData (4, 1, 0)] @@ -535,7 +535,7 @@ public class ShortcutTests [InlineData (7, 1, 0)] [InlineData (8, 1, 0)] [InlineData (9, 0, 0)] - public void MouseClick_Button_CommandView_Raises_Shortcut_Accept + public void MouseClick_Button_CommandView_Raises_Shortcut_Accepted (int mouseX, int expectedAccept, int expectedButtonAccept) { Application.Top = new Toplevel (); @@ -557,15 +557,16 @@ public class ShortcutTests shortcut.CommandView.Accepted += (s, e) => { buttonAccepted++; - // Must indicate handled - e.Handled = true; }; Application.Top.Add (shortcut); + Application.Top.SetRelativeLayout (new (100, 100)); + Application.Top.LayoutSubviews (); var accepted = 0; - shortcut.Accepted += (s, e) => accepted++; - - //Assert.True (shortcut.HasFocus); + shortcut.Accepted += (s, e) => + { + accepted++; + }; Application.OnMouseEvent ( new () @@ -574,8 +575,8 @@ public class ShortcutTests Flags = MouseFlags.Button1Clicked }); - Assert.Equal (expectedButtonAccept, buttonAccepted); Assert.Equal (expectedAccept, accepted); + Assert.Equal (expectedButtonAccept, buttonAccepted); Application.Top.Dispose (); Application.ResetState (true); @@ -586,7 +587,7 @@ public class ShortcutTests [InlineData (true, KeyCode.C, 1)] [InlineData (true, KeyCode.C | KeyCode.AltMask, 1)] [InlineData (true, KeyCode.Enter, 1)] - [InlineData (true, KeyCode.Space, 0)] + [InlineData (true, KeyCode.Space, 1)] [InlineData (true, KeyCode.F1, 0)] [InlineData (false, KeyCode.A, 1)] [InlineData (false, KeyCode.C, 1)] @@ -626,7 +627,7 @@ public class ShortcutTests [InlineData (KeyCode.C, 1)] [InlineData (KeyCode.C | KeyCode.AltMask, 1)] [InlineData (KeyCode.Enter, 1)] - [InlineData (KeyCode.Space, 0)] + [InlineData (KeyCode.Space, 1)] [InlineData (KeyCode.F1, 0)] public void KeyDown_App_Scope_Invokes_Accept (KeyCode key, int expectedAccept) { @@ -658,7 +659,7 @@ public class ShortcutTests [InlineData (true, KeyCode.C, 1)] [InlineData (true, KeyCode.C | KeyCode.AltMask, 1)] [InlineData (true, KeyCode.Enter, 1)] - [InlineData (true, KeyCode.Space, 0)] + [InlineData (true, KeyCode.Space, 1)] [InlineData (true, KeyCode.F1, 0)] [InlineData (false, KeyCode.A, 1)] [InlineData (false, KeyCode.C, 1)] @@ -698,7 +699,7 @@ public class ShortcutTests [InlineData (true, KeyCode.C, 1)] [InlineData (true, KeyCode.C | KeyCode.AltMask, 1)] [InlineData (true, KeyCode.Enter, 1)] - [InlineData (true, KeyCode.Space, 0)] + [InlineData (true, KeyCode.Space, 1)] [InlineData (true, KeyCode.F1, 0)] [InlineData (false, KeyCode.A, 1)] [InlineData (false, KeyCode.C, 1)] diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index 8220d949a..ef5f3ee71 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -516,6 +516,60 @@ public class TextFieldTests (ITestOutputHelper output) Assert.True (tf.IsDirty); } + [Fact] + public void Space_Does_Not_Raise_Selected () + { + TextField tf = new (); + + tf.Selected += (sender, args) => Assert.Fail ("Selected should not be raied."); + + Application.Top = new Toplevel (); + Application.Top.Add (tf); + tf.SetFocus (); + Application.OnKeyDown (Key.Space); + + Application.Top.Dispose (); + Application.ResetState (ignoreDisposed: true); + } + + [Fact] + public void Enter_Does_Not_Raise_Selected () + { + TextField tf = new (); + + int selectedCount = 0; + tf.Selected += (sender, args) => selectedCount++; + + Application.Top = new Toplevel (); + Application.Top.Add (tf); + tf.SetFocus (); + Application.OnKeyDown (Key.Enter); + + Assert.Equal (0, selectedCount); + + Application.Top.Dispose (); + Application.ResetState (ignoreDisposed: true); + } + + [Fact] + public void Enter_Raises_Accepted () + { + TextField tf = new (); + + int acceptedCount = 0; + tf.Accepted += (sender, args) => acceptedCount++; + + Application.Top = new Toplevel (); + Application.Top.Add (tf); + tf.SetFocus (); + Application.OnKeyDown (Key.Enter); + + Assert.Equal (1, acceptedCount); + + Application.Top.Dispose (); + Application.ResetState (ignoreDisposed: true); + } + [Fact] [AutoInitShutdown (useFakeClipboard: true)] public void KeyBindings_Command () diff --git a/UnitTests/Views/TreeTableSourceTests.cs b/UnitTests/Views/TreeTableSourceTests.cs index 5f17475cc..04fcf420e 100644 --- a/UnitTests/Views/TreeTableSourceTests.cs +++ b/UnitTests/Views/TreeTableSourceTests.cs @@ -150,7 +150,7 @@ public class TreeTableSourceTests : IDisposable } [Fact] - [AutoInitShutdown] + [AutoInitShutdown (configLocation:ConfigurationManager.ConfigLocations.DefaultOnly)] public void TestTreeTableSource_CombinedWithCheckboxes () { Toplevel top = new ();