From dd12df7fb7644e7c4c240261d2e267bff364968f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 14:41:20 -0700 Subject: [PATCH] Fixes #4176. Removes `View.MouseClick` (#4450) * Initial plan * Add comprehensive MouseClick removal analysis Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix code review feedback on analysis document Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add actual line numbers to analysis document Co-authored-by: tig <585482+tig@users.noreply.github.com> * Final polish on analysis document - clarify TextView Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 3: Port 6 simple Views from MouseClick to Selecting/MouseBindings Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 2: Port 10 Scenarios from MouseClick to Selecting/remove Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update analysis document - Phases 2 & 3 complete Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix failing tests - Button multi-button support and Label focus handling Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 4: Port ScrollBar from OnMouseClick to OnSelecting; remove analysis doc Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 5: Update/remove tests that explicitly test MouseClick API Co-authored-by: tig <585482+tig@users.noreply.github.com> * Code cleanup * Remove MouseClick event Consolidated mouse event handling by removing the `MouseClick` event and integrating its functionality into the `MouseEvent` event. Simplified `MouseEventArgs` documentation and added support for invoking commands bound to mouse events. Reorganized code by removing `Mouse Pressed Events` and `Mouse Click Events` regions, introducing a new `WhenGrabbed Handlers` region. Updated tests to replace `MouseClick` with `MouseEvent`, adjusted test logic, and improved variable naming for clarity. Removed redundant assertions and unused code related to `MouseClick`. Improved event propagation logic to ensure proper handling of unhandled events. Performed general code cleanup to enhance readability and maintainability. * Updated deep dives. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> Co-authored-by: Tig --- .../Scenarios/CharacterMap/CharacterMap.cs | 50 +++-- Examples/UICatalog/Scenarios/ContextMenus.cs | 82 ++++--- .../Scenarios/EditorsAndHelpers/EventLog.cs | 11 +- Examples/UICatalog/Scenarios/ListColumns.cs | 29 ++- Examples/UICatalog/Scenarios/Mouse.cs | 24 +-- Examples/UICatalog/Scenarios/ScrollBarDemo.cs | 203 +++++++++--------- Examples/UICatalog/Scenarios/TableEditor.cs | 48 +++-- .../Scenarios/TextAlignmentAndDirection.cs | 199 ++++++++--------- .../UICatalog/Scenarios/TreeViewFileSystem.cs | 20 +- .../UICatalog/Scenarios/ViewExperiments.cs | 19 +- Terminal.Gui/Input/Mouse/MouseEventArgs.cs | 5 +- Terminal.Gui/ViewBase/View.Mouse.cs | 99 +-------- Terminal.Gui/Views/Button.cs | 19 +- Terminal.Gui/Views/FileDialogs/FileDialog.cs | 22 +- Terminal.Gui/Views/Label.cs | 21 +- Terminal.Gui/Views/ScrollBar/ScrollBar.cs | 18 +- Terminal.Gui/Views/TabView/TabRow.cs | 10 +- Terminal.Gui/Views/TabView/TabView.cs | 23 +- .../TableView/CheckBoxTableSourceWrapper.cs | 18 +- .../Views/TableView/TreeTableSource.cs | 16 +- .../GuiTestContextMouseEventTests.cs | 4 +- .../Mouse/ApplicationMouseTests.cs | 2 +- Tests/UnitTests/View/Mouse/MouseTests.cs | 82 +++---- Tests/UnitTests/Views/ShortcutTests.cs | 6 +- Tests/UnitTests/Views/TextFieldTests.cs | 2 +- .../Application/MouseTests.cs | 68 +++--- .../ViewBase/Mouse/MouseEventRoutingTests.cs | 84 ++++---- docfx/docs/View.md | 3 +- docfx/docs/migratingfromv1.md | 116 +++++++++- docfx/docs/mouse.md | 176 +++++++++++++-- docfx/docs/navigation.md | 2 +- 31 files changed, 811 insertions(+), 670 deletions(-) diff --git a/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs b/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs index fba9130e3..daddc307e 100644 --- a/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs +++ b/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs @@ -25,7 +25,7 @@ public class CharacterMap : Scenario public override List GetDemoKeyStrokes () { - List keys = new (); + List keys = []; for (var i = 0; i < 200; i++) { @@ -91,7 +91,7 @@ public class CharacterMap : Scenario }; top.Add (jumpEdit); - _charMap.SelectedCodePointChanged += (sender, args) => + _charMap.SelectedCodePointChanged += (_, args) => { if (Rune.IsValid (args.Value)) { @@ -134,27 +134,33 @@ public class CharacterMap : Scenario _categoryList.Table = CreateCategoryTable (0, isDescending); // if user clicks the mouse in TableView - _categoryList.MouseClick += (s, e) => - { - _categoryList.ScreenToCell (e.Position, out int? clickedCol); + _categoryList.Selecting += (_, e) => + { + // Only handle mouse clicks + if (e.Context is not CommandContext { Binding.MouseEventArgs: { } mouseArgs }) + { + return; + } - if (clickedCol != null && e.Flags.HasFlag (MouseFlags.Button1Clicked)) - { - EnumerableTableSource table = (EnumerableTableSource)_categoryList.Table; - string prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category; - isDescending = !isDescending; + _categoryList.ScreenToCell (mouseArgs.Position, out int? clickedCol); - _categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending); + if (clickedCol != null && mouseArgs.Flags.HasFlag (MouseFlags.Button1Clicked)) + { + EnumerableTableSource table = (EnumerableTableSource)_categoryList.Table; + string prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category; + isDescending = !isDescending; - table = (EnumerableTableSource)_categoryList.Table; + _categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending); - _categoryList.SelectedRow = table.Data - .Select ((item, index) => new { item, index }) - .FirstOrDefault (x => x.item.Category == prevSelection) - ?.index - ?? -1; - } - }; + table = (EnumerableTableSource)_categoryList.Table; + + _categoryList.SelectedRow = table.Data + .Select ((item, index) => new { item, index }) + .FirstOrDefault (x => x.item.Category == prevSelection) + ?.index + ?? -1; + } + }; int longestName = UnicodeRange.Ranges.Max (r => r.Category.GetColumns ()); @@ -167,7 +173,7 @@ public class CharacterMap : Scenario _categoryList.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4; - _categoryList.SelectedCellChanged += (s, args) => + _categoryList.SelectedCellChanged += (_, args) => { EnumerableTableSource table = (EnumerableTableSource)_categoryList.Table; _charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start; @@ -219,7 +225,7 @@ public class CharacterMap : Scenario _errorLabel.Visible = true; - uint result = 0; + uint result; if (jumpEdit.Text.Length == 1) { @@ -283,7 +289,7 @@ public class CharacterMap : Scenario ?? -1; _categoryList.EnsureSelectedCellIsVisible (); - // Ensure the typed glyph is selected + // Ensure the typed glyph is selected _charMap.SelectedCodePoint = (int)result; _charMap.SetFocus (); diff --git a/Examples/UICatalog/Scenarios/ContextMenus.cs b/Examples/UICatalog/Scenarios/ContextMenus.cs index 029798b0a..1c6170c5b 100644 --- a/Examples/UICatalog/Scenarios/ContextMenus.cs +++ b/Examples/UICatalog/Scenarios/ContextMenus.cs @@ -35,7 +35,7 @@ public class ContextMenus : Scenario Application.Run (_appWindow); _appWindow.Dispose (); _appWindow.KeyDown -= OnAppWindowOnKeyDown; - _appWindow.MouseClick -= OnAppWindowOnMouseClick; + _appWindow.Selecting -= OnAppWindowOnSelecting; _winContextMenu?.Dispose (); // Shutdown - Calling Application.Shutdown is required. @@ -81,23 +81,26 @@ public class ContextMenus : Scenario _appWindow.Add (_tfBottomRight); _appWindow.KeyDown += OnAppWindowOnKeyDown; - _appWindow.MouseClick += OnAppWindowOnMouseClick; + _appWindow.Selecting += OnAppWindowOnSelecting; CultureInfo originalCulture = Thread.CurrentThread.CurrentUICulture; - _appWindow.IsRunningChanged += (s, e) => { + _appWindow.IsRunningChanged += (_, e) => { if (!e.Value) { Thread.CurrentThread.CurrentUICulture = originalCulture; } }; } - void OnAppWindowOnMouseClick (object? s, MouseEventArgs e) + void OnAppWindowOnSelecting (object? s, CommandEventArgs e) { - if (e.Flags == MouseFlags.Button3Clicked) + if (e.Context is CommandContext { Binding.MouseEventArgs: { } mouseArgs }) { - // ReSharper disable once AccessToDisposedClosure - _winContextMenu?.MakeVisible (e.ScreenPosition); - e.Handled = true; + if (mouseArgs.Flags == MouseFlags.Button3Clicked) + { + // ReSharper disable once AccessToDisposedClosure + _winContextMenu?.MakeVisible (mouseArgs.ScreenPosition); + e.Handled = true; + } } } @@ -139,7 +142,7 @@ public class ContextMenus : Scenario Title = "M_ore options", SubMenu = new ( [ - new MenuItem + new () { Title = "_Setup...", HelpText = "Perform setup", @@ -153,7 +156,7 @@ public class ContextMenus : Scenario ), Key = Key.T.WithCtrl }, - new MenuItem + new () { Title = "_Maintenance...", HelpText = "Maintenance mode", @@ -194,7 +197,7 @@ public class ContextMenus : Scenario if (index == -1) { - // Create English because GetSupportedCutures doesn't include it + // Create English because GetSupportedCultures doesn't include it culture.Id = "_English"; culture.Title = "_English"; culture.HelpText = "en-US"; @@ -240,38 +243,31 @@ public class ContextMenus : Scenario public override List GetDemoKeyStrokes () { - List keys = new (); - - keys.Add (Key.F10.WithShift); - keys.Add (Key.Esc); - - keys.Add (Key.Space.WithCtrl); - keys.Add (Key.CursorDown); - keys.Add (Key.Enter); - - keys.Add (Key.F10.WithShift); - keys.Add (Key.Esc); - - keys.Add (Key.Tab); - - keys.Add (Key.Space.WithCtrl); - keys.Add (Key.CursorDown); - keys.Add (Key.CursorDown); - keys.Add (Key.Enter); - - keys.Add (Key.F10.WithShift); - keys.Add (Key.Esc); - - keys.Add (Key.Tab); - - keys.Add (Key.Space.WithCtrl); - keys.Add (Key.CursorDown); - keys.Add (Key.CursorDown); - keys.Add (Key.CursorDown); - keys.Add (Key.Enter); - - keys.Add (Key.F10.WithShift); - keys.Add (Key.Esc); + List keys = + [ + Key.F10.WithShift, + Key.Esc, + Key.Space.WithCtrl, + Key.CursorDown, + Key.Enter, + Key.F10.WithShift, + Key.Esc, + Key.Tab, + Key.Space.WithCtrl, + Key.CursorDown, + Key.CursorDown, + Key.Enter, + Key.F10.WithShift, + Key.Esc, + Key.Tab, + Key.Space.WithCtrl, + Key.CursorDown, + Key.CursorDown, + Key.CursorDown, + Key.Enter, + Key.F10.WithShift, + Key.Esc + ]; return keys; } diff --git a/Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs b/Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs index 9b59d8812..af4cb3e88 100644 --- a/Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs +++ b/Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs @@ -73,17 +73,16 @@ public class EventLog : ListView if (_viewToLog is { }) { - _viewToLog.Initialized += (s, args) => + _viewToLog.Initialized += (s, _) => { var sender = s as View; Log ($"Initialized: {GetIdentifyingString (sender)}"); }; - _viewToLog.MouseClick += (s, args) => { Log ($"MouseClick: {args}"); }; - _viewToLog.MouseWheel += (s, args) => { Log ($"MouseWheel: {args}"); }; - _viewToLog.HandlingHotKey += (s, args) => { Log ($"HandlingHotKey: {args.Context}"); }; - _viewToLog.Selecting += (s, args) => { Log ($"Selecting: {args.Context}"); }; - _viewToLog.Accepting += (s, args) => { Log ($"Accepting: {args.Context}"); }; + _viewToLog.MouseWheel += (_, args) => { Log ($"MouseWheel: {args}"); }; + _viewToLog.HandlingHotKey += (_, args) => { Log ($"HandlingHotKey: {args.Context}"); }; + _viewToLog.Selecting += (_, args) => { Log ($"Selecting: {args.Context}"); }; + _viewToLog.Accepting += (_, args) => { Log ($"Accepting: {args.Context}"); }; } } } diff --git a/Examples/UICatalog/Scenarios/ListColumns.cs b/Examples/UICatalog/Scenarios/ListColumns.cs index c19e9c3b9..036b23206 100644 --- a/Examples/UICatalog/Scenarios/ListColumns.cs +++ b/Examples/UICatalog/Scenarios/ListColumns.cs @@ -18,17 +18,17 @@ public class ListColumns : Scenario private TableView? _listColView; private CheckBox? _alternatingColorsCheckBox; private CheckBox? _alwaysUseNormalColorForVerticalCellLinesCheckBox; - private CheckBox? _bottomlineCheckBox; + private CheckBox? _bottomLineCheckBox; private CheckBox? _cellLinesCheckBox; private CheckBox? _cursorCheckBox; private CheckBox? _expandLastColumnCheckBox; private CheckBox? _orientVerticalCheckBox; private CheckBox? _scrollParallelCheckBox; private CheckBox? _smoothScrollingCheckBox; - private CheckBox? _toplineCheckBox; + private CheckBox? _topLineCheckBox; /// - /// Builds a simple list in which values are the index. This helps testing that scrolling etc is working + /// Builds a simple list in which values are the index. This helps test that scrolling etc. is working /// correctly and not skipping out values when paging /// /// @@ -112,25 +112,22 @@ public class ListColumns : Scenario Normal = new (Color.White, Color.BrightBlue) }; - // if user clicks the mouse in TableView - _listColView.MouseClick += (s, e) => { _listColView.ScreenToCell (e.Position, out int? clickedCol); }; - _listColView.KeyBindings.ReplaceCommands (Key.Space, Command.Accept); // Setup menu checkboxes - _toplineCheckBox = new () + _topLineCheckBox = new () { Title = "_TopLine", CheckedState = _listColView.Style.ShowHorizontalHeaderOverline ? CheckState.Checked : CheckState.UnChecked }; - _toplineCheckBox.CheckedStateChanged += (s, e) => ToggleTopline (); + _topLineCheckBox.CheckedStateChanged += (s, e) => ToggleTopline (); - _bottomlineCheckBox = new () + _bottomLineCheckBox = new () { Title = "_BottomLine", CheckedState = _listColView.Style.ShowHorizontalBottomline ? CheckState.Checked : CheckState.UnChecked }; - _bottomlineCheckBox.CheckedStateChanged += (s, e) => ToggleBottomline (); + _bottomLineCheckBox.CheckedStateChanged += (s, e) => ToggleBottomline (); _cellLinesCheckBox = new () { @@ -221,11 +218,11 @@ public class ListColumns : Scenario [ new MenuItem { - CommandView = _toplineCheckBox + CommandView = _topLineCheckBox }, new MenuItem { - CommandView = _bottomlineCheckBox + CommandView = _bottomLineCheckBox }, new MenuItem { @@ -422,12 +419,12 @@ public class ListColumns : Scenario private void ToggleBottomline () { - if (_listColView is null || _bottomlineCheckBox is null) + if (_listColView is null || _bottomLineCheckBox is null) { return; } - _listColView.Style.ShowHorizontalBottomline = _bottomlineCheckBox.CheckedState == CheckState.Checked; + _listColView.Style.ShowHorizontalBottomline = _bottomLineCheckBox.CheckedState == CheckState.Checked; _listColView.Update (); } @@ -490,12 +487,12 @@ public class ListColumns : Scenario private void ToggleTopline () { - if (_listColView is null || _toplineCheckBox is null) + if (_listColView is null || _topLineCheckBox is null) { return; } - _listColView.Style.ShowHorizontalHeaderOverline = _toplineCheckBox.CheckedState == CheckState.Checked; + _listColView.Style.ShowHorizontalHeaderOverline = _topLineCheckBox.CheckedState == CheckState.Checked; _listColView.Update (); } diff --git a/Examples/UICatalog/Scenarios/Mouse.cs b/Examples/UICatalog/Scenarios/Mouse.cs index d56b3e82a..7651d96b1 100644 --- a/Examples/UICatalog/Scenarios/Mouse.cs +++ b/Examples/UICatalog/Scenarios/Mouse.cs @@ -143,7 +143,7 @@ public class Mouse : Scenario cbHighlightOnPressed.CheckedState = demo.HighlightStates.HasFlag (MouseState.Pressed) ? CheckState.Checked : CheckState.UnChecked; - cbHighlightOnPressed.CheckedStateChanging += (s, e) => + cbHighlightOnPressed.CheckedStateChanging += (_, e) => { if (e.Result == CheckState.Checked) { @@ -181,7 +181,7 @@ public class Mouse : Scenario cbHighlightOnPressedOutside.CheckedState = demo.HighlightStates.HasFlag (MouseState.PressedOutside) ? CheckState.Checked : CheckState.UnChecked; - cbHighlightOnPressedOutside.CheckedStateChanging += (s, e) => + cbHighlightOnPressedOutside.CheckedStateChanging += (_, e) => { if (e.Result == CheckState.Checked) { @@ -217,7 +217,7 @@ public class Mouse : Scenario } }; - cbWantContinuousPresses.CheckedStateChanging += (s, e) => + cbWantContinuousPresses.CheckedStateChanging += (_, _) => { demo.WantContinuousButtonPressed = !demo.WantContinuousButtonPressed; @@ -252,7 +252,7 @@ public class Mouse : Scenario }; win.Add (label, appLog); - Application.MouseEvent += (sender, a) => + Application.MouseEvent += (_, a) => { int i = filterSlider.Options.FindIndex (o => o.Data == a.Flags); @@ -270,7 +270,7 @@ public class Mouse : Scenario X = Pos.Right (appLog) + 1, Y = Pos.Top (label) }; - ObservableCollection winLogList = new (); + ObservableCollection winLogList = []; var winLog = new ListView { @@ -283,7 +283,7 @@ public class Mouse : Scenario }; win.Add (label, winLog); - clearButton.Accepting += (s, e) => + clearButton.Accepting += (_, _) => { appLogList.Clear (); appLog.SetSource (appLogList); @@ -291,7 +291,7 @@ public class Mouse : Scenario winLog.SetSource (winLogList); }; - win.MouseEvent += (sender, a) => + win.MouseEvent += (_, a) => { int i = filterSlider.Options.FindIndex (o => o.Data == a.Flags); @@ -302,12 +302,6 @@ public class Mouse : Scenario } }; - win.MouseClick += (sender, a) => - { - winLogList.Add ($"MouseClick: ({a.Position}) - {a.Flags} {count++}"); - winLog.MoveDown (); - }; - Application.Run (win); win.Dispose (); Application.Shutdown (); @@ -322,8 +316,8 @@ public class Mouse : Scenario Initialized += OnInitialized; - MouseLeave += (s, e) => { Text = "Leave"; }; - MouseEnter += (s, e) => { Text = "Enter"; }; + MouseLeave += (_, _) => { Text = "Leave"; }; + MouseEnter += (_, _) => { Text = "Enter"; }; return; diff --git a/Examples/UICatalog/Scenarios/ScrollBarDemo.cs b/Examples/UICatalog/Scenarios/ScrollBarDemo.cs index fcba06c9d..5a25d1fd1 100644 --- a/Examples/UICatalog/Scenarios/ScrollBarDemo.cs +++ b/Examples/UICatalog/Scenarios/ScrollBarDemo.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; +using System.Collections.ObjectModel; namespace UICatalog.Scenarios; @@ -18,7 +16,7 @@ public class ScrollBarDemo : Scenario Arrangement = ViewArrangement.Fixed }; - var demoFrame = new FrameView () + var demoFrame = new FrameView { Title = "Demo View", X = 0, @@ -36,7 +34,7 @@ public class ScrollBarDemo : Scenario X = Pos.AnchorEnd () - 5, AutoShow = false, ScrollableContentSize = 100, - Height = Dim.Fill() + Height = Dim.Fill () }; demoFrame.Add (scrollBar); @@ -45,7 +43,7 @@ public class ScrollBarDemo : Scenario X = Pos.AnchorEnd (), Width = 5, Height = Dim.Fill (), - SchemeName = "Error", + SchemeName = "Error" }; demoFrame.Add (controlledList); @@ -55,10 +53,9 @@ public class ScrollBarDemo : Scenario int GetMaxLabelWidth (int groupId) { - return demoFrame.SubViews.Max ( - v => + return demoFrame.SubViews.Max (v => { - if (v.Y.Has (out var pos) && pos.GroupId == groupId) + if (v.Y.Has (out PosAlign pos) && pos.GroupId == groupId) { return v.Text.GetColumns (); } @@ -71,7 +68,7 @@ public class ScrollBarDemo : Scenario { Text = "_Width/Height:", TextAlignment = Alignment.End, - Y = Pos.Align (Alignment.Start, AlignmentModes.StartToEnd, groupId: 1), + Y = Pos.Align (Alignment.Start, AlignmentModes.StartToEnd, 1), Width = Dim.Func (_ => GetMaxLabelWidth (1)) }; demoFrame.Add (lblWidthHeight); @@ -80,17 +77,17 @@ public class ScrollBarDemo : Scenario { Value = 1, X = Pos.Right (lblWidthHeight) + 1, - Y = Pos.Top (lblWidthHeight), + Y = Pos.Top (lblWidthHeight) }; demoFrame.Add (scrollWidthHeight); scrollWidthHeight.ValueChanging += (s, e) => { if (e.NewValue < 1 - || (e.NewValue - > (scrollBar.Orientation == Orientation.Vertical - ? scrollBar.SuperView?.GetContentSize ().Width - : scrollBar.SuperView?.GetContentSize ().Height))) + || e.NewValue + > (scrollBar.Orientation == Orientation.Vertical + ? scrollBar.SuperView?.GetContentSize ().Width + : scrollBar.SuperView?.GetContentSize ().Height)) { // TODO: This must be handled in the ScrollSlider if Width and Height being virtual e.Cancel = true; @@ -108,7 +105,6 @@ public class ScrollBarDemo : Scenario } }; - var lblOrientationLabel = new Label { Text = "_Orientation:", @@ -128,28 +124,26 @@ public class ScrollBarDemo : Scenario demoFrame.Add (osOrientation); osOrientation.ValueChanged += (s, e) => - { - - if (osOrientation.Value == Orientation.Horizontal) - { - scrollBar.Orientation = Orientation.Vertical; - scrollBar.X = Pos.AnchorEnd () - 5; - scrollBar.Y = 0; - scrollBar.Width = scrollWidthHeight.Value; - scrollBar.Height = Dim.Fill (); - controlledList.Visible = true; - } - else - { - scrollBar.Orientation = Orientation.Horizontal; - scrollBar.X = 0; - scrollBar.Y = Pos.AnchorEnd (); - scrollBar.Height = scrollWidthHeight.Value; - scrollBar.Width = Dim.Fill (); - controlledList.Visible = false; - - } - }; + { + if (osOrientation.Value == Orientation.Horizontal) + { + scrollBar.Orientation = Orientation.Vertical; + scrollBar.X = Pos.AnchorEnd () - 5; + scrollBar.Y = 0; + scrollBar.Width = scrollWidthHeight.Value; + scrollBar.Height = Dim.Fill (); + controlledList.Visible = true; + } + else + { + scrollBar.Orientation = Orientation.Horizontal; + scrollBar.X = 0; + scrollBar.Y = Pos.AnchorEnd (); + scrollBar.Height = scrollWidthHeight.Value; + scrollBar.Width = Dim.Fill (); + controlledList.Visible = false; + } + }; var lblSize = new Label { @@ -169,20 +163,24 @@ public class ScrollBarDemo : Scenario demoFrame.Add (scrollContentSize); scrollContentSize.ValueChanging += (s, e) => - { - if (e.NewValue < 0) - { - e.Cancel = true; + { + if (e.NewValue < 0) + { + e.Cancel = true; - return; - } + return; + } - if (scrollBar.ScrollableContentSize != e.NewValue) - { - scrollBar.ScrollableContentSize = e.NewValue; - controlledList.SetSource (new ObservableCollection (Enumerable.Range (0, scrollBar.ScrollableContentSize).Select (n => $"{n:00000}"))); - } - }; + if (scrollBar.ScrollableContentSize != e.NewValue) + { + scrollBar.ScrollableContentSize = e.NewValue; + + controlledList.SetSource ( + new ObservableCollection ( + Enumerable.Range (0, scrollBar.ScrollableContentSize) + .Select (n => $"{n:00000}"))); + } + }; var lblVisibleContentSize = new Label { @@ -202,20 +200,19 @@ public class ScrollBarDemo : Scenario demoFrame.Add (visibleContentSize); visibleContentSize.ValueChanging += (s, e) => - { - if (e.NewValue < 0) - { - e.Cancel = true; + { + if (e.NewValue < 0) + { + e.Cancel = true; - return; - } - - if (scrollBar.VisibleContentSize != e.NewValue) - { - scrollBar.VisibleContentSize = e.NewValue; - } - }; + return; + } + if (scrollBar.VisibleContentSize != e.NewValue) + { + scrollBar.VisibleContentSize = e.NewValue; + } + }; var lblPosition = new Label { @@ -223,7 +220,6 @@ public class ScrollBarDemo : Scenario TextAlignment = Alignment.End, Y = Pos.Align (Alignment.Start, groupId: 1), Width = Dim.Func (_ => GetMaxLabelWidth (1)) - }; demoFrame.Add (lblPosition); @@ -236,24 +232,24 @@ public class ScrollBarDemo : Scenario demoFrame.Add (scrollPosition); scrollPosition.ValueChanging += (s, e) => - { - if (e.NewValue < 0) - { - e.Cancel = true; + { + if (e.NewValue < 0) + { + e.Cancel = true; - return; - } + return; + } - if (scrollBar.Position != e.NewValue) - { - scrollBar.Position = e.NewValue; - } + if (scrollBar.Position != e.NewValue) + { + scrollBar.Position = e.NewValue; + } - if (scrollBar.Position != e.NewValue) - { - e.Cancel = true; - } - }; + if (scrollBar.Position != e.NewValue) + { + e.Cancel = true; + } + }; var lblOptions = new Label { @@ -263,11 +259,12 @@ public class ScrollBarDemo : Scenario Width = Dim.Func (_ => GetMaxLabelWidth (1)) }; demoFrame.Add (lblOptions); + var autoShow = new CheckBox { Y = Pos.Top (lblOptions), X = Pos.Right (lblOptions) + 1, - Text = $"_AutoShow", + Text = "_AutoShow", CheckedState = scrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked }; autoShow.CheckedStateChanging += (s, e) => scrollBar.AutoShow = e.Result == CheckState.Checked; @@ -296,9 +293,9 @@ public class ScrollBarDemo : Scenario TextAlignment = Alignment.End, Y = Pos.Align (Alignment.Start, groupId: 1), Width = Dim.Func (_ => GetMaxLabelWidth (1)) - }; demoFrame.Add (lblScrolled); + Label scrolled = new () { X = Pos.Right (lblScrolled) + 1, @@ -347,43 +344,41 @@ public class ScrollBarDemo : Scenario void AppOnInitialized (object sender, EventArgs e) { scrollBar.ScrollableContentSizeChanged += (s, e) => - { - eventLog.Log ($"SizeChanged: {e.Value}"); + { + eventLog.Log ($"SizeChanged: {e.Value}"); - if (scrollContentSize.Value != e.Value) - { - scrollContentSize.Value = e.Value; - } - }; + if (scrollContentSize.Value != e.Value) + { + scrollContentSize.Value = e.Value; + } + }; scrollBar.SliderPositionChanged += (s, e) => - { - eventLog.Log ($"SliderPositionChanged: {e.Value}"); - eventLog.Log ($" Position: {scrollBar.Position}"); - scrollSliderPosition.Text = e.Value.ToString (); - }; + { + eventLog.Log ($"SliderPositionChanged: {e.Value}"); + eventLog.Log ($" Position: {scrollBar.Position}"); + scrollSliderPosition.Text = e.Value.ToString (); + }; scrollBar.Scrolled += (s, e) => - { - eventLog.Log ($"Scrolled: {e.Value}"); - eventLog.Log ($" SliderPosition: {scrollBar.GetSliderPosition ()}"); - scrolled.Text = e.Value.ToString (); - }; + { + eventLog.Log ($"Scrolled: {e.Value}"); + eventLog.Log ($" SliderPosition: {scrollBar.GetSliderPosition ()}"); + scrolled.Text = e.Value.ToString (); + }; scrollBar.PositionChanged += (s, e) => - { - eventLog.Log ($"PositionChanged: {e.Value}"); - scrollPosition.Value = e.Value; - controlledList.Viewport = controlledList.Viewport with { Y = e.Value }; - }; - + { + eventLog.Log ($"PositionChanged: {e.Value}"); + scrollPosition.Value = e.Value; + controlledList.Viewport = controlledList.Viewport with { Y = e.Value }; + }; controlledList.ViewportChanged += (s, e) => { eventLog.Log ($"ViewportChanged: {e.NewViewport}"); scrollBar.Position = e.NewViewport.Y; }; - } Application.Run (app); diff --git a/Examples/UICatalog/Scenarios/TableEditor.cs b/Examples/UICatalog/Scenarios/TableEditor.cs index 4880f4c95..0ca8092d0 100644 --- a/Examples/UICatalog/Scenarios/TableEditor.cs +++ b/Examples/UICatalog/Scenarios/TableEditor.cs @@ -610,29 +610,35 @@ public class TableEditor : Scenario }; // if user clicks the mouse in TableView - _tableView!.MouseClick += (s, e) => - { - if (_currentTable == null) - { - return; - } + _tableView!.Selecting += (s, e) => + { + if (_currentTable == null) + { + return; + } - _tableView!.ScreenToCell (e.Position, out int? clickedCol); + // Only handle mouse clicks + if (e.Context is not CommandContext { Binding.MouseEventArgs: { } mouseArgs }) + { + return; + } - if (clickedCol != null) - { - if (e.Flags.HasFlag (MouseFlags.Button1Clicked)) - { - // left click in a header - SortColumn (clickedCol.Value); - } - else if (e.Flags.HasFlag (MouseFlags.Button3Clicked)) - { - // right click in a header - ShowHeaderContextMenu (clickedCol.Value, e); - } - } - }; + _tableView!.ScreenToCell (mouseArgs.Position, out int? clickedCol); + + if (clickedCol != null) + { + if (mouseArgs.Flags.HasFlag (MouseFlags.Button1Clicked)) + { + // left click in a header + SortColumn (clickedCol.Value); + } + else if (mouseArgs.Flags.HasFlag (MouseFlags.Button3Clicked)) + { + // right click in a header + ShowHeaderContextMenu (clickedCol.Value, mouseArgs); + } + } + }; _tableView!.KeyBindings.ReplaceCommands (Key.Space, Command.Accept); diff --git a/Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs b/Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs index ebd42a3db..3a90d52c9 100644 --- a/Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs +++ b/Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Text; namespace UICatalog.Scenarios; @@ -9,12 +6,11 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Text and Formatting")] public class TextAlignmentAndDirection : Scenario { - internal class AlignmentAndDirectionView : View { public AlignmentAndDirectionView () { - ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent; + ViewportSettings = ViewportSettingsFlags.Transparent; BorderStyle = LineStyle.Dotted; } } @@ -30,15 +26,15 @@ public class TextAlignmentAndDirection : Scenario var txt = $"Hello World{Environment.NewLine}HELLO WORLD{Environment.NewLine}世界 您好"; - SchemeManager.AddScheme ("TextAlignmentAndDirection1", new Scheme { Normal = new (Color.Black, Color.Gray) }); - SchemeManager.AddScheme ("TextAlignmentAndDirection2", new Scheme { Normal = new (Color.Black, Color.DarkGray) }); + SchemeManager.AddScheme ("TextAlignmentAndDirection1", new () { Normal = new (Color.Black, Color.Gray) }); + SchemeManager.AddScheme ("TextAlignmentAndDirection2", new () { Normal = new (Color.Black, Color.DarkGray) }); - List singleLineLabels = new (); // single line - List multiLineLabels = new (); // multi line + List singleLineLabels = []; // single line + List multiLineLabels = []; // multi line - // Horizontal Single-Line + // Horizontal Single-Line - var labelHL = new Label + Label labelHL = new () { X = 0, Y = 0, @@ -46,10 +42,10 @@ public class TextAlignmentAndDirection : Scenario Height = 1, TextAlignment = Alignment.End, SchemeName = "Dialog", - Text = "Start", + Text = "Start" }; - var labelHC = new Label + Label labelHC = new () { X = 0, Y = 1, @@ -60,7 +56,7 @@ public class TextAlignmentAndDirection : Scenario Text = "Center" }; - var labelHR = new Label + Label labelHR = new () { X = 0, Y = 2, @@ -71,7 +67,7 @@ public class TextAlignmentAndDirection : Scenario Text = "End" }; - var labelHJ = new Label + Label labelHJ = new () { X = 0, Y = 3, @@ -82,7 +78,7 @@ public class TextAlignmentAndDirection : Scenario Text = "Fill" }; - var txtLabelHL = new View + View txtLabelHL = new () { X = Pos.Right (labelHL) + 1, Y = Pos.Y (labelHL), @@ -91,10 +87,10 @@ public class TextAlignmentAndDirection : Scenario SchemeName = "TextAlignmentAndDirection1", TextAlignment = Alignment.Start, Text = txt, - ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent + ViewportSettings = ViewportSettingsFlags.Transparent }; - var txtLabelHC = new View + View txtLabelHC = new () { X = Pos.Right (labelHC) + 1, Y = Pos.Y (labelHC), @@ -103,10 +99,10 @@ public class TextAlignmentAndDirection : Scenario SchemeName = "TextAlignmentAndDirection2", TextAlignment = Alignment.Center, Text = txt, - ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent + ViewportSettings = ViewportSettingsFlags.Transparent }; - var txtLabelHR = new View + View txtLabelHR = new () { X = Pos.Right (labelHR) + 1, Y = Pos.Y (labelHR), @@ -115,10 +111,10 @@ public class TextAlignmentAndDirection : Scenario SchemeName = "TextAlignmentAndDirection1", TextAlignment = Alignment.End, Text = txt, - ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent + ViewportSettings = ViewportSettingsFlags.Transparent }; - var txtLabelHJ = new View + View txtLabelHJ = new () { X = Pos.Right (labelHJ) + 1, Y = Pos.Y (labelHJ), @@ -127,7 +123,7 @@ public class TextAlignmentAndDirection : Scenario SchemeName = "TextAlignmentAndDirection2", TextAlignment = Alignment.Fill, Text = txt, - ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent + ViewportSettings = ViewportSettingsFlags.Transparent }; singleLineLabels.Add (txtLabelHL); @@ -146,7 +142,7 @@ public class TextAlignmentAndDirection : Scenario // Vertical Single-Line - var labelVT = new Label + Label labelVT = new () { X = Pos.AnchorEnd () - 6, Y = 0, @@ -159,7 +155,7 @@ public class TextAlignmentAndDirection : Scenario }; labelVT.TextFormatter.WordWrap = false; - var labelVM = new Label + Label labelVM = new () { X = Pos.AnchorEnd () - 4, Y = 0, @@ -172,7 +168,7 @@ public class TextAlignmentAndDirection : Scenario }; labelVM.TextFormatter.WordWrap = false; - var labelVB = new Label + Label labelVB = new () { X = Pos.AnchorEnd () - 2, Y = 0, @@ -185,7 +181,7 @@ public class TextAlignmentAndDirection : Scenario }; labelVB.TextFormatter.WordWrap = false; - var labelVJ = new Label + Label labelVJ = new () { X = Pos.AnchorEnd (), Y = 0, @@ -198,7 +194,7 @@ public class TextAlignmentAndDirection : Scenario }; labelVJ.TextFormatter.WordWrap = false; - var txtLabelVT = new View + View txtLabelVT = new () { X = Pos.X (labelVT), Y = Pos.Bottom (labelVT) + 1, @@ -208,11 +204,11 @@ public class TextAlignmentAndDirection : Scenario TextDirection = TextDirection.TopBottom_LeftRight, VerticalTextAlignment = Alignment.Start, Text = txt, - ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent + ViewportSettings = ViewportSettingsFlags.Transparent }; txtLabelVT.TextFormatter.WordWrap = false; - var txtLabelVM = new View + View txtLabelVM = new () { X = Pos.X (labelVM), Y = Pos.Bottom (labelVM) + 1, @@ -222,11 +218,11 @@ public class TextAlignmentAndDirection : Scenario TextDirection = TextDirection.TopBottom_LeftRight, VerticalTextAlignment = Alignment.Center, Text = txt, - ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent + ViewportSettings = ViewportSettingsFlags.Transparent }; txtLabelVM.TextFormatter.WordWrap = false; - var txtLabelVB = new View + View txtLabelVB = new () { X = Pos.X (labelVB), Y = Pos.Bottom (labelVB) + 1, @@ -236,11 +232,11 @@ public class TextAlignmentAndDirection : Scenario TextDirection = TextDirection.TopBottom_LeftRight, VerticalTextAlignment = Alignment.End, Text = txt, - ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent + ViewportSettings = ViewportSettingsFlags.Transparent }; txtLabelVB.TextFormatter.WordWrap = false; - var txtLabelVJ = new View + View txtLabelVJ = new () { X = Pos.X (labelVJ), Y = Pos.Bottom (labelVJ) + 1, @@ -250,7 +246,7 @@ public class TextAlignmentAndDirection : Scenario TextDirection = TextDirection.TopBottom_LeftRight, VerticalTextAlignment = Alignment.Fill, Text = txt, - ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent + ViewportSettings = ViewportSettingsFlags.Transparent }; txtLabelVJ.TextFormatter.WordWrap = false; @@ -270,7 +266,7 @@ public class TextAlignmentAndDirection : Scenario // Multi-Line - var container = new View + View container = new () { X = 0, Y = Pos.Bottom (txtLabelHJ), @@ -280,7 +276,7 @@ public class TextAlignmentAndDirection : Scenario //SchemeName = "TextAlignmentAndDirection2" }; - var txtLabelTL = new AlignmentAndDirectionView + AlignmentAndDirectionView txtLabelTL = new () { X = 0, Y = 1, @@ -289,11 +285,11 @@ public class TextAlignmentAndDirection : Scenario TextAlignment = Alignment.Start, VerticalTextAlignment = Alignment.Start, SchemeName = "TextAlignmentAndDirection1", - Text = txt, + Text = txt }; txtLabelTL.TextFormatter.MultiLine = true; - var txtLabelTC = new AlignmentAndDirectionView + AlignmentAndDirectionView txtLabelTC = new () { X = Pos.Right (txtLabelTL), Y = 1, @@ -302,11 +298,11 @@ public class TextAlignmentAndDirection : Scenario TextAlignment = Alignment.Center, VerticalTextAlignment = Alignment.Start, SchemeName = "TextAlignmentAndDirection1", - Text = txt, + Text = txt }; txtLabelTC.TextFormatter.MultiLine = true; - var txtLabelTR = new AlignmentAndDirectionView + AlignmentAndDirectionView txtLabelTR = new () { X = Pos.Right (txtLabelTC), Y = 1, @@ -315,11 +311,11 @@ public class TextAlignmentAndDirection : Scenario TextAlignment = Alignment.End, VerticalTextAlignment = Alignment.Start, SchemeName = "TextAlignmentAndDirection1", - Text = txt, + Text = txt }; txtLabelTR.TextFormatter.MultiLine = true; - var txtLabelML = new AlignmentAndDirectionView + AlignmentAndDirectionView txtLabelML = new () { X = Pos.X (txtLabelTL), Y = Pos.Bottom (txtLabelTL), @@ -328,11 +324,11 @@ public class TextAlignmentAndDirection : Scenario TextAlignment = Alignment.Start, VerticalTextAlignment = Alignment.Center, SchemeName = "TextAlignmentAndDirection1", - Text = txt, + Text = txt }; txtLabelML.TextFormatter.MultiLine = true; - var txtLabelMC = new AlignmentAndDirectionView + AlignmentAndDirectionView txtLabelMC = new () { X = Pos.X (txtLabelTC), Y = Pos.Bottom (txtLabelTC), @@ -341,11 +337,11 @@ public class TextAlignmentAndDirection : Scenario TextAlignment = Alignment.Center, VerticalTextAlignment = Alignment.Center, SchemeName = "TextAlignmentAndDirection1", - Text = txt, + Text = txt }; txtLabelMC.TextFormatter.MultiLine = true; - var txtLabelMR = new AlignmentAndDirectionView + AlignmentAndDirectionView txtLabelMR = new () { X = Pos.X (txtLabelTR), Y = Pos.Bottom (txtLabelTR), @@ -354,11 +350,11 @@ public class TextAlignmentAndDirection : Scenario TextAlignment = Alignment.End, VerticalTextAlignment = Alignment.Center, SchemeName = "TextAlignmentAndDirection1", - Text = txt, + Text = txt }; txtLabelMR.TextFormatter.MultiLine = true; - var txtLabelBL = new AlignmentAndDirectionView + AlignmentAndDirectionView txtLabelBL = new () { X = Pos.X (txtLabelML), Y = Pos.Bottom (txtLabelML), @@ -367,11 +363,11 @@ public class TextAlignmentAndDirection : Scenario TextAlignment = Alignment.Start, VerticalTextAlignment = Alignment.End, SchemeName = "TextAlignmentAndDirection1", - Text = txt, + Text = txt }; txtLabelBL.TextFormatter.MultiLine = true; - var txtLabelBC = new AlignmentAndDirectionView + AlignmentAndDirectionView txtLabelBC = new () { X = Pos.X (txtLabelMC), Y = Pos.Bottom (txtLabelMC), @@ -380,11 +376,11 @@ public class TextAlignmentAndDirection : Scenario TextAlignment = Alignment.Center, VerticalTextAlignment = Alignment.End, SchemeName = "TextAlignmentAndDirection1", - Text = txt, + Text = txt }; txtLabelBC.TextFormatter.MultiLine = true; - var txtLabelBR = new AlignmentAndDirectionView + AlignmentAndDirectionView txtLabelBR = new () { X = Pos.X (txtLabelMR), Y = Pos.Bottom (txtLabelMR), @@ -393,7 +389,7 @@ public class TextAlignmentAndDirection : Scenario TextAlignment = Alignment.End, VerticalTextAlignment = Alignment.End, SchemeName = "TextAlignmentAndDirection1", - Text = txt, + Text = txt }; txtLabelBR.TextFormatter.MultiLine = true; @@ -429,7 +425,7 @@ public class TextAlignmentAndDirection : Scenario // Edit Text - var label = new Label + Label label = new () { X = 1, Y = Pos.Bottom (container) + 1, @@ -438,7 +434,7 @@ public class TextAlignmentAndDirection : Scenario Text = "Edit Text:" }; - var editText = new TextView + TextView editText = new () { X = Pos.Right (label) + 1, Y = Pos.Top (label), @@ -447,19 +443,6 @@ public class TextAlignmentAndDirection : Scenario Text = txt }; - editText.MouseClick += (s, m) => - { - foreach (View v in singleLineLabels) - { - v.Text = editText.Text; - } - - foreach (View v in multiLineLabels) - { - v.Text = editText.Text; - } - }; - app.KeyUp += (s, m) => { foreach (View v in singleLineLabels) @@ -479,7 +462,7 @@ public class TextAlignmentAndDirection : Scenario // JUSTIFY CHECKBOX - var justifyCheckbox = new CheckBox + CheckBox justifyCheckbox = new () { X = Pos.Right (container) + 1, Y = Pos.Y (container) + 1, @@ -492,7 +475,7 @@ public class TextAlignmentAndDirection : Scenario // JUSTIFY OPTIONS - var justifyOptions = new OptionSelector + OptionSelector justifyOptions = new () { X = Pos.Left (justifyCheckbox) + 1, Y = Pos.Y (justifyCheckbox) + 1, @@ -509,7 +492,7 @@ public class TextAlignmentAndDirection : Scenario // WRAP CHECKBOX - var wrapCheckbox = new CheckBox + CheckBox wrapCheckbox = new () { X = Pos.Right (container) + 1, Y = Pos.Bottom (justifyOptions), @@ -520,28 +503,28 @@ public class TextAlignmentAndDirection : Scenario wrapCheckbox.CheckedState = wrapCheckbox.TextFormatter.WordWrap ? CheckState.Checked : CheckState.UnChecked; wrapCheckbox.CheckedStateChanging += (s, e) => - { - if (e.Result == CheckState.Checked) - { - foreach (View t in multiLineLabels) - { - t.TextFormatter.WordWrap = false; - } - } - else - { - foreach (View t in multiLineLabels) - { - t.TextFormatter.WordWrap = true; - } - } - }; + { + if (e.Result == CheckState.Checked) + { + foreach (View t in multiLineLabels) + { + t.TextFormatter.WordWrap = false; + } + } + else + { + foreach (View t in multiLineLabels) + { + t.TextFormatter.WordWrap = true; + } + } + }; app.Add (wrapCheckbox); List directionsEnum = Enum.GetValues (typeof (TextDirection)).Cast ().ToList (); - var directionOptions = new OptionSelector + OptionSelector directionOptions = new () { X = Pos.Right (container) + 1, Y = Pos.Bottom (wrapCheckbox) + 1, @@ -552,24 +535,24 @@ public class TextAlignmentAndDirection : Scenario }; directionOptions.ValueChanged += (s, ev) => - { - bool justChecked = justifyCheckbox.CheckedState == CheckState.Checked; + { + bool justChecked = justifyCheckbox.CheckedState == CheckState.Checked; - if (justChecked) - { - ToggleJustify (true); - } + if (justChecked) + { + ToggleJustify (true); + } - foreach (View v in multiLineLabels.Where (v => ev.Value is { })) - { - v.TextDirection = (TextDirection)ev.Value!.Value; - } + foreach (View v in multiLineLabels.Where (v => ev.Value is { })) + { + v.TextDirection = (TextDirection)ev.Value!.Value; + } - if (justChecked) - { - ToggleJustify (false); - } - }; + if (justChecked) + { + ToggleJustify (false); + } + }; app.Add (directionOptions); @@ -617,14 +600,17 @@ public class TextAlignmentAndDirection : Scenario case 0: t.VerticalTextAlignment = Alignment.Fill; t.TextAlignment = data!.h; + break; case 1: t.VerticalTextAlignment = data!.v; t.TextAlignment = Alignment.Fill; + break; case 2: t.VerticalTextAlignment = Alignment.Fill; t.TextAlignment = Alignment.Fill; + break; } } @@ -635,14 +621,17 @@ public class TextAlignmentAndDirection : Scenario case 0: t.TextAlignment = Alignment.Fill; t.VerticalTextAlignment = data!.v; + break; case 1: t.TextAlignment = data!.h; t.VerticalTextAlignment = Alignment.Fill; + break; case 2: t.TextAlignment = Alignment.Fill; t.VerticalTextAlignment = Alignment.Fill; + break; } } diff --git a/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs b/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs index 64102a935..3753ac35d 100644 --- a/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -60,7 +60,7 @@ public class TreeViewFileSystem : Scenario }; win.Add (_detailsFrame); - _treeViewFiles.MouseClick += TreeViewFiles_MouseClick; + _treeViewFiles.Selecting += TreeViewFiles_Selecting; _treeViewFiles.KeyDown += TreeViewFiles_KeyPress; _treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged; @@ -556,17 +556,23 @@ public class TreeViewFileSystem : Scenario } } - private void TreeViewFiles_MouseClick (object? sender, MouseEventArgs obj) + private void TreeViewFiles_Selecting (object? sender, CommandEventArgs e) { if (_treeViewFiles is null) { return; } - // if user right clicks - if (obj.Flags.HasFlag (MouseFlags.Button3Clicked)) + // Only handle mouse clicks + if (e.Context is not CommandContext { Binding.MouseEventArgs: { } mouseArgs }) { - IFileSystemInfo? rightClicked = _treeViewFiles.GetObjectOnRow (obj.Position.Y); + return; + } + + // if user right clicks + if (mouseArgs.Flags.HasFlag (MouseFlags.Button3Clicked)) + { + IFileSystemInfo? rightClicked = _treeViewFiles.GetObjectOnRow (mouseArgs.Position.Y); // nothing was clicked if (rightClicked is null) @@ -576,8 +582,8 @@ public class TreeViewFileSystem : Scenario ShowContextMenu ( new ( - obj.Position.X + _treeViewFiles.Frame.X, - obj.Position.Y + _treeViewFiles.Frame.Y + 2 + mouseArgs.Position.X + _treeViewFiles.Frame.X, + mouseArgs.Position.Y + _treeViewFiles.Frame.Y + 2 ), rightClicked ); diff --git a/Examples/UICatalog/Scenarios/ViewExperiments.cs b/Examples/UICatalog/Scenarios/ViewExperiments.cs index c36fce3e7..aeeaa14c9 100644 --- a/Examples/UICatalog/Scenarios/ViewExperiments.cs +++ b/Examples/UICatalog/Scenarios/ViewExperiments.cs @@ -86,18 +86,19 @@ public class ViewExperiments : Scenario //App?.Popover!.Visible = true; } - testFrame.MouseClick += TestFrameOnMouseClick; - - void TestFrameOnMouseClick (object sender, MouseEventArgs e) + testFrame.Selecting += (sender, e) => { - if (e.Flags == MouseFlags.Button3Clicked) + if (e.Context is CommandContext { Binding.MouseEventArgs: { } mouseArgs }) { - popoverView.X = e.ScreenPosition.X; - popoverView.Y = e.ScreenPosition.Y; - //App?.Popover = popoverView; - //App?.Popover!.Visible = true; + if (mouseArgs.Flags == MouseFlags.Button3Clicked) + { + popoverView.X = mouseArgs.ScreenPosition.X; + popoverView.Y = mouseArgs.ScreenPosition.Y; + //App?.Popover = popoverView; + //App?.Popover!.Visible = true; + } } - } + }; testFrame.Add (button); diff --git a/Terminal.Gui/Input/Mouse/MouseEventArgs.cs b/Terminal.Gui/Input/Mouse/MouseEventArgs.cs index 3e10c68a4..4e44d93c7 100644 --- a/Terminal.Gui/Input/Mouse/MouseEventArgs.cs +++ b/Terminal.Gui/Input/Mouse/MouseEventArgs.cs @@ -4,10 +4,7 @@ using System.ComponentModel; namespace Terminal.Gui.Input; /// -/// Specifies the event arguments for . This is a higher-level construct than -/// the wrapped class and is used for the events defined on -/// and subclasses -/// of View (e.g. and ). +/// Specifies the event arguments for . /// public class MouseEventArgs : HandledEventArgs { diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index c899c2d2e..146057f63 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -236,10 +236,6 @@ public partial class View // Mouse APIs /// A view must be both enabled and visible to receive mouse events. /// /// - /// This method raises /; if not handled, and one of the - /// mouse buttons was clicked, the / event will be raised - /// - /// /// If is , and the user presses and holds the /// mouse button, will be repeatedly called with the same for /// as long as the mouse button remains pressed. @@ -291,13 +287,6 @@ public partial class View // Mouse APIs } } - // We get here if the view did not handle the mouse event via OnMouseEvent/MouseEvent, and - // it did not handle the press/release/clicked events via HandlePress/HandleRelease/HandleClicked - if (mouseEvent.IsSingleDoubleOrTripleClicked) - { - return RaiseMouseClickEvent (mouseEvent); - } - if (mouseEvent.IsWheel) { return RaiseMouseWheelEvent (mouseEvent); @@ -333,6 +322,11 @@ public partial class View // Mouse APIs MouseEvent?.Invoke (this, mouseEvent); + if (!mouseEvent.Handled) + { + mouseEvent.Handled = InvokeCommandsBoundToMouse (mouseEvent) == true; + } + return mouseEvent.Handled; } @@ -356,7 +350,7 @@ public partial class View // Mouse APIs #endregion Low Level Mouse Events - #region Mouse Pressed Events + #region WhenGrabbed Handlers /// /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the released event @@ -450,81 +444,6 @@ public partial class View // Mouse APIs return false; } - #endregion Mouse Pressed Events - - #region Mouse Click Events - - /// Raises the / event. - /// - /// - /// Called when the mouse is either clicked or double-clicked. - /// - /// - /// If is , will be invoked on every mouse event - /// where - /// the mouse button is pressed. - /// - /// - /// , if the event was handled, otherwise. - protected bool RaiseMouseClickEvent (MouseEventArgs args) - { - // Pre-conditions - if (!Enabled) - { - // QUESTION: Is this right? Should a disabled view eat mouse clicks? - return args.Handled = false; - } - - // Cancellable event - if (OnMouseClick (args) || args.Handled) - { - return args.Handled; - } - - MouseClick?.Invoke (this, args); - - if (args.Handled) - { - return true; - } - - // Post-conditions - - // By default, this will raise Selecting/OnSelecting - Subclasses can override this via AddCommand (Command.Select ...). - args.Handled = InvokeCommandsBoundToMouse (args) == true; - - return args.Handled; - } - - /// - /// Called when a mouse click occurs. Check to see which button was clicked. - /// - /// - /// - /// Called when the mouse is either clicked or double-clicked. - /// - /// - /// If is , will be called on every mouse event - /// where - /// the mouse button is pressed. - /// - /// - /// - /// , if the event was handled, otherwise. - protected virtual bool OnMouseClick (MouseEventArgs args) { return false; } - - /// Raised when a mouse click occurs. - /// - /// - /// Raised when the mouse is either clicked or double-clicked. - /// - /// - /// If is , will be raised on every mouse event - /// where - /// the mouse button is pressed. - /// - /// - public event EventHandler? MouseClick; /// /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the click event @@ -553,7 +472,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); + // By default, this will raise Selecting/OnSelecting - Subclasses can override this via AddCommand (Command.Select ...). + mouseEvent.Handled = InvokeCommandsBoundToMouse (mouseEvent) == true; } return mouseEvent.Handled = true; @@ -562,7 +482,8 @@ public partial class View // Mouse APIs return false; } - #endregion Mouse Clicked Events + + #endregion WhenGrabbed Handlers #region Mouse Wheel Events diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index b003d0c75..7b0bc4779 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -75,8 +75,15 @@ public class Button : View, IDesignable KeyBindings.Remove (Key.Enter); KeyBindings.Add (Key.Enter, Command.HotKey); + // Replace default Select binding with HotKey for mouse clicks + MouseBindings.Clear (); + MouseBindings.Add (MouseFlags.Button1Clicked, Command.HotKey); + MouseBindings.Add (MouseFlags.Button2Clicked, Command.HotKey); + MouseBindings.Add (MouseFlags.Button3Clicked, Command.HotKey); + MouseBindings.Add (MouseFlags.Button4Clicked, Command.HotKey); + MouseBindings.Add (MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl, Command.HotKey); + TitleChanged += Button_TitleChanged; - MouseClick += Button_MouseClick; base.ShadowStyle = DefaultShadow; HighlightStates = DefaultHighlightStates; @@ -115,16 +122,6 @@ public class Button : View, IDesignable return false; } - private void Button_MouseClick (object sender, MouseEventArgs e) - { - if (e.Handled) - { - return; - } - - // TODO: With https://github.com/gui-cs/Terminal.Gui/issues/3778 we won't have to pass data: - e.Handled = InvokeCommand (Command.HotKey, new KeyBinding ([Command.HotKey], this, data: null)) == true; - } private void Button_TitleChanged (object sender, EventArgs e) { diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.cs index 9f93cdda2..9ecfe23c4 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.cs @@ -195,7 +195,7 @@ public class FileDialog : Dialog, IDesignable }; _tableView.CollectionNavigator = new FileDialogCollectionNavigator (this, _tableView); _tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select); - _tableView.MouseClick += OnTableViewMouseClick; + _tableView.Selecting += OnTableViewSelecting; Style.TableStyle = _tableView.Style; ColumnStyle nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0); @@ -1046,29 +1046,35 @@ public class FileDialog : Dialog, IDesignable } } - private void OnTableViewMouseClick (object? sender, MouseEventArgs e) + private void OnTableViewSelecting (object? sender, CommandEventArgs e) { - Point? clickedCell = _tableView.ScreenToCell (e.Position.X, e.Position.Y, out int? clickedCol); + // Only handle mouse clicks, not keyboard selections + if (e.Context is not CommandContext { Binding.MouseEventArgs: { } mouseArgs }) + { + return; + } + + Point? clickedCell = _tableView.ScreenToCell (mouseArgs.Position.X, mouseArgs.Position.Y, out int? clickedCol); if (clickedCol is { }) { - if (e.Flags.HasFlag (MouseFlags.Button1Clicked)) + if (mouseArgs.Flags.HasFlag (MouseFlags.Button1Clicked)) { // left click in a header SortColumn (clickedCol.Value); } - else if (e.Flags.HasFlag (MouseFlags.Button3Clicked)) + else if (mouseArgs.Flags.HasFlag (MouseFlags.Button3Clicked)) { // right click in a header - ShowHeaderContextMenu (clickedCol.Value, e); + ShowHeaderContextMenu (clickedCol.Value, mouseArgs); } } else { - if (clickedCell is { } && e.Flags.HasFlag (MouseFlags.Button3Clicked)) + if (clickedCell is { } && mouseArgs.Flags.HasFlag (MouseFlags.Button3Clicked)) { // right click in rest of table - ShowCellContextMenu (clickedCell, e); + ShowCellContextMenu (clickedCell, mouseArgs); } } } diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index c11347f9f..512e961fc 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -27,15 +27,6 @@ public class Label : View, IDesignable AddCommand (Command.HotKey, InvokeHotKeyOnNextPeer!); TitleChanged += Label_TitleChanged; - MouseClick += Label_MouseClick; - } - - private void Label_MouseClick (object? sender, MouseEventArgs e) - { - if (!CanFocus) - { - e.Handled = InvokeCommand (Command.HotKey, new ([Command.HotKey], this, this)) == true; - } } private void Label_TitleChanged (object? sender, EventArgs e) @@ -89,6 +80,18 @@ public class Label : View, IDesignable return false; } + /// + protected override bool OnSelecting (CommandEventArgs args) + { + // If Label can't focus and is clicked, invoke HotKey on next peer + if (!CanFocus) + { + return InvokeCommand (Command.HotKey, args.Context) == true; + } + + return base.OnSelecting (args); + } + /// bool IDesignable.EnableForDesign () { diff --git a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs index 4d7468434..e854bcc63 100644 --- a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs +++ b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs @@ -517,12 +517,18 @@ public class ScrollBar : View, IOrientation, IDesignable // TODO: Change this to work OnMouseEvent with continuouse press and grab so it's continous. /// - protected override bool OnMouseClick (MouseEventArgs args) + protected override bool OnSelecting (CommandEventArgs args) { - // Check if the mouse click is a single click - if (!args.IsSingleClicked) + // Only handle mouse clicks + if (args.Context is not CommandContext { Binding.MouseEventArgs: { } mouseArgs }) { - return false; + return base.OnSelecting (args); + } + + // Check if the mouse click is a single click + if (!mouseArgs.IsSingleClicked) + { + return base.OnSelecting (args); } int sliderCenter; @@ -531,12 +537,12 @@ public class ScrollBar : View, IOrientation, IDesignable if (Orientation == Orientation.Vertical) { sliderCenter = 1 + _slider.Frame.Y + _slider.Frame.Height / 2; - distanceFromCenter = args.Position.Y - sliderCenter; + distanceFromCenter = mouseArgs.Position.Y - sliderCenter; } else { sliderCenter = 1 + _slider.Frame.X + _slider.Frame.Width / 2; - distanceFromCenter = args.Position.X - sliderCenter; + distanceFromCenter = mouseArgs.Position.X - sliderCenter; } #if PROPORTIONAL_SCROLL_JUMP diff --git a/Terminal.Gui/Views/TabView/TabRow.cs b/Terminal.Gui/Views/TabView/TabRow.cs index 5534ac5be..54dd618fa 100644 --- a/Terminal.Gui/Views/TabView/TabRow.cs +++ b/Terminal.Gui/Views/TabView/TabRow.cs @@ -24,7 +24,10 @@ internal class TabRow : View Visible = false, Text = Glyphs.RightArrow.ToString () }; - _rightScrollIndicator.MouseClick += _host.Tab_MouseClick!; + _rightScrollIndicator.Selecting += (s, e) => + { + _host.Tab_Selecting (s, e); + }; _leftScrollIndicator = new View { @@ -34,7 +37,10 @@ internal class TabRow : View Visible = false, Text = Glyphs.LeftArrow.ToString () }; - _leftScrollIndicator.MouseClick += _host.Tab_MouseClick!; + _leftScrollIndicator.Selecting += (s, e) => + { + _host.Tab_Selecting (s, e); + }; Add (_rightScrollIndicator, _leftScrollIndicator); } diff --git a/Terminal.Gui/Views/TabView/TabView.cs b/Terminal.Gui/Views/TabView/TabView.cs index ae5b3d89e..b41ac7bb8 100644 --- a/Terminal.Gui/Views/TabView/TabView.cs +++ b/Terminal.Gui/Views/TabView/TabView.cs @@ -563,8 +563,8 @@ public class TabView : View if (maxWidth == 0) { tab.Visible = true; - tab.MouseClick += Tab_MouseClick!; - tab.Border!.MouseClick += Tab_MouseClick!; + tab.Selecting += Tab_Selecting!; + tab.Border!.Selecting += Tab_Selecting!; yield return tab; @@ -594,8 +594,8 @@ public class TabView : View // there is enough space! tab.Visible = true; - tab.MouseClick += Tab_MouseClick!; - tab.Border!.MouseClick += Tab_MouseClick!; + tab.Selecting += Tab_Selecting!; + tab.Border!.Selecting += Tab_Selecting!; yield return tab; @@ -636,9 +636,12 @@ public class TabView : View return Style.ShowTopLine ? 3 : 2; } - internal void Tab_MouseClick (object sender, MouseEventArgs e) + internal void Tab_Selecting (object? sender, CommandEventArgs e) { - e.Handled = _tabsBar.NewMouseEvent (e) == true; + if (e.Context is CommandContext { Binding.MouseEventArgs: { } mouseArgs }) + { + e.Handled = _tabsBar.NewMouseEvent (mouseArgs) == true; + } } private void UnSetCurrentTabs () @@ -652,8 +655,8 @@ public class TabView : View if (tab.Visible) { - tab.MouseClick -= Tab_MouseClick!; - tab.Border!.MouseClick -= Tab_MouseClick!; + tab.Selecting -= Tab_Selecting!; + tab.Border!.Selecting -= Tab_Selecting!; tab.Visible = false; } } @@ -662,8 +665,8 @@ public class TabView : View { foreach (Tab tabToRender in _tabLocations) { - tabToRender.MouseClick -= Tab_MouseClick!; - tabToRender.Border!.MouseClick -= Tab_MouseClick!; + tabToRender.Selecting -= Tab_Selecting!; + tabToRender.Border!.Selecting -= Tab_Selecting!; tabToRender.Visible = false; } diff --git a/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs b/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs index 5d0b41ed9..aaec4e9b5 100644 --- a/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs +++ b/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs @@ -30,7 +30,7 @@ public abstract class CheckBoxTableSourceWrapperBase : ITableSource tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select); - tableView.MouseClick += TableView_MouseClick; + tableView.Selecting += TableView_Selecting; tableView.CellToggled += TableView_CellToggled; } @@ -152,15 +152,22 @@ public abstract class CheckBoxTableSourceWrapperBase : ITableSource tableView.SetNeedsDraw (); } - private void TableView_MouseClick (object sender, MouseEventArgs e) +#nullable enable + private void TableView_Selecting (object? sender, CommandEventArgs e) { - // we only care about clicks (not movements) - if (!e.Flags.HasFlag (MouseFlags.Button1Clicked)) + // Only handle mouse clicks, not keyboard selections + if (e.Context is not CommandContext { Binding.MouseEventArgs: { } mouseArgs }) { return; } - Point? hit = tableView.ScreenToCell (e.Position.X, e.Position.Y, out int? headerIfAny); + // we only care about clicks (not movements) + if (!mouseArgs.Flags.HasFlag (MouseFlags.Button1Clicked)) + { + return; + } + + Point? hit = tableView.ScreenToCell (mouseArgs.Position.X, mouseArgs.Position.Y, out int? headerIfAny); if (headerIfAny.HasValue && headerIfAny.Value == 0) { @@ -191,4 +198,5 @@ public abstract class CheckBoxTableSourceWrapperBase : ITableSource tableView.SetNeedsDraw (); } } +#nullable restore } diff --git a/Terminal.Gui/Views/TableView/TreeTableSource.cs b/Terminal.Gui/Views/TableView/TreeTableSource.cs index 61e15f78a..8eccb4203 100644 --- a/Terminal.Gui/Views/TableView/TreeTableSource.cs +++ b/Terminal.Gui/Views/TableView/TreeTableSource.cs @@ -42,7 +42,7 @@ public class TreeTableSource : IEnumerableTableSource, IDisposable where T _tableView = table; _tree = tree; _tableView.KeyDown += Table_KeyPress; - _tableView.MouseClick += Table_MouseClick; + _tableView.Selecting += Table_Selecting; List colList = subsequentColumns.Keys.ToList (); colList.Insert (0, firstColumnName); @@ -56,7 +56,7 @@ public class TreeTableSource : IEnumerableTableSource, IDisposable where T public void Dispose () { _tableView.KeyDown -= Table_KeyPress; - _tableView.MouseClick -= Table_MouseClick; + _tableView.Selecting -= Table_Selecting; _tree.Dispose (); } @@ -168,9 +168,16 @@ public class TreeTableSource : IEnumerableTableSource, IDisposable where T } } - private void Table_MouseClick (object sender, MouseEventArgs e) +#nullable enable + private void Table_Selecting (object? sender, CommandEventArgs e) { - Point? hit = _tableView.ScreenToCell (e.Position.X, e.Position.Y, out int? headerIfAny, out int? offsetX); + // Only handle mouse clicks, not keyboard selections + if (e.Context is not CommandContext { Binding.MouseEventArgs: { } mouseArgs }) + { + return; + } + + Point? hit = _tableView.ScreenToCell (mouseArgs.Position.X, mouseArgs.Position.Y, out int? headerIfAny, out int? offsetX); if (hit is null || headerIfAny is { } || !IsInTreeColumn (hit.Value.X, false) || offsetX is null) { @@ -202,4 +209,5 @@ public class TreeTableSource : IEnumerableTableSource, IDisposable where T _tableView.SetNeedsDraw (); } } +#nullable restore } diff --git a/Tests/IntegrationTests/FluentTests/GuiTestContextMouseEventTests.cs b/Tests/IntegrationTests/FluentTests/GuiTestContextMouseEventTests.cs index 073852944..1a84a0b61 100644 --- a/Tests/IntegrationTests/FluentTests/GuiTestContextMouseEventTests.cs +++ b/Tests/IntegrationTests/FluentTests/GuiTestContextMouseEventTests.cs @@ -213,7 +213,7 @@ public class GuiTestContextMouseEventTests (ITestOutputHelper outputHelper) Height = 5 }; - view.MouseClick += (s, e) => clickCount++; + view.MouseEvent += (s, e) => clickCount++; using GuiTestContext context = With.A (40, 10, d, _out) .Add (view); @@ -240,7 +240,7 @@ public class GuiTestContextMouseEventTests (ITestOutputHelper outputHelper) Height = 5 }; - view.MouseClick += (s, e) => clickCount++; + view.MouseEvent += (s, e) => clickCount++; using GuiTestContext context = With.A (40, 10, d, _out) .Add (view) diff --git a/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs b/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs index 887b10c0d..056640c49 100644 --- a/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs +++ b/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs @@ -119,7 +119,7 @@ public class ApplicationMouseTests var mouseEvent = new MouseEventArgs { ScreenPosition = new (clickX, clickY), Flags = MouseFlags.Button1Clicked }; - view.MouseClick += (s, e) => + view.MouseEvent += (s, e) => { Assert.Equal (expectedX, e.Position.X); Assert.Equal (expectedY, e.Position.Y); diff --git a/Tests/UnitTests/View/Mouse/MouseTests.cs b/Tests/UnitTests/View/Mouse/MouseTests.cs index 05326f7ea..c832af152 100644 --- a/Tests/UnitTests/View/Mouse/MouseTests.cs +++ b/Tests/UnitTests/View/Mouse/MouseTests.cs @@ -25,7 +25,7 @@ public class MouseTests : TestsAllViews [AutoInitShutdown] public void ButtonPressed_In_Border_Starts_Drag (int marginThickness, int borderThickness, int paddingThickness, int xy, bool expectedMoved) { - var testView = new View + View testView = new () { CanFocus = true, X = 4, @@ -38,7 +38,7 @@ public class MouseTests : TestsAllViews testView.Border!.Thickness = new (borderThickness); testView.Padding!.Thickness = new (paddingThickness); - var top = new Runnable (); + Runnable top = new (); top.Add (testView); SessionToken rs = Application.Begin (top); @@ -61,9 +61,9 @@ public class MouseTests : TestsAllViews [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)] public void WantContinuousButtonPressed_False_Button_Press_Release_DoesNotClick (MouseFlags pressed, MouseFlags released, MouseFlags clicked) { - var me = new MouseEventArgs (); + MouseEventArgs me = new (); - var view = new View + View view = new () { Width = 1, Height = 1, @@ -72,7 +72,7 @@ public class MouseTests : TestsAllViews var clickedCount = 0; - view.MouseClick += (s, e) => clickedCount++; + view.MouseEvent += (s, e) => clickedCount += e.IsSingleDoubleOrTripleClicked ? 1 : 0; me.Flags = pressed; view.NewMouseEvent (me); @@ -99,36 +99,6 @@ public class MouseTests : TestsAllViews Application.ResetState (true); } - [Theory] - [InlineData (MouseFlags.Button1Clicked)] - [InlineData (MouseFlags.Button2Clicked)] - [InlineData (MouseFlags.Button3Clicked)] - [InlineData (MouseFlags.Button4Clicked)] - public void WantContinuousButtonPressed_True_Button_Clicked_Raises_MouseClick (MouseFlags clicked) - { - var me = new MouseEventArgs (); - - var view = new View - { - Width = 1, - Height = 1, - WantContinuousButtonPressed = true - }; - - var clickedCount = 0; - - view.MouseClick += (s, e) => clickedCount++; - - me.Flags = clicked; - view.NewMouseEvent (me); - Assert.Equal (1, clickedCount); - - view.Dispose (); - - // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set - Application.ResetState (true); - } - [Theory] [InlineData (MouseFlags.Button1Clicked)] [InlineData (MouseFlags.Button2Clicked)] @@ -136,9 +106,9 @@ public class MouseTests : TestsAllViews [InlineData (MouseFlags.Button4Clicked)] public void WantContinuousButtonPressed_True_Button_Clicked_Raises_Selecting (MouseFlags clicked) { - var me = new MouseEventArgs (); + MouseEventArgs me = new (); - var view = new View + View view = new () { Width = 1, Height = 1, @@ -166,9 +136,9 @@ public class MouseTests : TestsAllViews [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released)] public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Button_Press_Release_Clicks (MouseFlags pressed, MouseFlags released) { - var me = new MouseEventArgs (); + MouseEventArgs me = new (); - var view = new View + View view = new () { Width = 1, Height = 1, @@ -177,8 +147,8 @@ public class MouseTests : TestsAllViews }; // Setup components for mouse held down - var timed = new TimedEvents (); - var grab = new MouseGrabHandler (); + TimedEvents timed = new (); + MouseGrabHandler grab = new (); view.MouseHeldDown = new MouseHeldDown (view, timed, grab); // Register callback for what to do when the mouse is held down @@ -226,9 +196,9 @@ public class MouseTests : TestsAllViews MouseFlags clicked ) { - var me = new MouseEventArgs (); + MouseEventArgs me = new (); - var view = new View + View view = new () { Width = 1, Height = 1, @@ -237,8 +207,8 @@ public class MouseTests : TestsAllViews }; // Setup components for mouse held down - var timed = new TimedEvents (); - var grab = new MouseGrabHandler (); + TimedEvents timed = new (); + MouseGrabHandler grab = new (); view.MouseHeldDown = new MouseHeldDown (view, timed, grab); // Register callback for what to do when the mouse is held down @@ -279,9 +249,9 @@ public class MouseTests : TestsAllViews [Fact] public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Move_InViewport_OutOfViewport_Keeps_Counting () { - var me = new MouseEventArgs (); + MouseEventArgs me = new (); - var view = new View + View view = new () { Width = 1, Height = 1, @@ -290,8 +260,8 @@ public class MouseTests : TestsAllViews }; // Setup components for mouse held down - var timed = new TimedEvents (); - var grab = new MouseGrabHandler (); + TimedEvents timed = new (); + MouseGrabHandler grab = new (); view.MouseHeldDown = new MouseHeldDown (view, timed, grab); // Register callback for what to do when the mouse is held down @@ -352,7 +322,7 @@ public class MouseTests : TestsAllViews //[InlineData (true, MouseState.PressedOutside, 0, 0, 0, 1)] //public void MouseState_Button1_Pressed_Then_Released_Outside (bool inViewport, MouseState highlightFlags, int noneCount, int expectedInCount, int expectedPressedCount, int expectedPressedOutsideCount) //{ - // var testView = new MouseEventTestView + // MouseEventTestView testView = new () // { // HighlightStates = highlightFlags // }; @@ -389,7 +359,7 @@ public class MouseTests : TestsAllViews [InlineData (10)] public void MouseState_None_Button1_Pressed_Move_No_Changes (int x) { - var testView = new MouseEventTestView + MouseEventTestView testView = new () { HighlightStates = MouseState.None }; @@ -403,7 +373,7 @@ public class MouseTests : TestsAllViews Assert.Equal (0, testView.MouseStatePressedOutsideCount); Assert.Equal (0, testView.MouseStateNoneCount); - // Move to x,0 + // Move to x,0 testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (x, 0) }); if (inViewport) @@ -421,7 +391,7 @@ public class MouseTests : TestsAllViews Assert.Equal (0, testView.MouseStateNoneCount); } - // Move backto 0,0 ; in viewport + // Move back to 0,0 ; in viewport testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed }); if (inViewport) @@ -451,7 +421,7 @@ public class MouseTests : TestsAllViews [InlineData (10)] public void MouseState_Pressed_Button1_Pressed_Move_Keeps_Pressed (int x) { - var testView = new MouseEventTestView + MouseEventTestView testView = new () { HighlightStates = MouseState.Pressed }; @@ -513,7 +483,7 @@ public class MouseTests : TestsAllViews [InlineData (10)] public void MouseState_PressedOutside_Button1_Pressed_Move_Raises_PressedOutside (int x) { - var testView = new MouseEventTestView + MouseEventTestView testView = new () { HighlightStates = MouseState.PressedOutside, WantContinuousButtonPressed = false @@ -576,7 +546,7 @@ public class MouseTests : TestsAllViews [InlineData (10)] public void MouseState_PressedOutside_Button1_Pressed_Move_Raises_PressedOutside_WantContinuousButtonPressed (int x) { - var testView = new MouseEventTestView + MouseEventTestView testView = new () { HighlightStates = MouseState.PressedOutside, WantContinuousButtonPressed = true diff --git a/Tests/UnitTests/Views/ShortcutTests.cs b/Tests/UnitTests/Views/ShortcutTests.cs index c517afc96..045644b9d 100644 --- a/Tests/UnitTests/Views/ShortcutTests.cs +++ b/Tests/UnitTests/Views/ShortcutTests.cs @@ -55,9 +55,9 @@ public class ShortcutTests // 0123456789 // " C 0 A " [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, 0, 1, 1, 1)] // mouseX = 2 is on the CommandView.Margin, so Shortcut will get MouseClick + [InlineData (0, 0, 1, 1, 1)] // mouseX = 0 is on the CommandView.Margin, so Shortcut will get MouseEvent for click + [InlineData (1, 0, 1, 1, 1)] // mouseX = 1 is on the CommandView, so CommandView will get MouseEvent for click + [InlineData (2, 0, 1, 1, 1)] // mouseX = 2 is on the CommandView.Margin, so Shortcut will get MouseEvent for click [InlineData (3, 0, 1, 1, 1)] [InlineData (4, 0, 1, 1, 1)] [InlineData (5, 0, 1, 1, 1)] diff --git a/Tests/UnitTests/Views/TextFieldTests.cs b/Tests/UnitTests/Views/TextFieldTests.cs index 1207a9ae0..8b73824e8 100644 --- a/Tests/UnitTests/Views/TextFieldTests.cs +++ b/Tests/UnitTests/Views/TextFieldTests.cs @@ -917,7 +917,7 @@ public class TextFieldTests (ITestOutputHelper output) var tf = new TextField { Width = 10 }; var clickCounter = 0; - tf.MouseClick += (s, m) => { clickCounter++; }; + tf.MouseEvent += (s, m) => { clickCounter++; }; var top = new Runnable (); top.Add (tf); diff --git a/Tests/UnitTestsParallelizable/Application/MouseTests.cs b/Tests/UnitTestsParallelizable/Application/MouseTests.cs index 71dbd6b09..df000e8fe 100644 --- a/Tests/UnitTestsParallelizable/Application/MouseTests.cs +++ b/Tests/UnitTestsParallelizable/Application/MouseTests.cs @@ -128,46 +128,46 @@ public class MouseTests [Theory] // click on border - [InlineData (0, 0, 0, 0, 0, false)] - [InlineData (0, 1, 0, 0, 0, false)] - [InlineData (0, 0, 1, 0, 0, false)] - [InlineData (0, 9, 0, 0, 0, false)] - [InlineData (0, 0, 9, 0, 0, false)] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 1, 0, 0, 0, 0)] + [InlineData (0, 0, 1, 0, 0, 0)] + [InlineData (0, 9, 0, 0, 0, 0)] + [InlineData (0, 0, 9, 0, 0, 0)] // outside border - [InlineData (0, 10, 0, 0, 0, false)] - [InlineData (0, 0, 10, 0, 0, false)] + [InlineData (0, 10, 0, 0, 0, 0)] + [InlineData (0, 0, 10, 0, 0, 0)] // view is offset from origin ; click is on border - [InlineData (1, 1, 1, 0, 0, false)] - [InlineData (1, 2, 1, 0, 0, false)] - [InlineData (1, 1, 2, 0, 0, false)] - [InlineData (1, 10, 1, 0, 0, false)] - [InlineData (1, 1, 10, 0, 0, false)] + [InlineData (1, 1, 1, 0, 0, 0)] + [InlineData (1, 2, 1, 0, 0, 0)] + [InlineData (1, 1, 2, 0, 0, 0)] + [InlineData (1, 10, 1, 0, 0, 0)] + [InlineData (1, 1, 10, 0, 0, 0)] // outside border - [InlineData (1, -1, 0, 0, 0, false)] - [InlineData (1, 0, -1, 0, 0, false)] - [InlineData (1, 10, 10, 0, 0, false)] - [InlineData (1, 11, 11, 0, 0, false)] + [InlineData (1, -1, 0, 0, 0, 0)] + [InlineData (1, 0, -1, 0, 0, 0)] + [InlineData (1, 10, 10, 0, 0, 0)] + [InlineData (1, 11, 11, 0, 0, 0)] // view is at origin, click is inside border - [InlineData (0, 1, 1, 0, 0, true)] - [InlineData (0, 2, 1, 1, 0, true)] - [InlineData (0, 1, 2, 0, 1, true)] - [InlineData (0, 8, 1, 7, 0, true)] - [InlineData (0, 1, 8, 0, 7, true)] - [InlineData (0, 8, 8, 7, 7, true)] + [InlineData (0, 1, 1, 0, 0, 1)] + [InlineData (0, 2, 1, 1, 0, 1)] + [InlineData (0, 1, 2, 0, 1, 1)] + [InlineData (0, 8, 1, 7, 0, 1)] + [InlineData (0, 1, 8, 0, 7, 1)] + [InlineData (0, 8, 8, 7, 7, 1)] // view is offset from origin ; click inside border // our view is 10x10, but has a border, so it's bounds is 8x8 - [InlineData (1, 2, 2, 0, 0, true)] - [InlineData (1, 3, 2, 1, 0, true)] - [InlineData (1, 2, 3, 0, 1, true)] - [InlineData (1, 9, 2, 7, 0, true)] - [InlineData (1, 2, 9, 0, 7, true)] - [InlineData (1, 9, 9, 7, 7, true)] - [InlineData (1, 10, 10, 7, 7, false)] + [InlineData (1, 2, 2, 0, 0, 1)] + [InlineData (1, 3, 2, 1, 0, 1)] + [InlineData (1, 2, 3, 0, 1, 1)] + [InlineData (1, 9, 2, 7, 0, 1)] + [InlineData (1, 2, 9, 0, 7, 1)] + [InlineData (1, 9, 9, 7, 7, 1)] + [InlineData (1, 10, 10, 7, 7, 0)] //01234567890123456789 // |12345678| @@ -178,13 +178,13 @@ public class MouseTests int clickY, int expectedX, int expectedY, - bool expectedClicked + int expectedClickedCount ) { Size size = new (10, 10); Point pos = new (offset, offset); - var clicked = false; + int clickedCount = 0; using IApplication? application = Application.Create (); @@ -208,14 +208,14 @@ public class MouseTests var mouseEvent = new MouseEventArgs { Position = new (clickX, clickY), ScreenPosition = new (clickX, clickY), Flags = MouseFlags.Button1Clicked }; - view.MouseClick += (s, e) => + view.MouseEvent += (_s, e) => { Assert.Equal (expectedX, e.Position.X); Assert.Equal (expectedY, e.Position.Y); - clicked = true; + clickedCount += e.IsSingleDoubleOrTripleClicked ? 1 : 0; }; application.Mouse.RaiseMouseEvent (mouseEvent); - Assert.Equal (expectedClicked, clicked); + Assert.Equal (expectedClickedCount, clickedCount); } } diff --git a/Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs index 5f8450efa..d132c220d 100644 --- a/Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs +++ b/Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs @@ -32,9 +32,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output) }; Point? receivedPosition = null; - var eventReceived = false; + bool eventReceived = false; - view.MouseEvent += (sender, args) => + view.MouseEvent += (_, args) => { eventReceived = true; receivedPosition = args.Position; @@ -90,9 +90,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output) }; Point? receivedPosition = null; - var eventReceived = false; + bool eventReceived = false; - view.MouseEvent += (sender, args) => + view.MouseEvent += (_, args) => { eventReceived = true; receivedPosition = args.Position; @@ -100,7 +100,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output) MouseEventArgs mouseEvent = new () { - Position = new Point (viewRelativeX, viewRelativeY), + Position = new (viewRelativeX, viewRelativeY), Flags = MouseFlags.Button1Clicked }; @@ -146,9 +146,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output) superView.Add (subView); Point? subViewReceivedPosition = null; - var subViewEventReceived = false; + bool subViewEventReceived = false; - subView.MouseEvent += (sender, args) => + subView.MouseEvent += (_, args) => { subViewEventReceived = true; subViewReceivedPosition = args.Position; @@ -175,7 +175,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output) } [Fact] - public void MouseClick_OnSubView_RaisesMouseClickEvent () + public void MouseClick_OnSubView_RaisesSelectingEvent () { // Arrange View superView = new () @@ -194,8 +194,8 @@ public class MouseEventRoutingTests (ITestOutputHelper output) superView.Add (subView); - var clickCount = 0; - subView.MouseClick += (sender, args) => clickCount++; + int selectingCount = 0; + subView.Selecting += (_, _) => selectingCount++; MouseEventArgs mouseEvent = new () { @@ -207,7 +207,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output) subView.NewMouseEvent (mouseEvent); // Assert - Assert.Equal (1, clickCount); + Assert.Equal (1, selectingCount); subView.Dispose (); superView.Dispose (); @@ -222,20 +222,20 @@ public class MouseEventRoutingTests (ITestOutputHelper output) { // Arrange View view = new () { Width = 10, Height = 10 }; - var handlerCalled = false; - var clickHandlerCalled = false; + bool handlerCalled = false; + bool clickHandlerCalled = false; - view.MouseEvent += (sender, args) => + view.MouseEvent += (_, args) => { handlerCalled = true; args.Handled = true; // Mark as handled }; - view.MouseClick += (sender, args) => { clickHandlerCalled = true; }; + view.MouseEvent += (_, e) => { clickHandlerCalled = !e.IsSingleDoubleOrTripleClicked; ; }; MouseEventArgs mouseEvent = new () { - Position = new Point (5, 5), + Position = new (5, 5), Flags = MouseFlags.Button1Clicked }; @@ -255,20 +255,17 @@ public class MouseEventRoutingTests (ITestOutputHelper output) { // Arrange View view = new () { Width = 10, Height = 10 }; - var eventHandlerCalled = false; - var clickHandlerCalled = false; + bool eventHandlerCalled = false; - view.MouseEvent += (sender, args) => + view.MouseEvent += (_, _) => { eventHandlerCalled = true; // Don't set Handled = true }; - view.MouseClick += (sender, args) => { clickHandlerCalled = true; }; - MouseEventArgs mouseEvent = new () { - Position = new Point (5, 5), + Position = new (5, 5), Flags = MouseFlags.Button1Clicked }; @@ -277,7 +274,6 @@ public class MouseEventRoutingTests (ITestOutputHelper output) // Assert Assert.True (eventHandlerCalled); - Assert.True (clickHandlerCalled); // Click handler should be called when event is not handled view.Dispose (); } @@ -294,11 +290,10 @@ public class MouseEventRoutingTests (ITestOutputHelper output) { // Arrange View view = new () { Width = 10, Height = 10 }; - var pressedCount = 0; - var releasedCount = 0; - var clickedCount = 0; + int pressedCount = 0; + int releasedCount = 0; - view.MouseEvent += (sender, args) => + view.MouseEvent += (_, args) => { if (args.Flags.HasFlag (MouseFlags.Button1Pressed)) { @@ -311,11 +306,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output) } }; - view.MouseClick += (sender, args) => { clickedCount++; }; - MouseEventArgs mouseEvent = new () { - Position = new Point (5, 5), + Position = new (5, 5), Flags = flags }; @@ -325,7 +318,6 @@ public class MouseEventRoutingTests (ITestOutputHelper output) // Assert Assert.Equal (expectedPressed, pressedCount); Assert.Equal (expectedReleased, releasedCount); - Assert.Equal (expectedClicked, clickedCount); view.Dispose (); } @@ -339,13 +331,13 @@ public class MouseEventRoutingTests (ITestOutputHelper output) { // Arrange View view = new () { Width = 10, Height = 10 }; - var clickCount = 0; + int clickCount = 0; - view.MouseClick += (sender, args) => clickCount++; + view.MouseEvent += (_, a) => clickCount += a.IsSingleDoubleOrTripleClicked ? 1 : 0; MouseEventArgs mouseEvent = new () { - Position = new Point (5, 5), + Position = new (5, 5), Flags = clickFlag }; @@ -373,12 +365,12 @@ public class MouseEventRoutingTests (ITestOutputHelper output) Enabled = false }; - var eventCalled = false; - view.MouseEvent += (sender, args) => { eventCalled = true; }; + bool eventCalled = false; + view.MouseEvent += (_, _) => { eventCalled = true; }; MouseEventArgs mouseEvent = new () { - Position = new Point (5, 5), + Position = new (5, 5), Flags = MouseFlags.Button1Clicked }; @@ -392,7 +384,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output) } [Fact] - public void View_Disabled_DoesNotRaiseMouseClickEvent () + public void View_Disabled_DoesNotRaiseSelectingEvent () { // Arrange View view = new () @@ -402,12 +394,12 @@ public class MouseEventRoutingTests (ITestOutputHelper output) Enabled = false }; - var clickCalled = false; - view.MouseClick += (sender, args) => { clickCalled = true; }; + bool selectingCalled = false; + view.Selecting += (_, _) => { selectingCalled = true; }; MouseEventArgs mouseEvent = new () { - Position = new Point (5, 5), + Position = new (5, 5), Flags = MouseFlags.Button1Clicked }; @@ -415,7 +407,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output) view.NewMouseEvent (mouseEvent); // Assert - Assert.False (clickCalled); + Assert.False (selectingCalled); view.Dispose (); } @@ -445,7 +437,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output) MouseEventArgs mouseEvent = new () { - Position = new Point (2, 2), + Position = new (2, 2), Flags = MouseFlags.Button1Clicked }; @@ -475,12 +467,12 @@ public class MouseEventRoutingTests (ITestOutputHelper output) superView.Add (view); - var selectingCount = 0; - view.Selecting += (sender, args) => selectingCount++; + int selectingCount = 0; + view.Selecting += (_, _) => selectingCount++; MouseEventArgs mouseEvent = new () { - Position = new Point (5, 5), + Position = new (5, 5), Flags = MouseFlags.Button1Clicked }; diff --git a/docfx/docs/View.md b/docfx/docs/View.md index c22585d1e..f9c8d2138 100644 --- a/docfx/docs/View.md +++ b/docfx/docs/View.md @@ -148,7 +148,7 @@ See the [Mouse Deep Dive](mouse.md). - [View.WantContinuousButtonPresses](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_WantContinuousButtonPresses) - Enables continuous button press events - [View.Highlight](~/api/Terminal.Gui.ViewBase.View.yml) - Event for visual feedback on mouse hover/click - [View.HighlightStyle](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_HighlightStyle) - Visual style when highlighted -- Events: `MouseEnter`, `MouseLeave`, `MouseClick`, `MouseEvent` +- Events: `MouseEnter`, `MouseLeave`, `MouseEvent` ### Layout and Arrangement @@ -368,7 +368,6 @@ The mouse subsystem processes mouse events through: 3. [View.MouseEnter](~/api/Terminal.Gui.ViewBase.View.yml) / [View.MouseLeave](~/api/Terminal.Gui.ViewBase.View.yml) events 4. [View.MouseBindings](~/api/Terminal.Gui.ViewBase.View.yml) - Converts mouse actions to commands 5. Command handlers -6. [View.MouseClick](~/api/Terminal.Gui.ViewBase.View.yml) event (high-level) ### Layout diff --git a/docfx/docs/migratingfromv1.md b/docfx/docs/migratingfromv1.md index 7e81994e6..e19ac5033 100644 --- a/docfx/docs/migratingfromv1.md +++ b/docfx/docs/migratingfromv1.md @@ -555,7 +555,7 @@ void HandleMouse(object? sender, MouseEventArgs args) { } ```csharp // v2 - Viewport-relative coordinates -view.MouseClick += (s, e) => +view.MouseEvent += (s, e) => { // e.Position is relative to view's Viewport var x = e.Position.X; // 0 = left edge of viewport @@ -563,16 +563,120 @@ view.MouseClick += (s, e) => }; ``` -### Highlight Event +### Mouse Click Handling -v2 adds a `Highlight` event for visual feedback: +**v1:** +```csharp +// v1 - MouseClick event +view.MouseClick += (mouseEvent) => +{ + // Handle click + DoSomething(); +}; +``` + +**v2:** +```csharp +// v2 - Use MouseBindings + Commands + Selecting event +view.MouseBindings.Add(MouseFlags.Button1Clicked, Command.Select); +view.Selecting += (s, e) => +{ + // Handle selection (called when Button1Clicked) + DoSomething(); +}; + +// Alternative: Use MouseEvent for low-level handling +view.MouseEvent += (s, e) => +{ + if (e.Flags.HasFlag(MouseFlags.Button1Clicked)) + { + DoSomething(); + e.Handled = true; + } +}; +``` + +**Key Changes:** +- `View.MouseClick` event has been **removed** +- Use `MouseBindings` to map mouse events to `Command`s +- Default mouse bindings invoke `Command.Select` which raises the `Selecting` event +- For custom behavior, override `OnSelecting` or subscribe to the `Selecting` event +- For low-level mouse handling, use `MouseEvent` directly + +**Migration Pattern:** +```csharp +// ❌ v1 - OnMouseClick override +protected override bool OnMouseClick(MouseEventArgs mouseEvent) +{ + if (mouseEvent.Flags.HasFlag(MouseFlags.Button1Clicked)) + { + PerformAction(); + return true; + } + return base.OnMouseClick(mouseEvent); +} + +// ✅ v2 - OnSelecting override +protected override bool OnSelecting(CommandEventArgs args) +{ + if (args.Context is CommandContext { Binding.MouseEventArgs: { } mouseArgs }) + { + // Access mouse position and flags via context + if (mouseArgs.Flags.HasFlag(MouseFlags.Button1Clicked)) + { + PerformAction(); + return true; + } + } + return base.OnSelecting(args); +} + +// ✅ v2 - Selecting event (simpler) +view.Selecting += (s, e) => +{ + PerformAction(); + e.Handled = true; +}; +``` + +**Accessing Mouse Position in Selecting Event:** +```csharp +view.Selecting += (s, e) => +{ + // Extract mouse event args from command context + if (e.Context is CommandContext { Binding.MouseEventArgs: { } mouseArgs }) + { + Point position = mouseArgs.Position; + MouseFlags flags = mouseArgs.Flags; + + // Use position and flags for custom logic + HandleClick(position, flags); + e.Handled = true; + } +}; +``` + +### Mouse State and Highlighting + +v2 adds enhanced mouse state tracking: ```csharp -view.Highlight += (s, e) => +// Configure which mouse states trigger highlighting +view.HighlightStates = MouseState.In | MouseState.Pressed; + +// React to mouse state changes +view.MouseStateChanged += (s, e) => { - // Provide visual feedback on mouse hover + switch (e.Value) + { + case MouseState.In: + // Mouse entered view + break; + case MouseState.Pressed: + // Mouse button pressed in view + break; + } }; -view.HighlightStyle = HighlightStyle.Hover; ``` See [Mouse Deep Dive](mouse.md) for complete details. diff --git a/docfx/docs/mouse.md b/docfx/docs/mouse.md index 56e8dff85..6c5aaed88 100644 --- a/docfx/docs/mouse.md +++ b/docfx/docs/mouse.md @@ -44,8 +44,12 @@ public class MyView : View AddCommand (Command.ScrollDown, () => ScrollVertical (1)); MouseBindings.Add (MouseFlags.WheelDown, Command.ScrollDown); - AddCommand (Command.Select, () => SelectItem()); - MouseBindings.Add (MouseFlags.Button1Clicked, Command.Select); + // Mouse clicks invoke Command.Select by default + // Override to customize click behavior + AddCommand (Command.Select, () => { + SelectItem(); + return true; + }); } } ``` @@ -56,17 +60,65 @@ The @Terminal.Gui.Input.Command enum lists generic operations that are implement Here are some common mouse binding patterns used throughout Terminal.Gui: -* **Click Events**: `MouseFlags.Button1Clicked` for primary selection/activation +* **Click Events**: `MouseFlags.Button1Clicked` for primary selection/activation - maps to `Command.Select` by default * **Double-Click Events**: `MouseFlags.Button1DoubleClicked` for default actions (like opening/accepting) * **Right-Click Events**: `MouseFlags.Button3Clicked` for context menus * **Scroll Events**: `MouseFlags.WheelUp` and `MouseFlags.WheelDown` for scrolling content * **Drag Events**: `MouseFlags.Button1Pressed` combined with mouse move tracking for drag operations +### Default Mouse Bindings + +By default, all views have the following mouse bindings configured: + +```cs +MouseBindings.Add (MouseFlags.Button1Clicked, Command.Select); +MouseBindings.Add (MouseFlags.Button2Clicked, Command.Select); +MouseBindings.Add (MouseFlags.Button3Clicked, Command.Select); +MouseBindings.Add (MouseFlags.Button4Clicked, Command.Select); +MouseBindings.Add (MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl, Command.Select); +``` + +When a mouse click occurs, the `Command.Select` is invoked, which raises the `Selecting` event. Views can override `OnSelecting` or subscribe to the `Selecting` event to handle clicks: + +```cs +public class MyView : View +{ + public MyView() + { + // Option 1: Subscribe to Selecting event + Selecting += (s, e) => + { + if (e.Context is CommandContext { Binding.MouseEventArgs: { } mouseArgs }) + { + // Access mouse position and flags + HandleSelection(mouseArgs.Position, mouseArgs.Flags); + e.Handled = true; + } + }; + } + + // Option 2: Override OnSelecting + protected override bool OnSelecting(CommandEventArgs args) + { + if (args.Context is CommandContext { Binding.MouseEventArgs: { } mouseArgs }) + { + // Custom selection logic with mouse position + if (mouseArgs.Position.Y == 0) + { + HandleHeaderClick(); + return true; + } + } + return base.OnSelecting(args); + } +} +``` + ## Mouse Events At the core of *Terminal.Gui*'s mouse API is the @Terminal.Gui.Input.MouseEventArgs class. The @Terminal.Gui.Input.MouseEventArgs class provides a platform-independent abstraction for common mouse events. Every mouse event can be fully described in a @Terminal.Gui.Input.MouseEventArgs instance, and most of the mouse-related APIs are simply helper functions for decoding a @Terminal.Gui.Input.MouseEventArgs. -When the user does something with the mouse, the driver maps the platform-specific mouse event into a `MouseEventArgs` and calls `IApplication.Mouse.RaiseMouseEvent`. Then, `IApplication.Mouse.RaiseMouseEvent` determines which `View` the event should go to. The `View.OnMouseEvent` method can be overridden or the `View.MouseEvent` event can be subscribed to, to handle the low-level mouse event. If the low-level event is not handled by a view, `IApplication` will then call the appropriate high-level helper APIs. For example, if the user double-clicks the mouse, `View.OnMouseClick` will be called/`View.MouseClick` will be raised with the event arguments indicating which mouse button was double-clicked. +When the user does something with the mouse, the driver maps the platform-specific mouse event into a `MouseEventArgs` and calls `IApplication.Mouse.RaiseMouseEvent`. Then, `IApplication.Mouse.RaiseMouseEvent` determines which `View` the event should go to. The `View.OnMouseEvent` method can be overridden or the `View.MouseEvent` event can be subscribed to, to handle the low-level mouse event. If the low-level event is not handled by a view, `IApplication` will then call the appropriate high-level helper APIs. ### Mouse Event Processing Flow @@ -77,8 +129,9 @@ Mouse events are processed through the following workflow using the [Cancellable 3. **View Level**: The target view processes the event through: - `OnMouseEvent` (virtual method that can be overridden) - `MouseEvent` event (for event subscribers) - - Mouse bindings (if the event wasn't handled) - - High-level events like `OnMouseClick`, `MouseEnter`, `MouseLeave` + - Mouse bindings (if the event wasn't handled) which invoke commands + - Command handlers (e.g., `OnSelecting` for `Command.Select`) + - High-level events like `MouseEnter`, `MouseLeave` ### Handling Mouse Events Directly @@ -114,15 +167,76 @@ public class CustomView : View } ``` +### Handling Mouse Clicks + +The recommended pattern for handling mouse clicks is to use the `Selecting` event or override `OnSelecting`. This integrates with the command system and provides access to mouse event details through the command context: + +```cs +public class ClickableView : View +{ + public ClickableView() + { + Selecting += OnSelecting; + } + + private void OnSelecting(object sender, CommandEventArgs e) + { + // Extract mouse event information from command context + if (e.Context is CommandContext { Binding.MouseEventArgs: { } mouseArgs }) + { + // Access mouse position (viewport-relative) + Point clickPosition = mouseArgs.Position; + + // Check which button was clicked + if (mouseArgs.Flags.HasFlag(MouseFlags.Button1Clicked)) + { + HandleLeftClick(clickPosition); + } + else if (mouseArgs.Flags.HasFlag(MouseFlags.Button3Clicked)) + { + ShowContextMenu(clickPosition); + } + + e.Handled = true; + } + } +} +``` + +For views that need different behavior for different mouse buttons, configure custom mouse bindings: + +```cs +public class MultiButtonView : View +{ + public MultiButtonView() + { + // Clear default bindings + MouseBindings.Clear(); + + // Map different buttons to different commands + MouseBindings.Add(MouseFlags.Button1Clicked, Command.Select); + MouseBindings.Add(MouseFlags.Button3Clicked, Command.ContextMenu); + + AddCommand(Command.ContextMenu, HandleContextMenu); + } + + private bool HandleContextMenu() + { + // Show context menu + return true; + } +} +``` + ## Mouse State The @Terminal.Gui.ViewBase.View.MouseState property provides an abstraction for the current state of the mouse, enabling views to do interesting things like change their appearance based on the mouse state. Mouse states include: * **Normal** - Default state when mouse is not interacting with the view -* **Over** - Mouse is positioned over the view +* **In** - Mouse is positioned over the view (inside the viewport) * **Pressed** - Mouse button is pressed down while over the view -* **Clicked** - Mouse was clicked on the view +* **PressedOutside** - Mouse was pressed inside but moved outside the view It works in conjunction with the @Terminal.Gui.ViewBase.View.HighlightStates which is a list of mouse states that will cause a view to become highlighted. @@ -131,9 +245,9 @@ Subscribe to the @Terminal.Gui.ViewBase.View.MouseStateChanged event to be notif ```cs view.MouseStateChanged += (sender, e) => { - switch (e.NewState) + switch (e.Value) { - case MouseState.Over: + case MouseState.In: // Change appearance when mouse hovers break; case MouseState.Pressed: @@ -143,6 +257,13 @@ view.MouseStateChanged += (sender, e) => }; ``` +Configure which states should cause highlighting: + +```cs +// Highlight when mouse is over the view or when pressed +view.HighlightStates = MouseState.In | MouseState.Pressed; +``` + ## Mouse Button and Movement Concepts * **Down** - Indicates the user pushed a mouse button down. @@ -150,6 +271,7 @@ view.MouseStateChanged += (sender, e) => * **Released** - Indicates the user released a mouse button. * **Clicked** - Indicates the user pressed then released the mouse button while over a particular View. * **Double-Clicked** - Indicates the user clicked twice in rapid succession. +* **Triple-Clicked** - Indicates the user clicked three times in rapid succession. * **Moved** - Indicates the mouse moved to a new location since the last mouse event. * **Wheel** - Indicates the mouse wheel was scrolled up or down. @@ -179,7 +301,7 @@ public class MyView : View if (mouseEvent.Flags.HasFlag(MouseFlags.Button3Clicked)) { // Access application mouse functionality through View.App - App?.MouseEvent?.Invoke(this, mouseEvent); + App?.Mouse?.RaiseMouseEvent(mouseEvent); return true; } return base.OnMouseEvent(mouseEvent); @@ -209,16 +331,31 @@ view.MouseLeave += (sender, e) => Mouse coordinates in Terminal.Gui are provided in multiple coordinate systems: -* **Screen Coordinates** - Relative to the entire terminal screen (0,0 is top-left of terminal) -* **View Coordinates** - Relative to the view's content area (0,0 is top-left of view's viewport) +* **Screen Coordinates** - Relative to the entire terminal screen (0,0 is top-left of terminal) - available via `MouseEventArgs.ScreenPosition` +* **View Coordinates** - Relative to the view's viewport (0,0 is top-left of view's viewport) - available via `MouseEventArgs.Position` The `MouseEventArgs` provides both coordinate systems: -* `MouseEventArgs.Position` - Screen coordinates -* `MouseEventArgs.ViewPosition` - View-relative coordinates (when available) +* `MouseEventArgs.ScreenPosition` - Screen coordinates (absolute position on screen) +* `MouseEventArgs.Position` - Viewport-relative coordinates (position within the view's content area) + +When handling mouse events in views, use `Position` for viewport-relative coordinates: + +```cs +view.MouseEvent += (s, e) => +{ + // e.Position is viewport-relative + if (e.Position.X < 10 && e.Position.Y < 5) + { + // Click in top-left corner of viewport + } +}; +``` ## Best Practices -* **Use Mouse Bindings** when possible for simple mouse interactions - they integrate well with the Command system +* **Use Mouse Bindings and Commands** for simple mouse interactions - they integrate well with the Command system and work alongside keyboard bindings +* **Use the `Selecting` event** to handle mouse clicks - it's raised by the default `Command.Select` binding for all mouse buttons +* **Access mouse details via CommandContext** when you need position or flags in `Selecting` handlers * **Handle Mouse Events directly** for complex interactions like drag-and-drop or custom gestures * **Respect platform conventions** - use right-click for context menus, double-click for default actions * **Provide keyboard alternatives** - ensure all mouse functionality has keyboard equivalents @@ -231,9 +368,4 @@ The `MouseEventArgs` provides both coordinate systems: * Mouse wheel support may vary between platforms and terminals * Some terminals may not support all mouse buttons or modifier keys * Mouse coordinates are limited to character cell boundaries - sub-character precision is not available -* Performance can be impacted by excessive mouse move event handling - use mouse enter/leave events when appropriate rather than tracking all mouse moves - - - - - +* Performance can be impacted by excessive mouse move event handling - use mouse enter/leave events when appropriate rather than tracking all mouse moves \ No newline at end of file diff --git a/docfx/docs/navigation.md b/docfx/docs/navigation.md index 6ebb64911..941f99017 100644 --- a/docfx/docs/navigation.md +++ b/docfx/docs/navigation.md @@ -7,7 +7,7 @@ This document covers Terminal.Gui's navigation system, which determines: - What are the visual cues that help the user know what keystrokes will change the focus? - What are the visual cues that help the user know what keystrokes will cause action in elements of the application that don't currently have focus? - What is the order in which UI elements are traversed when using keyboard navigation? -- What are the default actions for standard key/mouse input (e.g. Hotkey, `Space`, `Enter`, `MouseClick`)? +- What are the default actions for standard key/mouse input (e.g. Hotkey, `Space`, `Enter`, or a mouse click)? ## See Also