Browse Source

Merge branch 'v2_develop' into copilot/fix-3ee850b1-eb6b-46b0-964b-3b98d2c0c14e

Tig 6 days ago
parent
commit
1cd71fefed
83 changed files with 3005 additions and 1858 deletions
  1. 100 0
      .github/workflows/stress-tests.yml
  2. 38 70
      .github/workflows/unit-tests.yml
  3. 32 26
      Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs
  4. 39 43
      Examples/UICatalog/Scenarios/ContextMenus.cs
  5. 5 6
      Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs
  6. 1 1
      Examples/UICatalog/Scenarios/Keys.cs
  7. 13 16
      Examples/UICatalog/Scenarios/ListColumns.cs
  8. 9 15
      Examples/UICatalog/Scenarios/Mouse.cs
  9. 102 107
      Examples/UICatalog/Scenarios/ScrollBarDemo.cs
  10. 29 23
      Examples/UICatalog/Scenarios/TableEditor.cs
  11. 94 105
      Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs
  12. 12 6
      Examples/UICatalog/Scenarios/TreeViewFileSystem.cs
  13. 10 9
      Examples/UICatalog/Scenarios/ViewExperiments.cs
  14. 199 0
      Examples/UICatalog/Scenarios/WideGlyphs.cs
  15. 2 0
      Terminal.Gui/App/ApplicationImpl.Run.cs
  16. 7 1
      Terminal.Gui/App/Mouse/MouseImpl.cs
  17. 2 1
      Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs
  18. 21 14
      Terminal.Gui/Drivers/DriverImpl.cs
  19. 20 20
      Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs
  20. 6 1
      Terminal.Gui/Drivers/IDriver.cs
  21. 6 0
      Terminal.Gui/Drivers/IOutput.cs
  22. 48 25
      Terminal.Gui/Drivers/OutputBase.cs
  23. 125 92
      Terminal.Gui/Drivers/OutputBufferImpl.cs
  24. 1 0
      Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs
  25. 3 1
      Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs
  26. 1 4
      Terminal.Gui/Input/Mouse/MouseEventArgs.cs
  27. 11 12
      Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs
  28. 1 1
      Terminal.Gui/ViewBase/Adornment/Border.cs
  29. 140 40
      Terminal.Gui/ViewBase/View.Drawing.cs
  30. 10 89
      Terminal.Gui/ViewBase/View.Mouse.cs
  31. 8 11
      Terminal.Gui/Views/Button.cs
  32. 14 8
      Terminal.Gui/Views/FileDialogs/FileDialog.cs
  33. 5 3
      Terminal.Gui/Views/GraphView/LegendAnnotation.cs
  34. 12 9
      Terminal.Gui/Views/Label.cs
  35. 1 1
      Terminal.Gui/Views/Runnable/Runnable.cs
  36. 1 1
      Terminal.Gui/Views/Runnable/RunnableTResult.cs
  37. 1 1
      Terminal.Gui/Views/Runnable/RunnableWrapper.cs
  38. 1 1
      Terminal.Gui/Views/Runnable/ViewRunnableExtensions.cs
  39. 11 5
      Terminal.Gui/Views/ScrollBar/ScrollBar.cs
  40. 8 2
      Terminal.Gui/Views/TabView/TabRow.cs
  41. 13 10
      Terminal.Gui/Views/TabView/TabView.cs
  42. 12 4
      Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs
  43. 12 4
      Terminal.Gui/Views/TableView/TreeTableSource.cs
  44. 2 2
      Tests/IntegrationTests/FluentTests/GuiTestContextMouseEventTests.cs
  45. 1 1
      Tests/TerminalGuiFluentTesting/GuiTestContext.Input.cs
  46. 1 1
      Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs
  47. 129 1
      Tests/UnitTests/DriverAssert.cs
  48. 29 57
      Tests/UnitTests/View/Mouse/MouseTests.cs
  49. 3 0
      Tests/UnitTests/View/ViewCommandTests.cs
  50. 3 3
      Tests/UnitTests/Views/ShortcutTests.cs
  51. 1 1
      Tests/UnitTests/Views/TextFieldTests.cs
  52. 1 1
      Tests/UnitTestsParallelizable/Application/Application.NavigationTests.cs
  53. 60 92
      Tests/UnitTestsParallelizable/Application/ApplicationImplTests.cs
  54. 0 510
      Tests/UnitTestsParallelizable/Application/ApplicationTests.cs
  55. 69 1
      Tests/UnitTestsParallelizable/Application/BeginEndTests.cs
  56. 56 49
      Tests/UnitTestsParallelizable/Application/CWP/ResultEventArgsTests.cs
  57. 170 0
      Tests/UnitTestsParallelizable/Application/InitTests.cs
  58. 1 1
      Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardImplThreadSafetyTests.cs
  59. 20 3
      Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardTests.cs
  60. 1 1
      Tests/UnitTestsParallelizable/Application/Mouse/ApplicationMouseEnterLeaveTests.cs
  61. 24 26
      Tests/UnitTestsParallelizable/Application/Mouse/MouseInterfaceTests.cs
  62. 35 37
      Tests/UnitTestsParallelizable/Application/Mouse/MouseTests.cs
  63. 1 1
      Tests/UnitTestsParallelizable/Application/Popover/Application.PopoverTests.cs
  64. 4 8
      Tests/UnitTestsParallelizable/Application/Popover/PopoverBaseImplTests.cs
  65. 252 0
      Tests/UnitTestsParallelizable/Application/RunTests.cs
  66. 2 3
      Tests/UnitTestsParallelizable/Application/Runnable/RunnableEdgeCasesTests.cs
  67. 44 40
      Tests/UnitTestsParallelizable/Application/Runnable/RunnableIntegrationTests.cs
  68. 29 2
      Tests/UnitTestsParallelizable/Application/ScreeenTests.cs
  69. 1 1
      Tests/UnitTestsParallelizable/Application/Timeouts/LogarithmicTimeoutTests.cs
  70. 35 32
      Tests/UnitTestsParallelizable/Application/Timeouts/NestedRunTimeoutTests.cs
  71. 1 2
      Tests/UnitTestsParallelizable/Application/Timeouts/SmoothAcceleratingTimeoutTests.cs
  72. 19 0
      Tests/UnitTestsParallelizable/Application/Timeouts/TimeoutTests.cs
  73. 32 4
      Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs
  74. 1 0
      Tests/UnitTestsParallelizable/Drivers/ClipRegionTests.cs
  75. 45 0
      Tests/UnitTestsParallelizable/Drivers/DriverTests.cs
  76. 98 31
      Tests/UnitTestsParallelizable/Drivers/OutputBaseTests.cs
  77. 3 3
      Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawTextAndLineCanvasTests.cs
  78. 337 74
      Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs
  79. 43 51
      Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs
  80. 1 2
      docfx/docs/View.md
  81. 110 6
      docfx/docs/migratingfromv1.md
  82. 154 22
      docfx/docs/mouse.md
  83. 1 1
      docfx/docs/navigation.md

+ 100 - 0
.github/workflows/stress-tests.yml

@@ -49,3 +49,103 @@ jobs:
             logs/
             logs/
             TestResults/StressTests
             TestResults/StressTests
 
 
+  parallel_unittests_stress:
+    name: Parallel Unit Tests Stress (3 iterations)
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ ubuntu-latest, windows-latest, macos-latest ]
+
+    timeout-minutes: 90
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Setup .NET Core
+        uses: actions/setup-dotnet@v4
+        with:
+          dotnet-version: 8.x
+          dotnet-quality: 'ga'
+
+      - name: Install dependencies
+        run: dotnet restore
+
+      - name: Build UnitTestsParallelizable
+        run: dotnet build Tests/UnitTestsParallelizable --configuration Debug --no-restore
+
+      - name: Disable Windows Defender (Windows only)
+        if: runner.os == 'Windows'
+        shell: powershell
+        run: |
+          Add-MpPreference -ExclusionPath "${{ github.workspace }}"
+          Add-MpPreference -ExclusionProcess "dotnet.exe"
+          Add-MpPreference -ExclusionProcess "testhost.exe"
+          Add-MpPreference -ExclusionProcess "VSTest.Console.exe"
+
+      - name: Set VSTEST_DUMP_PATH
+        shell: bash
+        run: echo "VSTEST_DUMP_PATH=logs/UnitTestsParallelizable/${{ runner.os }}/" >> $GITHUB_ENV
+
+      - name: Run UnitTestsParallelizable (3 iterations with varying parallelization)
+        shell: bash
+        run: |
+          # Run tests 3 times with different parallelization settings to expose concurrency issues
+          # Run 1: Default parallelization (2x) - standard test execution
+          # Run 2: Maximum parallelization (unlimited) - stress test with high concurrency
+          # Run 3: Single-threaded execution (1) - deterministic execution to expose ordering issues
+          for RUN in {1..3}; do
+            echo "============================================"
+            echo "Starting test run $RUN of 3"
+            echo "============================================"
+            
+            # Use a combination of run number and timestamp to create different execution patterns
+            SEED=$((1000 + $RUN + $(date +%s) % 1000))
+            echo "Using randomization seed: $SEED"
+            
+            # Vary the xUnit parallelization based on run number to expose race conditions
+            if [ $RUN -eq 1 ]; then
+              XUNIT_MAX_PARALLEL_THREADS="2x"
+              echo "Run $RUN: Using default parallelization (2x)"
+            elif [ $RUN -eq 2 ]; then
+              XUNIT_MAX_PARALLEL_THREADS="unlimited"
+              echo "Run $RUN: Using maximum parallelization (unlimited)"
+            else
+              XUNIT_MAX_PARALLEL_THREADS="1"
+              echo "Run $RUN: Using single-threaded execution"
+            fi
+            
+            dotnet test Tests/UnitTestsParallelizable \
+              --no-build \
+              --verbosity normal \
+              --settings Tests/UnitTestsParallelizable/runsettings.xml \
+              --diag:logs/UnitTestsParallelizable/${{ runner.os }}/run${RUN}-logs.txt \
+              --blame \
+              --blame-crash \
+              --blame-hang \
+              --blame-hang-timeout 60s \
+              --blame-crash-collect-always \
+              -- xUnit.MaxParallelThreads=${XUNIT_MAX_PARALLEL_THREADS}
+            
+            if [ $? -ne 0 ]; then
+              echo "ERROR: Test run $RUN failed!"
+              exit 1
+            fi
+            
+            echo "Test run $RUN completed successfully"
+            echo ""
+          done
+          
+          echo "============================================"
+          echo "All 3 test runs completed successfully!"
+          echo "============================================"
+
+      - name: Upload UnitTestsParallelizable Logs
+        if: always()
+        uses: actions/upload-artifact@v4
+        with:
+          name: parallel_unittests_stress-logs-${{ runner.os }}
+          path: |
+            logs/UnitTestsParallelizable/
+            TestResults/
+

+ 38 - 70
.github/workflows/unit-tests.yml

@@ -154,80 +154,48 @@ jobs:
       shell: bash
       shell: bash
       run: echo "VSTEST_DUMP_PATH=logs/UnitTestsParallelizable/${{ runner.os }}/" >> $GITHUB_ENV
       run: echo "VSTEST_DUMP_PATH=logs/UnitTestsParallelizable/${{ runner.os }}/" >> $GITHUB_ENV
 
 
-    - name: Run UnitTestsParallelizable (10 iterations with varying parallelization)
+    - name: Run UnitTestsParallelizable
       shell: bash
       shell: bash
       run: |
       run: |
-        # Run tests 3 times with different parallelization settings to expose concurrency issues
-        for RUN in {1..3}; do
-          echo "============================================"
-          echo "Starting test run $RUN of 3"
-          echo "============================================"
-          
-          # Use a combination of run number and timestamp to create different execution patterns
-          SEED=$((1000 + $RUN + $(date +%s) % 1000))
-          echo "Using randomization seed: $SEED"
-          
-          # Vary the xUnit parallelization based on run number to expose race conditions
-          # Runs 1-3: Default parallelization (2x CPU cores)
-          # Runs 4-6: Max parallelization (unlimited)
-          # Runs 7-9: Single threaded (1)
-          # Run 10: Random (1-4 threads)
-          if [ $RUN -le 3 ]; then
-            XUNIT_MAX_PARALLEL_THREADS="2x"
-            echo "Run $RUN: Using default parallelization (2x)"
-          elif [ $RUN -le 6 ]; then
-            XUNIT_MAX_PARALLEL_THREADS="unlimited"
-            echo "Run $RUN: Using maximum parallelization (unlimited)"
-          elif [ $RUN -le 9 ]; then
-            XUNIT_MAX_PARALLEL_THREADS="1"
-            echo "Run $RUN: Using single-threaded execution"
-          else
-            # Random parallelization based on seed
-            PROC_COUNT=$(( ($SEED % 4) + 1 ))
-            XUNIT_MAX_PARALLEL_THREADS="$PROC_COUNT"
-            echo "Run $RUN: Using random parallelization with $PROC_COUNT threads"
-          fi
-          
-          # Run tests with or without coverage based on OS and run number
-          if [ "${{ runner.os }}" == "Linux" ] && [ $RUN -eq 1 ]; then
-            echo "Run $RUN: Running with coverage collection"
-            dotnet test Tests/UnitTestsParallelizable \
-              --no-build \
-              --verbosity normal \
-              --collect:"XPlat Code Coverage" \
-              --settings Tests/UnitTests/runsettings.coverage.xml \
-              --diag:logs/UnitTestsParallelizable/${{ runner.os }}/run${RUN}-logs.txt \
-              --blame \
-              --blame-crash \
-              --blame-hang \
-              --blame-hang-timeout 60s \
-              --blame-crash-collect-always \
-              -- xUnit.MaxParallelThreads=${XUNIT_MAX_PARALLEL_THREADS}
-          else
-            dotnet test Tests/UnitTestsParallelizable \
-              --no-build \
-              --verbosity normal \
-              --settings Tests/UnitTestsParallelizable/runsettings.xml \
-              --diag:logs/UnitTestsParallelizable/${{ runner.os }}/run${RUN}-logs.txt \
-              --blame \
-              --blame-crash \
-              --blame-hang \
-              --blame-hang-timeout 60s \
-              --blame-crash-collect-always \
-              -- xUnit.MaxParallelThreads=${XUNIT_MAX_PARALLEL_THREADS}
-          fi
-          
-          if [ $? -ne 0 ]; then
-            echo "ERROR: Test run $RUN failed!"
-            exit 1
-          fi
-          
-          echo "Test run $RUN completed successfully"
-          echo ""
-        done
+        # Run tests once for regular workflow runs (stress tests will run multiple iterations)
+        echo "============================================"
+        echo "Starting parallel unit tests"
+        echo "============================================"
+        
+        # Use default parallelization (2x CPU cores)
+        echo "Using default parallelization (2x)"
+        
+        # Run tests with or without coverage based on OS
+        if [ "${{ runner.os }}" == "Linux" ]; then
+          echo "Running with coverage collection on Linux"
+          dotnet test Tests/UnitTestsParallelizable \
+            --no-build \
+            --verbosity normal \
+            --collect:"XPlat Code Coverage" \
+            --settings Tests/UnitTests/runsettings.coverage.xml \
+            --diag:logs/UnitTestsParallelizable/${{ runner.os }}/logs.txt \
+            --blame \
+            --blame-crash \
+            --blame-hang \
+            --blame-hang-timeout 60s \
+            --blame-crash-collect-always \
+            -- xUnit.MaxParallelThreads=2x
+        else
+          dotnet test Tests/UnitTestsParallelizable \
+            --no-build \
+            --verbosity normal \
+            --settings Tests/UnitTestsParallelizable/runsettings.xml \
+            --diag:logs/UnitTestsParallelizable/${{ runner.os }}/logs.txt \
+            --blame \
+            --blame-crash \
+            --blame-hang \
+            --blame-hang-timeout 60s \
+            --blame-crash-collect-always \
+            -- xUnit.MaxParallelThreads=2x
+        fi
         
         
         echo "============================================"
         echo "============================================"
-        echo "All 10 test runs completed successfully!"
+        echo "Parallel unit tests completed successfully!"
         echo "============================================"
         echo "============================================"
 
 
     - name: Upload UnitTestsParallelizable Logs
     - name: Upload UnitTestsParallelizable Logs

+ 32 - 26
Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs

@@ -25,7 +25,7 @@ public class CharacterMap : Scenario
 
 
     public override List<Key> GetDemoKeyStrokes ()
     public override List<Key> GetDemoKeyStrokes ()
     {
     {
-        List<Key> keys = new ();
+        List<Key> keys = [];
 
 
         for (var i = 0; i < 200; i++)
         for (var i = 0; i < 200; i++)
         {
         {
@@ -91,7 +91,7 @@ public class CharacterMap : Scenario
         };
         };
         top.Add (jumpEdit);
         top.Add (jumpEdit);
 
 
-        _charMap.SelectedCodePointChanged += (sender, args) =>
+        _charMap.SelectedCodePointChanged += (_, args) =>
                                              {
                                              {
                                                  if (Rune.IsValid (args.Value))
                                                  if (Rune.IsValid (args.Value))
                                                  {
                                                  {
@@ -134,27 +134,33 @@ public class CharacterMap : Scenario
         _categoryList.Table = CreateCategoryTable (0, isDescending);
         _categoryList.Table = CreateCategoryTable (0, isDescending);
 
 
         // if user clicks the mouse in TableView
         // if user clicks the mouse in TableView
-        _categoryList.MouseClick += (s, e) =>
-                                    {
-                                        _categoryList.ScreenToCell (e.Position, out int? clickedCol);
-
-                                        if (clickedCol != null && e.Flags.HasFlag (MouseFlags.Button1Clicked))
-                                        {
-                                            EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
-                                            string prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category;
-                                            isDescending = !isDescending;
-
-                                            _categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending);
-
-                                            table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
-
-                                            _categoryList.SelectedRow = table.Data
-                                                                             .Select ((item, index) => new { item, index })
-                                                                             .FirstOrDefault (x => x.item.Category == prevSelection)
-                                                                             ?.index
-                                                                        ?? -1;
-                                        }
-                                    };
+        _categoryList.Selecting += (_, e) =>
+                                   {
+                                       // Only handle mouse clicks
+                                       if (e.Context is not CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
+                                       {
+                                           return;
+                                       }
+
+                                       _categoryList.ScreenToCell (mouseArgs.Position, out int? clickedCol);
+
+                                       if (clickedCol != null && mouseArgs.Flags.HasFlag (MouseFlags.Button1Clicked))
+                                       {
+                                           EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
+                                           string prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category;
+                                           isDescending = !isDescending;
+
+                                           _categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending);
+
+                                           table = (EnumerableTableSource<UnicodeRange>)_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 ());
         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.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4;
 
 
-        _categoryList.SelectedCellChanged += (s, args) =>
+        _categoryList.SelectedCellChanged += (_, args) =>
                                              {
                                              {
                                                  EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
                                                  EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
                                                  _charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
                                                  _charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
@@ -219,7 +225,7 @@ public class CharacterMap : Scenario
 
 
             _errorLabel.Visible = true;
             _errorLabel.Visible = true;
 
 
-            uint result = 0;
+            uint result;
 
 
             if (jumpEdit.Text.Length == 1)
             if (jumpEdit.Text.Length == 1)
             {
             {
@@ -283,7 +289,7 @@ public class CharacterMap : Scenario
                                         ?? -1;
                                         ?? -1;
             _categoryList.EnsureSelectedCellIsVisible ();
             _categoryList.EnsureSelectedCellIsVisible ();
 
 
-            // Ensure the typed glyph is selected 
+            // Ensure the typed glyph is selected
             _charMap.SelectedCodePoint = (int)result;
             _charMap.SelectedCodePoint = (int)result;
             _charMap.SetFocus ();
             _charMap.SetFocus ();
 
 

+ 39 - 43
Examples/UICatalog/Scenarios/ContextMenus.cs

@@ -35,7 +35,7 @@ public class ContextMenus : Scenario
         Application.Run (_appWindow);
         Application.Run (_appWindow);
         _appWindow.Dispose ();
         _appWindow.Dispose ();
         _appWindow.KeyDown -= OnAppWindowOnKeyDown;
         _appWindow.KeyDown -= OnAppWindowOnKeyDown;
-        _appWindow.MouseClick -= OnAppWindowOnMouseClick;
+        _appWindow.Selecting -= OnAppWindowOnSelecting;
         _winContextMenu?.Dispose ();
         _winContextMenu?.Dispose ();
 
 
         // Shutdown - Calling Application.Shutdown is required.
         // Shutdown - Calling Application.Shutdown is required.
@@ -81,23 +81,26 @@ public class ContextMenus : Scenario
             _appWindow.Add (_tfBottomRight);
             _appWindow.Add (_tfBottomRight);
 
 
             _appWindow.KeyDown += OnAppWindowOnKeyDown;
             _appWindow.KeyDown += OnAppWindowOnKeyDown;
-            _appWindow.MouseClick += OnAppWindowOnMouseClick;
+            _appWindow.Selecting += OnAppWindowOnSelecting;
 
 
             CultureInfo originalCulture = Thread.CurrentThread.CurrentUICulture;
             CultureInfo originalCulture = Thread.CurrentThread.CurrentUICulture;
-            _appWindow.IsRunningChanged += (s, e) => {
+            _appWindow.IsRunningChanged += (_, e) => {
                                                if (!e.Value)
                                                if (!e.Value)
                                                {
                                                {
                                                    Thread.CurrentThread.CurrentUICulture = originalCulture;
                                                    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<MouseBinding> { 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",
                                        Title = "M_ore options",
                                        SubMenu = new (
                                        SubMenu = new (
                                                       [
                                                       [
-                                                          new MenuItem
+                                                          new ()
                                                           {
                                                           {
                                                               Title = "_Setup...",
                                                               Title = "_Setup...",
                                                               HelpText = "Perform setup",
                                                               HelpText = "Perform setup",
@@ -153,7 +156,7 @@ public class ContextMenus : Scenario
                                                                                   ),
                                                                                   ),
                                                               Key = Key.T.WithCtrl
                                                               Key = Key.T.WithCtrl
                                                           },
                                                           },
-                                                          new MenuItem
+                                                          new ()
                                                           {
                                                           {
                                                               Title = "_Maintenance...",
                                                               Title = "_Maintenance...",
                                                               HelpText = "Maintenance mode",
                                                               HelpText = "Maintenance mode",
@@ -194,7 +197,7 @@ public class ContextMenus : Scenario
 
 
             if (index == -1)
             if (index == -1)
             {
             {
-                // Create English because GetSupportedCutures doesn't include it
+                // Create English because GetSupportedCultures doesn't include it
                 culture.Id = "_English";
                 culture.Id = "_English";
                 culture.Title = "_English";
                 culture.Title = "_English";
                 culture.HelpText = "en-US";
                 culture.HelpText = "en-US";
@@ -240,38 +243,31 @@ public class ContextMenus : Scenario
 
 
     public override List<Key> GetDemoKeyStrokes ()
     public override List<Key> GetDemoKeyStrokes ()
     {
     {
-        List<Key> 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<Key> 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;
         return keys;
     }
     }

+ 5 - 6
Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs

@@ -73,17 +73,16 @@ public class EventLog : ListView
 
 
             if (_viewToLog is { })
             if (_viewToLog is { })
             {
             {
-                _viewToLog.Initialized += (s, args) =>
+                _viewToLog.Initialized += (s, _) =>
                                           {
                                           {
                                               var sender = s as View;
                                               var sender = s as View;
                                               Log ($"Initialized: {GetIdentifyingString (sender)}");
                                               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}"); };
             }
             }
         }
         }
     }
     }

+ 1 - 1
Examples/UICatalog/Scenarios/Keys.cs

@@ -158,7 +158,7 @@ public class Keys : Scenario
         appKeyListView.SchemeName = "Runnable";
         appKeyListView.SchemeName = "Runnable";
         win.Add (onSwallowedListView);
         win.Add (onSwallowedListView);
 
 
-        Application.Driver!.InputProcessor.AnsiSequenceSwallowed += (s, e) => { swallowedList.Add (e.Replace ("\x1b", "Esc")); };
+        Application.Driver!.GetInputProcessor ().AnsiSequenceSwallowed += (s, e) => { swallowedList.Add (e.Replace ("\x1b", "Esc")); };
 
 
         Application.KeyDown += (s, a) => KeyDownPressUp (a, "Down");
         Application.KeyDown += (s, a) => KeyDownPressUp (a, "Down");
         Application.KeyUp += (s, a) => KeyDownPressUp (a, "Up");
         Application.KeyUp += (s, a) => KeyDownPressUp (a, "Up");

+ 13 - 16
Examples/UICatalog/Scenarios/ListColumns.cs

@@ -18,17 +18,17 @@ public class ListColumns : Scenario
     private TableView? _listColView;
     private TableView? _listColView;
     private CheckBox? _alternatingColorsCheckBox;
     private CheckBox? _alternatingColorsCheckBox;
     private CheckBox? _alwaysUseNormalColorForVerticalCellLinesCheckBox;
     private CheckBox? _alwaysUseNormalColorForVerticalCellLinesCheckBox;
-    private CheckBox? _bottomlineCheckBox;
+    private CheckBox? _bottomLineCheckBox;
     private CheckBox? _cellLinesCheckBox;
     private CheckBox? _cellLinesCheckBox;
     private CheckBox? _cursorCheckBox;
     private CheckBox? _cursorCheckBox;
     private CheckBox? _expandLastColumnCheckBox;
     private CheckBox? _expandLastColumnCheckBox;
     private CheckBox? _orientVerticalCheckBox;
     private CheckBox? _orientVerticalCheckBox;
     private CheckBox? _scrollParallelCheckBox;
     private CheckBox? _scrollParallelCheckBox;
     private CheckBox? _smoothScrollingCheckBox;
     private CheckBox? _smoothScrollingCheckBox;
-    private CheckBox? _toplineCheckBox;
+    private CheckBox? _topLineCheckBox;
 
 
     /// <summary>
     /// <summary>
-    ///     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
     ///     correctly and not skipping out values when paging
     /// </summary>
     /// </summary>
     /// <param name="items"></param>
     /// <param name="items"></param>
@@ -112,25 +112,22 @@ public class ListColumns : Scenario
             Normal = new (Color.White, Color.BrightBlue)
             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);
         _listColView.KeyBindings.ReplaceCommands (Key.Space, Command.Accept);
 
 
         // Setup menu checkboxes
         // Setup menu checkboxes
-        _toplineCheckBox = new ()
+        _topLineCheckBox = new ()
         {
         {
             Title = "_TopLine",
             Title = "_TopLine",
             CheckedState = _listColView.Style.ShowHorizontalHeaderOverline ? CheckState.Checked : CheckState.UnChecked
             CheckedState = _listColView.Style.ShowHorizontalHeaderOverline ? CheckState.Checked : CheckState.UnChecked
         };
         };
-        _toplineCheckBox.CheckedStateChanged += (s, e) => ToggleTopline ();
+        _topLineCheckBox.CheckedStateChanged += (s, e) => ToggleTopline ();
 
 
-        _bottomlineCheckBox = new ()
+        _bottomLineCheckBox = new ()
         {
         {
             Title = "_BottomLine",
             Title = "_BottomLine",
             CheckedState = _listColView.Style.ShowHorizontalBottomline ? CheckState.Checked : CheckState.UnChecked
             CheckedState = _listColView.Style.ShowHorizontalBottomline ? CheckState.Checked : CheckState.UnChecked
         };
         };
-        _bottomlineCheckBox.CheckedStateChanged += (s, e) => ToggleBottomline ();
+        _bottomLineCheckBox.CheckedStateChanged += (s, e) => ToggleBottomline ();
 
 
         _cellLinesCheckBox = new ()
         _cellLinesCheckBox = new ()
         {
         {
@@ -221,11 +218,11 @@ public class ListColumns : Scenario
                                    [
                                    [
                                        new MenuItem
                                        new MenuItem
                                        {
                                        {
-                                           CommandView = _toplineCheckBox
+                                           CommandView = _topLineCheckBox
                                        },
                                        },
                                        new MenuItem
                                        new MenuItem
                                        {
                                        {
-                                           CommandView = _bottomlineCheckBox
+                                           CommandView = _bottomLineCheckBox
                                        },
                                        },
                                        new MenuItem
                                        new MenuItem
                                        {
                                        {
@@ -422,12 +419,12 @@ public class ListColumns : Scenario
 
 
     private void ToggleBottomline ()
     private void ToggleBottomline ()
     {
     {
-        if (_listColView is null || _bottomlineCheckBox is null)
+        if (_listColView is null || _bottomLineCheckBox is null)
         {
         {
             return;
             return;
         }
         }
 
 
-        _listColView.Style.ShowHorizontalBottomline = _bottomlineCheckBox.CheckedState == CheckState.Checked;
+        _listColView.Style.ShowHorizontalBottomline = _bottomLineCheckBox.CheckedState == CheckState.Checked;
         _listColView.Update ();
         _listColView.Update ();
     }
     }
 
 
@@ -490,12 +487,12 @@ public class ListColumns : Scenario
 
 
     private void ToggleTopline ()
     private void ToggleTopline ()
     {
     {
-        if (_listColView is null || _toplineCheckBox is null)
+        if (_listColView is null || _topLineCheckBox is null)
         {
         {
             return;
             return;
         }
         }
 
 
-        _listColView.Style.ShowHorizontalHeaderOverline = _toplineCheckBox.CheckedState == CheckState.Checked;
+        _listColView.Style.ShowHorizontalHeaderOverline = _topLineCheckBox.CheckedState == CheckState.Checked;
         _listColView.Update ();
         _listColView.Update ();
     }
     }
 
 

+ 9 - 15
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.CheckedState = demo.HighlightStates.HasFlag (MouseState.Pressed) ? CheckState.Checked : CheckState.UnChecked;
 
 
-        cbHighlightOnPressed.CheckedStateChanging += (s, e) =>
+        cbHighlightOnPressed.CheckedStateChanging += (_, e) =>
                                                      {
                                                      {
                                                          if (e.Result == CheckState.Checked)
                                                          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.CheckedState = demo.HighlightStates.HasFlag (MouseState.PressedOutside) ? CheckState.Checked : CheckState.UnChecked;
 
 
-        cbHighlightOnPressedOutside.CheckedStateChanging += (s, e) =>
+        cbHighlightOnPressedOutside.CheckedStateChanging += (_, e) =>
                                                             {
                                                             {
                                                                 if (e.Result == CheckState.Checked)
                                                                 if (e.Result == CheckState.Checked)
                                                                 {
                                                                 {
@@ -217,7 +217,7 @@ public class Mouse : Scenario
                                                                 }
                                                                 }
                                                             };
                                                             };
 
 
-        cbWantContinuousPresses.CheckedStateChanging += (s, e) =>
+        cbWantContinuousPresses.CheckedStateChanging += (_, _) =>
                                                         {
                                                         {
                                                             demo.WantContinuousButtonPressed = !demo.WantContinuousButtonPressed;
                                                             demo.WantContinuousButtonPressed = !demo.WantContinuousButtonPressed;
 
 
@@ -252,7 +252,7 @@ public class Mouse : Scenario
         };
         };
         win.Add (label, appLog);
         win.Add (label, appLog);
 
 
-        Application.MouseEvent += (sender, a) =>
+        Application.MouseEvent += (_, a) =>
                                   {
                                   {
                                       int i = filterSlider.Options.FindIndex (o => o.Data == a.Flags);
                                       int i = filterSlider.Options.FindIndex (o => o.Data == a.Flags);
 
 
@@ -270,7 +270,7 @@ public class Mouse : Scenario
             X = Pos.Right (appLog) + 1,
             X = Pos.Right (appLog) + 1,
             Y = Pos.Top (label)
             Y = Pos.Top (label)
         };
         };
-        ObservableCollection<string> winLogList = new ();
+        ObservableCollection<string> winLogList = [];
 
 
         var winLog = new ListView
         var winLog = new ListView
         {
         {
@@ -283,7 +283,7 @@ public class Mouse : Scenario
         };
         };
         win.Add (label, winLog);
         win.Add (label, winLog);
 
 
-        clearButton.Accepting += (s, e) =>
+        clearButton.Accepting += (_, _) =>
                                  {
                                  {
                                      appLogList.Clear ();
                                      appLogList.Clear ();
                                      appLog.SetSource (appLogList);
                                      appLog.SetSource (appLogList);
@@ -291,7 +291,7 @@ public class Mouse : Scenario
                                      winLog.SetSource (winLogList);
                                      winLog.SetSource (winLogList);
                                  };
                                  };
 
 
-        win.MouseEvent += (sender, a) =>
+        win.MouseEvent += (_, a) =>
                           {
                           {
                               int i = filterSlider.Options.FindIndex (o => o.Data == a.Flags);
                               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);
         Application.Run (win);
         win.Dispose ();
         win.Dispose ();
         Application.Shutdown ();
         Application.Shutdown ();
@@ -322,8 +316,8 @@ public class Mouse : Scenario
 
 
             Initialized += OnInitialized;
             Initialized += OnInitialized;
 
 
-            MouseLeave += (s, e) => { Text = "Leave"; };
-            MouseEnter += (s, e) => { Text = "Enter"; };
+            MouseLeave += (_, _) => { Text = "Leave"; };
+            MouseEnter += (_, _) => { Text = "Enter"; };
 
 
             return;
             return;
 
 

+ 102 - 107
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;
 namespace UICatalog.Scenarios;
 
 
@@ -18,7 +16,7 @@ public class ScrollBarDemo : Scenario
             Arrangement = ViewArrangement.Fixed
             Arrangement = ViewArrangement.Fixed
         };
         };
 
 
-        var demoFrame = new FrameView ()
+        var demoFrame = new FrameView
         {
         {
             Title = "Demo View",
             Title = "Demo View",
             X = 0,
             X = 0,
@@ -36,7 +34,7 @@ public class ScrollBarDemo : Scenario
             X = Pos.AnchorEnd () - 5,
             X = Pos.AnchorEnd () - 5,
             AutoShow = false,
             AutoShow = false,
             ScrollableContentSize = 100,
             ScrollableContentSize = 100,
-            Height = Dim.Fill()
+            Height = Dim.Fill ()
         };
         };
         demoFrame.Add (scrollBar);
         demoFrame.Add (scrollBar);
 
 
@@ -45,7 +43,7 @@ public class ScrollBarDemo : Scenario
             X = Pos.AnchorEnd (),
             X = Pos.AnchorEnd (),
             Width = 5,
             Width = 5,
             Height = Dim.Fill (),
             Height = Dim.Fill (),
-            SchemeName = "Error",
+            SchemeName = "Error"
         };
         };
 
 
         demoFrame.Add (controlledList);
         demoFrame.Add (controlledList);
@@ -55,10 +53,9 @@ public class ScrollBarDemo : Scenario
 
 
         int GetMaxLabelWidth (int groupId)
         int GetMaxLabelWidth (int groupId)
         {
         {
-            return demoFrame.SubViews.Max (
-                                           v =>
+            return demoFrame.SubViews.Max (v =>
                                            {
                                            {
-                                               if (v.Y.Has<PosAlign> (out var pos) && pos.GroupId == groupId)
+                                               if (v.Y.Has<PosAlign> (out PosAlign pos) && pos.GroupId == groupId)
                                                {
                                                {
                                                    return v.Text.GetColumns ();
                                                    return v.Text.GetColumns ();
                                                }
                                                }
@@ -71,7 +68,7 @@ public class ScrollBarDemo : Scenario
         {
         {
             Text = "_Width/Height:",
             Text = "_Width/Height:",
             TextAlignment = Alignment.End,
             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))
             Width = Dim.Func (_ => GetMaxLabelWidth (1))
         };
         };
         demoFrame.Add (lblWidthHeight);
         demoFrame.Add (lblWidthHeight);
@@ -80,17 +77,17 @@ public class ScrollBarDemo : Scenario
         {
         {
             Value = 1,
             Value = 1,
             X = Pos.Right (lblWidthHeight) + 1,
             X = Pos.Right (lblWidthHeight) + 1,
-            Y = Pos.Top (lblWidthHeight),
+            Y = Pos.Top (lblWidthHeight)
         };
         };
         demoFrame.Add (scrollWidthHeight);
         demoFrame.Add (scrollWidthHeight);
 
 
         scrollWidthHeight.ValueChanging += (s, e) =>
         scrollWidthHeight.ValueChanging += (s, e) =>
                                            {
                                            {
                                                if (e.NewValue < 1
                                                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
                                                    // TODO: This must be handled in the ScrollSlider if Width and Height being virtual
                                                    e.Cancel = true;
                                                    e.Cancel = true;
@@ -108,7 +105,6 @@ public class ScrollBarDemo : Scenario
                                                }
                                                }
                                            };
                                            };
 
 
-
         var lblOrientationLabel = new Label
         var lblOrientationLabel = new Label
         {
         {
             Text = "_Orientation:",
             Text = "_Orientation:",
@@ -128,28 +124,26 @@ public class ScrollBarDemo : Scenario
         demoFrame.Add (osOrientation);
         demoFrame.Add (osOrientation);
 
 
         osOrientation.ValueChanged += (s, e) =>
         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
         var lblSize = new Label
         {
         {
@@ -169,20 +163,24 @@ public class ScrollBarDemo : Scenario
         demoFrame.Add (scrollContentSize);
         demoFrame.Add (scrollContentSize);
 
 
         scrollContentSize.ValueChanging += (s, e) =>
         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<string> (Enumerable.Range (0, scrollBar.ScrollableContentSize).Select (n => $"{n:00000}")));
-                                        }
-                                    };
+                                               if (scrollBar.ScrollableContentSize != e.NewValue)
+                                               {
+                                                   scrollBar.ScrollableContentSize = e.NewValue;
+
+                                                   controlledList.SetSource (
+                                                                             new ObservableCollection<string> (
+                                                                              Enumerable.Range (0, scrollBar.ScrollableContentSize)
+                                                                                        .Select (n => $"{n:00000}")));
+                                               }
+                                           };
 
 
         var lblVisibleContentSize = new Label
         var lblVisibleContentSize = new Label
         {
         {
@@ -202,20 +200,19 @@ public class ScrollBarDemo : Scenario
         demoFrame.Add (visibleContentSize);
         demoFrame.Add (visibleContentSize);
 
 
         visibleContentSize.ValueChanging += (s, e) =>
         visibleContentSize.ValueChanging += (s, e) =>
-                                           {
-                                               if (e.NewValue < 0)
-                                               {
-                                                   e.Cancel = true;
-
-                                                   return;
-                                               }
+                                            {
+                                                if (e.NewValue < 0)
+                                                {
+                                                    e.Cancel = true;
 
 
-                                               if (scrollBar.VisibleContentSize != e.NewValue)
-                                               {
-                                                   scrollBar.VisibleContentSize = e.NewValue;
-                                               }
-                                           };
+                                                    return;
+                                                }
 
 
+                                                if (scrollBar.VisibleContentSize != e.NewValue)
+                                                {
+                                                    scrollBar.VisibleContentSize = e.NewValue;
+                                                }
+                                            };
 
 
         var lblPosition = new Label
         var lblPosition = new Label
         {
         {
@@ -223,7 +220,6 @@ public class ScrollBarDemo : Scenario
             TextAlignment = Alignment.End,
             TextAlignment = Alignment.End,
             Y = Pos.Align (Alignment.Start, groupId: 1),
             Y = Pos.Align (Alignment.Start, groupId: 1),
             Width = Dim.Func (_ => GetMaxLabelWidth (1))
             Width = Dim.Func (_ => GetMaxLabelWidth (1))
-
         };
         };
         demoFrame.Add (lblPosition);
         demoFrame.Add (lblPosition);
 
 
@@ -236,24 +232,24 @@ public class ScrollBarDemo : Scenario
         demoFrame.Add (scrollPosition);
         demoFrame.Add (scrollPosition);
 
 
         scrollPosition.ValueChanging += (s, e) =>
         scrollPosition.ValueChanging += (s, e) =>
-                                               {
-                                                   if (e.NewValue < 0)
-                                                   {
-                                                       e.Cancel = true;
-
-                                                       return;
-                                                   }
-
-                                                   if (scrollBar.Position != e.NewValue)
-                                                   {
-                                                       scrollBar.Position = e.NewValue;
-                                                   }
-
-                                                   if (scrollBar.Position != e.NewValue)
-                                                   {
-                                                       e.Cancel = true;
-                                                   }
-                                               };
+                                        {
+                                            if (e.NewValue < 0)
+                                            {
+                                                e.Cancel = true;
+
+                                                return;
+                                            }
+
+                                            if (scrollBar.Position != e.NewValue)
+                                            {
+                                                scrollBar.Position = e.NewValue;
+                                            }
+
+                                            if (scrollBar.Position != e.NewValue)
+                                            {
+                                                e.Cancel = true;
+                                            }
+                                        };
 
 
         var lblOptions = new Label
         var lblOptions = new Label
         {
         {
@@ -263,11 +259,12 @@ public class ScrollBarDemo : Scenario
             Width = Dim.Func (_ => GetMaxLabelWidth (1))
             Width = Dim.Func (_ => GetMaxLabelWidth (1))
         };
         };
         demoFrame.Add (lblOptions);
         demoFrame.Add (lblOptions);
+
         var autoShow = new CheckBox
         var autoShow = new CheckBox
         {
         {
             Y = Pos.Top (lblOptions),
             Y = Pos.Top (lblOptions),
             X = Pos.Right (lblOptions) + 1,
             X = Pos.Right (lblOptions) + 1,
-            Text = $"_AutoShow",
+            Text = "_AutoShow",
             CheckedState = scrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked
             CheckedState = scrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked
         };
         };
         autoShow.CheckedStateChanging += (s, e) => scrollBar.AutoShow = e.Result == CheckState.Checked;
         autoShow.CheckedStateChanging += (s, e) => scrollBar.AutoShow = e.Result == CheckState.Checked;
@@ -296,9 +293,9 @@ public class ScrollBarDemo : Scenario
             TextAlignment = Alignment.End,
             TextAlignment = Alignment.End,
             Y = Pos.Align (Alignment.Start, groupId: 1),
             Y = Pos.Align (Alignment.Start, groupId: 1),
             Width = Dim.Func (_ => GetMaxLabelWidth (1))
             Width = Dim.Func (_ => GetMaxLabelWidth (1))
-
         };
         };
         demoFrame.Add (lblScrolled);
         demoFrame.Add (lblScrolled);
+
         Label scrolled = new ()
         Label scrolled = new ()
         {
         {
             X = Pos.Right (lblScrolled) + 1,
             X = Pos.Right (lblScrolled) + 1,
@@ -347,43 +344,41 @@ public class ScrollBarDemo : Scenario
         void AppOnInitialized (object sender, EventArgs e)
         void AppOnInitialized (object sender, EventArgs e)
         {
         {
             scrollBar.ScrollableContentSizeChanged += (s, 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) =>
             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) =>
             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) =>
             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) =>
             controlledList.ViewportChanged += (s, e) =>
                                               {
                                               {
                                                   eventLog.Log ($"ViewportChanged: {e.NewViewport}");
                                                   eventLog.Log ($"ViewportChanged: {e.NewViewport}");
                                                   scrollBar.Position = e.NewViewport.Y;
                                                   scrollBar.Position = e.NewViewport.Y;
                                               };
                                               };
-
         }
         }
 
 
         Application.Run (app);
         Application.Run (app);

+ 29 - 23
Examples/UICatalog/Scenarios/TableEditor.cs

@@ -610,29 +610,35 @@ public class TableEditor : Scenario
         };
         };
 
 
         // if user clicks the mouse in TableView
         // if user clicks the mouse in TableView
-        _tableView!.MouseClick += (s, e) =>
-                                  {
-                                      if (_currentTable == null)
-                                      {
-                                          return;
-                                      }
-
-                                      _tableView!.ScreenToCell (e.Position, out int? clickedCol);
-
-                                      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!.Selecting += (s, e) =>
+                                 {
+                                     if (_currentTable == null)
+                                     {
+                                         return;
+                                     }
+
+                                     // Only handle mouse clicks
+                                     if (e.Context is not CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
+                                     {
+                                         return;
+                                     }
+
+                                     _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);
         _tableView!.KeyBindings.ReplaceCommands (Key.Space, Command.Accept);
 
 

+ 94 - 105
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;
 namespace UICatalog.Scenarios;
 
 
@@ -9,12 +6,11 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Text and Formatting")]
 [ScenarioCategory ("Text and Formatting")]
 public class TextAlignmentAndDirection : Scenario
 public class TextAlignmentAndDirection : Scenario
 {
 {
-
     internal class AlignmentAndDirectionView : View
     internal class AlignmentAndDirectionView : View
     {
     {
         public AlignmentAndDirectionView ()
         public AlignmentAndDirectionView ()
         {
         {
-            ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent;
+            ViewportSettings = ViewportSettingsFlags.Transparent;
             BorderStyle = LineStyle.Dotted;
             BorderStyle = LineStyle.Dotted;
         }
         }
     }
     }
@@ -30,15 +26,15 @@ public class TextAlignmentAndDirection : Scenario
 
 
         var txt = $"Hello World{Environment.NewLine}HELLO WORLD{Environment.NewLine}世界 您好";
         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<View> singleLineLabels = new (); // single line
-        List<View> multiLineLabels = new (); // multi line
+        List<View> singleLineLabels = []; // single line
+        List<View> multiLineLabels = []; // multi line
 
 
-        // Horizontal Single-Line 
+        // Horizontal Single-Line
 
 
-        var labelHL = new Label
+        Label labelHL = new ()
         {
         {
             X = 0,
             X = 0,
             Y = 0,
             Y = 0,
@@ -46,10 +42,10 @@ public class TextAlignmentAndDirection : Scenario
             Height = 1,
             Height = 1,
             TextAlignment = Alignment.End,
             TextAlignment = Alignment.End,
             SchemeName = "Dialog",
             SchemeName = "Dialog",
-            Text = "Start",
+            Text = "Start"
         };
         };
 
 
-        var labelHC = new Label
+        Label labelHC = new ()
         {
         {
             X = 0,
             X = 0,
             Y = 1,
             Y = 1,
@@ -60,7 +56,7 @@ public class TextAlignmentAndDirection : Scenario
             Text = "Center"
             Text = "Center"
         };
         };
 
 
-        var labelHR = new Label
+        Label labelHR = new ()
         {
         {
             X = 0,
             X = 0,
             Y = 2,
             Y = 2,
@@ -71,7 +67,7 @@ public class TextAlignmentAndDirection : Scenario
             Text = "End"
             Text = "End"
         };
         };
 
 
-        var labelHJ = new Label
+        Label labelHJ = new ()
         {
         {
             X = 0,
             X = 0,
             Y = 3,
             Y = 3,
@@ -82,7 +78,7 @@ public class TextAlignmentAndDirection : Scenario
             Text = "Fill"
             Text = "Fill"
         };
         };
 
 
-        var txtLabelHL = new View
+        View txtLabelHL = new ()
         {
         {
             X = Pos.Right (labelHL) + 1,
             X = Pos.Right (labelHL) + 1,
             Y = Pos.Y (labelHL),
             Y = Pos.Y (labelHL),
@@ -91,10 +87,10 @@ public class TextAlignmentAndDirection : Scenario
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
             TextAlignment = Alignment.Start,
             TextAlignment = Alignment.Start,
             Text = txt,
             Text = txt,
-            ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent
+            ViewportSettings = ViewportSettingsFlags.Transparent
         };
         };
 
 
-        var txtLabelHC = new View
+        View txtLabelHC = new ()
         {
         {
             X = Pos.Right (labelHC) + 1,
             X = Pos.Right (labelHC) + 1,
             Y = Pos.Y (labelHC),
             Y = Pos.Y (labelHC),
@@ -103,10 +99,10 @@ public class TextAlignmentAndDirection : Scenario
             SchemeName = "TextAlignmentAndDirection2",
             SchemeName = "TextAlignmentAndDirection2",
             TextAlignment = Alignment.Center,
             TextAlignment = Alignment.Center,
             Text = txt,
             Text = txt,
-            ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent
+            ViewportSettings = ViewportSettingsFlags.Transparent
         };
         };
 
 
-        var txtLabelHR = new View
+        View txtLabelHR = new ()
         {
         {
             X = Pos.Right (labelHR) + 1,
             X = Pos.Right (labelHR) + 1,
             Y = Pos.Y (labelHR),
             Y = Pos.Y (labelHR),
@@ -115,10 +111,10 @@ public class TextAlignmentAndDirection : Scenario
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
             TextAlignment = Alignment.End,
             TextAlignment = Alignment.End,
             Text = txt,
             Text = txt,
-            ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent
+            ViewportSettings = ViewportSettingsFlags.Transparent
         };
         };
 
 
-        var txtLabelHJ = new View
+        View txtLabelHJ = new ()
         {
         {
             X = Pos.Right (labelHJ) + 1,
             X = Pos.Right (labelHJ) + 1,
             Y = Pos.Y (labelHJ),
             Y = Pos.Y (labelHJ),
@@ -127,7 +123,7 @@ public class TextAlignmentAndDirection : Scenario
             SchemeName = "TextAlignmentAndDirection2",
             SchemeName = "TextAlignmentAndDirection2",
             TextAlignment = Alignment.Fill,
             TextAlignment = Alignment.Fill,
             Text = txt,
             Text = txt,
-            ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent
+            ViewportSettings = ViewportSettingsFlags.Transparent
         };
         };
 
 
         singleLineLabels.Add (txtLabelHL);
         singleLineLabels.Add (txtLabelHL);
@@ -146,7 +142,7 @@ public class TextAlignmentAndDirection : Scenario
 
 
         // Vertical Single-Line
         // Vertical Single-Line
 
 
-        var labelVT = new Label
+        Label labelVT = new ()
         {
         {
             X = Pos.AnchorEnd () - 6,
             X = Pos.AnchorEnd () - 6,
             Y = 0,
             Y = 0,
@@ -159,7 +155,7 @@ public class TextAlignmentAndDirection : Scenario
         };
         };
         labelVT.TextFormatter.WordWrap = false;
         labelVT.TextFormatter.WordWrap = false;
 
 
-        var labelVM = new Label
+        Label labelVM = new ()
         {
         {
             X = Pos.AnchorEnd () - 4,
             X = Pos.AnchorEnd () - 4,
             Y = 0,
             Y = 0,
@@ -172,7 +168,7 @@ public class TextAlignmentAndDirection : Scenario
         };
         };
         labelVM.TextFormatter.WordWrap = false;
         labelVM.TextFormatter.WordWrap = false;
 
 
-        var labelVB = new Label
+        Label labelVB = new ()
         {
         {
             X = Pos.AnchorEnd () - 2,
             X = Pos.AnchorEnd () - 2,
             Y = 0,
             Y = 0,
@@ -185,7 +181,7 @@ public class TextAlignmentAndDirection : Scenario
         };
         };
         labelVB.TextFormatter.WordWrap = false;
         labelVB.TextFormatter.WordWrap = false;
 
 
-        var labelVJ = new Label
+        Label labelVJ = new ()
         {
         {
             X = Pos.AnchorEnd (),
             X = Pos.AnchorEnd (),
             Y = 0,
             Y = 0,
@@ -198,7 +194,7 @@ public class TextAlignmentAndDirection : Scenario
         };
         };
         labelVJ.TextFormatter.WordWrap = false;
         labelVJ.TextFormatter.WordWrap = false;
 
 
-        var txtLabelVT = new View
+        View txtLabelVT = new ()
         {
         {
             X = Pos.X (labelVT),
             X = Pos.X (labelVT),
             Y = Pos.Bottom (labelVT) + 1,
             Y = Pos.Bottom (labelVT) + 1,
@@ -208,11 +204,11 @@ public class TextAlignmentAndDirection : Scenario
             TextDirection = TextDirection.TopBottom_LeftRight,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = Alignment.Start,
             VerticalTextAlignment = Alignment.Start,
             Text = txt,
             Text = txt,
-            ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent
+            ViewportSettings = ViewportSettingsFlags.Transparent
         };
         };
         txtLabelVT.TextFormatter.WordWrap = false;
         txtLabelVT.TextFormatter.WordWrap = false;
 
 
-        var txtLabelVM = new View
+        View txtLabelVM = new ()
         {
         {
             X = Pos.X (labelVM),
             X = Pos.X (labelVM),
             Y = Pos.Bottom (labelVM) + 1,
             Y = Pos.Bottom (labelVM) + 1,
@@ -222,11 +218,11 @@ public class TextAlignmentAndDirection : Scenario
             TextDirection = TextDirection.TopBottom_LeftRight,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = Alignment.Center,
             VerticalTextAlignment = Alignment.Center,
             Text = txt,
             Text = txt,
-            ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent
+            ViewportSettings = ViewportSettingsFlags.Transparent
         };
         };
         txtLabelVM.TextFormatter.WordWrap = false;
         txtLabelVM.TextFormatter.WordWrap = false;
 
 
-        var txtLabelVB = new View
+        View txtLabelVB = new ()
         {
         {
             X = Pos.X (labelVB),
             X = Pos.X (labelVB),
             Y = Pos.Bottom (labelVB) + 1,
             Y = Pos.Bottom (labelVB) + 1,
@@ -236,11 +232,11 @@ public class TextAlignmentAndDirection : Scenario
             TextDirection = TextDirection.TopBottom_LeftRight,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = Alignment.End,
             VerticalTextAlignment = Alignment.End,
             Text = txt,
             Text = txt,
-            ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent
+            ViewportSettings = ViewportSettingsFlags.Transparent
         };
         };
         txtLabelVB.TextFormatter.WordWrap = false;
         txtLabelVB.TextFormatter.WordWrap = false;
 
 
-        var txtLabelVJ = new View
+        View txtLabelVJ = new ()
         {
         {
             X = Pos.X (labelVJ),
             X = Pos.X (labelVJ),
             Y = Pos.Bottom (labelVJ) + 1,
             Y = Pos.Bottom (labelVJ) + 1,
@@ -250,7 +246,7 @@ public class TextAlignmentAndDirection : Scenario
             TextDirection = TextDirection.TopBottom_LeftRight,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = Alignment.Fill,
             VerticalTextAlignment = Alignment.Fill,
             Text = txt,
             Text = txt,
-            ViewportSettings = Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent
+            ViewportSettings = ViewportSettingsFlags.Transparent
         };
         };
         txtLabelVJ.TextFormatter.WordWrap = false;
         txtLabelVJ.TextFormatter.WordWrap = false;
 
 
@@ -270,7 +266,7 @@ public class TextAlignmentAndDirection : Scenario
 
 
         // Multi-Line
         // Multi-Line
 
 
-        var container = new View
+        View container = new ()
         {
         {
             X = 0,
             X = 0,
             Y = Pos.Bottom (txtLabelHJ),
             Y = Pos.Bottom (txtLabelHJ),
@@ -280,7 +276,7 @@ public class TextAlignmentAndDirection : Scenario
             //SchemeName = "TextAlignmentAndDirection2"
             //SchemeName = "TextAlignmentAndDirection2"
         };
         };
 
 
-        var txtLabelTL = new AlignmentAndDirectionView
+        AlignmentAndDirectionView txtLabelTL = new ()
         {
         {
             X = 0,
             X = 0,
             Y = 1,
             Y = 1,
@@ -289,11 +285,11 @@ public class TextAlignmentAndDirection : Scenario
             TextAlignment = Alignment.Start,
             TextAlignment = Alignment.Start,
             VerticalTextAlignment = Alignment.Start,
             VerticalTextAlignment = Alignment.Start,
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
-            Text = txt,
+            Text = txt
         };
         };
         txtLabelTL.TextFormatter.MultiLine = true;
         txtLabelTL.TextFormatter.MultiLine = true;
 
 
-        var txtLabelTC = new AlignmentAndDirectionView
+        AlignmentAndDirectionView txtLabelTC = new ()
         {
         {
             X = Pos.Right (txtLabelTL),
             X = Pos.Right (txtLabelTL),
             Y = 1,
             Y = 1,
@@ -302,11 +298,11 @@ public class TextAlignmentAndDirection : Scenario
             TextAlignment = Alignment.Center,
             TextAlignment = Alignment.Center,
             VerticalTextAlignment = Alignment.Start,
             VerticalTextAlignment = Alignment.Start,
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
-            Text = txt,
+            Text = txt
         };
         };
         txtLabelTC.TextFormatter.MultiLine = true;
         txtLabelTC.TextFormatter.MultiLine = true;
 
 
-        var txtLabelTR = new AlignmentAndDirectionView
+        AlignmentAndDirectionView txtLabelTR = new ()
         {
         {
             X = Pos.Right (txtLabelTC),
             X = Pos.Right (txtLabelTC),
             Y = 1,
             Y = 1,
@@ -315,11 +311,11 @@ public class TextAlignmentAndDirection : Scenario
             TextAlignment = Alignment.End,
             TextAlignment = Alignment.End,
             VerticalTextAlignment = Alignment.Start,
             VerticalTextAlignment = Alignment.Start,
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
-            Text = txt,
+            Text = txt
         };
         };
         txtLabelTR.TextFormatter.MultiLine = true;
         txtLabelTR.TextFormatter.MultiLine = true;
 
 
-        var txtLabelML = new AlignmentAndDirectionView
+        AlignmentAndDirectionView txtLabelML = new ()
         {
         {
             X = Pos.X (txtLabelTL),
             X = Pos.X (txtLabelTL),
             Y = Pos.Bottom (txtLabelTL),
             Y = Pos.Bottom (txtLabelTL),
@@ -328,11 +324,11 @@ public class TextAlignmentAndDirection : Scenario
             TextAlignment = Alignment.Start,
             TextAlignment = Alignment.Start,
             VerticalTextAlignment = Alignment.Center,
             VerticalTextAlignment = Alignment.Center,
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
-            Text = txt,
+            Text = txt
         };
         };
         txtLabelML.TextFormatter.MultiLine = true;
         txtLabelML.TextFormatter.MultiLine = true;
 
 
-        var txtLabelMC = new AlignmentAndDirectionView
+        AlignmentAndDirectionView txtLabelMC = new ()
         {
         {
             X = Pos.X (txtLabelTC),
             X = Pos.X (txtLabelTC),
             Y = Pos.Bottom (txtLabelTC),
             Y = Pos.Bottom (txtLabelTC),
@@ -341,11 +337,11 @@ public class TextAlignmentAndDirection : Scenario
             TextAlignment = Alignment.Center,
             TextAlignment = Alignment.Center,
             VerticalTextAlignment = Alignment.Center,
             VerticalTextAlignment = Alignment.Center,
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
-            Text = txt,
+            Text = txt
         };
         };
         txtLabelMC.TextFormatter.MultiLine = true;
         txtLabelMC.TextFormatter.MultiLine = true;
 
 
-        var txtLabelMR = new AlignmentAndDirectionView
+        AlignmentAndDirectionView txtLabelMR = new ()
         {
         {
             X = Pos.X (txtLabelTR),
             X = Pos.X (txtLabelTR),
             Y = Pos.Bottom (txtLabelTR),
             Y = Pos.Bottom (txtLabelTR),
@@ -354,11 +350,11 @@ public class TextAlignmentAndDirection : Scenario
             TextAlignment = Alignment.End,
             TextAlignment = Alignment.End,
             VerticalTextAlignment = Alignment.Center,
             VerticalTextAlignment = Alignment.Center,
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
-            Text = txt,
+            Text = txt
         };
         };
         txtLabelMR.TextFormatter.MultiLine = true;
         txtLabelMR.TextFormatter.MultiLine = true;
 
 
-        var txtLabelBL = new AlignmentAndDirectionView
+        AlignmentAndDirectionView txtLabelBL = new ()
         {
         {
             X = Pos.X (txtLabelML),
             X = Pos.X (txtLabelML),
             Y = Pos.Bottom (txtLabelML),
             Y = Pos.Bottom (txtLabelML),
@@ -367,11 +363,11 @@ public class TextAlignmentAndDirection : Scenario
             TextAlignment = Alignment.Start,
             TextAlignment = Alignment.Start,
             VerticalTextAlignment = Alignment.End,
             VerticalTextAlignment = Alignment.End,
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
-            Text = txt,
+            Text = txt
         };
         };
         txtLabelBL.TextFormatter.MultiLine = true;
         txtLabelBL.TextFormatter.MultiLine = true;
 
 
-        var txtLabelBC = new AlignmentAndDirectionView
+        AlignmentAndDirectionView txtLabelBC = new ()
         {
         {
             X = Pos.X (txtLabelMC),
             X = Pos.X (txtLabelMC),
             Y = Pos.Bottom (txtLabelMC),
             Y = Pos.Bottom (txtLabelMC),
@@ -380,11 +376,11 @@ public class TextAlignmentAndDirection : Scenario
             TextAlignment = Alignment.Center,
             TextAlignment = Alignment.Center,
             VerticalTextAlignment = Alignment.End,
             VerticalTextAlignment = Alignment.End,
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
-            Text = txt,
+            Text = txt
         };
         };
         txtLabelBC.TextFormatter.MultiLine = true;
         txtLabelBC.TextFormatter.MultiLine = true;
 
 
-        var txtLabelBR = new AlignmentAndDirectionView
+        AlignmentAndDirectionView txtLabelBR = new ()
         {
         {
             X = Pos.X (txtLabelMR),
             X = Pos.X (txtLabelMR),
             Y = Pos.Bottom (txtLabelMR),
             Y = Pos.Bottom (txtLabelMR),
@@ -393,7 +389,7 @@ public class TextAlignmentAndDirection : Scenario
             TextAlignment = Alignment.End,
             TextAlignment = Alignment.End,
             VerticalTextAlignment = Alignment.End,
             VerticalTextAlignment = Alignment.End,
             SchemeName = "TextAlignmentAndDirection1",
             SchemeName = "TextAlignmentAndDirection1",
-            Text = txt,
+            Text = txt
         };
         };
         txtLabelBR.TextFormatter.MultiLine = true;
         txtLabelBR.TextFormatter.MultiLine = true;
 
 
@@ -429,7 +425,7 @@ public class TextAlignmentAndDirection : Scenario
 
 
         // Edit Text
         // Edit Text
 
 
-        var label = new Label
+        Label label = new ()
         {
         {
             X = 1,
             X = 1,
             Y = Pos.Bottom (container) + 1,
             Y = Pos.Bottom (container) + 1,
@@ -438,7 +434,7 @@ public class TextAlignmentAndDirection : Scenario
             Text = "Edit Text:"
             Text = "Edit Text:"
         };
         };
 
 
-        var editText = new TextView
+        TextView editText = new ()
         {
         {
             X = Pos.Right (label) + 1,
             X = Pos.Right (label) + 1,
             Y = Pos.Top (label),
             Y = Pos.Top (label),
@@ -447,19 +443,6 @@ public class TextAlignmentAndDirection : Scenario
             Text = txt
             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) =>
         app.KeyUp += (s, m) =>
                      {
                      {
                          foreach (View v in singleLineLabels)
                          foreach (View v in singleLineLabels)
@@ -479,7 +462,7 @@ public class TextAlignmentAndDirection : Scenario
 
 
         // JUSTIFY CHECKBOX
         // JUSTIFY CHECKBOX
 
 
-        var justifyCheckbox = new CheckBox
+        CheckBox justifyCheckbox = new ()
         {
         {
             X = Pos.Right (container) + 1,
             X = Pos.Right (container) + 1,
             Y = Pos.Y (container) + 1,
             Y = Pos.Y (container) + 1,
@@ -492,7 +475,7 @@ public class TextAlignmentAndDirection : Scenario
 
 
         // JUSTIFY OPTIONS
         // JUSTIFY OPTIONS
 
 
-        var justifyOptions = new OptionSelector
+        OptionSelector justifyOptions = new ()
         {
         {
             X = Pos.Left (justifyCheckbox) + 1,
             X = Pos.Left (justifyCheckbox) + 1,
             Y = Pos.Y (justifyCheckbox) + 1,
             Y = Pos.Y (justifyCheckbox) + 1,
@@ -509,7 +492,7 @@ public class TextAlignmentAndDirection : Scenario
 
 
         // WRAP CHECKBOX
         // WRAP CHECKBOX
 
 
-        var wrapCheckbox = new CheckBox
+        CheckBox wrapCheckbox = new ()
         {
         {
             X = Pos.Right (container) + 1,
             X = Pos.Right (container) + 1,
             Y = Pos.Bottom (justifyOptions),
             Y = Pos.Bottom (justifyOptions),
@@ -520,28 +503,28 @@ public class TextAlignmentAndDirection : Scenario
         wrapCheckbox.CheckedState = wrapCheckbox.TextFormatter.WordWrap ? CheckState.Checked : CheckState.UnChecked;
         wrapCheckbox.CheckedState = wrapCheckbox.TextFormatter.WordWrap ? CheckState.Checked : CheckState.UnChecked;
 
 
         wrapCheckbox.CheckedStateChanging += (s, e) =>
         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);
         app.Add (wrapCheckbox);
 
 
         List<TextDirection> directionsEnum = Enum.GetValues (typeof (TextDirection)).Cast<TextDirection> ().ToList ();
         List<TextDirection> directionsEnum = Enum.GetValues (typeof (TextDirection)).Cast<TextDirection> ().ToList ();
 
 
-        var directionOptions = new OptionSelector
+        OptionSelector directionOptions = new ()
         {
         {
             X = Pos.Right (container) + 1,
             X = Pos.Right (container) + 1,
             Y = Pos.Bottom (wrapCheckbox) + 1,
             Y = Pos.Bottom (wrapCheckbox) + 1,
@@ -552,24 +535,24 @@ public class TextAlignmentAndDirection : Scenario
         };
         };
 
 
         directionOptions.ValueChanged += (s, ev) =>
         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);
         app.Add (directionOptions);
 
 
@@ -617,14 +600,17 @@ public class TextAlignmentAndDirection : Scenario
                             case 0:
                             case 0:
                                 t.VerticalTextAlignment = Alignment.Fill;
                                 t.VerticalTextAlignment = Alignment.Fill;
                                 t.TextAlignment = data!.h;
                                 t.TextAlignment = data!.h;
+
                                 break;
                                 break;
                             case 1:
                             case 1:
                                 t.VerticalTextAlignment = data!.v;
                                 t.VerticalTextAlignment = data!.v;
                                 t.TextAlignment = Alignment.Fill;
                                 t.TextAlignment = Alignment.Fill;
+
                                 break;
                                 break;
                             case 2:
                             case 2:
                                 t.VerticalTextAlignment = Alignment.Fill;
                                 t.VerticalTextAlignment = Alignment.Fill;
                                 t.TextAlignment = Alignment.Fill;
                                 t.TextAlignment = Alignment.Fill;
+
                                 break;
                                 break;
                         }
                         }
                     }
                     }
@@ -635,14 +621,17 @@ public class TextAlignmentAndDirection : Scenario
                             case 0:
                             case 0:
                                 t.TextAlignment = Alignment.Fill;
                                 t.TextAlignment = Alignment.Fill;
                                 t.VerticalTextAlignment = data!.v;
                                 t.VerticalTextAlignment = data!.v;
+
                                 break;
                                 break;
                             case 1:
                             case 1:
                                 t.TextAlignment = data!.h;
                                 t.TextAlignment = data!.h;
                                 t.VerticalTextAlignment = Alignment.Fill;
                                 t.VerticalTextAlignment = Alignment.Fill;
+
                                 break;
                                 break;
                             case 2:
                             case 2:
                                 t.TextAlignment = Alignment.Fill;
                                 t.TextAlignment = Alignment.Fill;
                                 t.VerticalTextAlignment = Alignment.Fill;
                                 t.VerticalTextAlignment = Alignment.Fill;
+
                                 break;
                                 break;
                         }
                         }
                     }
                     }

+ 12 - 6
Examples/UICatalog/Scenarios/TreeViewFileSystem.cs

@@ -60,7 +60,7 @@ public class TreeViewFileSystem : Scenario
         };
         };
 
 
         win.Add (_detailsFrame);
         win.Add (_detailsFrame);
-        _treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
+        _treeViewFiles.Selecting += TreeViewFiles_Selecting;
         _treeViewFiles.KeyDown += TreeViewFiles_KeyPress;
         _treeViewFiles.KeyDown += TreeViewFiles_KeyPress;
         _treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
         _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)
         if (_treeViewFiles is null)
         {
         {
             return;
             return;
         }
         }
 
 
+        // Only handle mouse clicks
+        if (e.Context is not CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
+        {
+            return;
+        }
+
         // if user right clicks
         // if user right clicks
-        if (obj.Flags.HasFlag (MouseFlags.Button3Clicked))
+        if (mouseArgs.Flags.HasFlag (MouseFlags.Button3Clicked))
         {
         {
-            IFileSystemInfo? rightClicked = _treeViewFiles.GetObjectOnRow (obj.Position.Y);
+            IFileSystemInfo? rightClicked = _treeViewFiles.GetObjectOnRow (mouseArgs.Position.Y);
 
 
             // nothing was clicked
             // nothing was clicked
             if (rightClicked is null)
             if (rightClicked is null)
@@ -576,8 +582,8 @@ public class TreeViewFileSystem : Scenario
 
 
             ShowContextMenu (
             ShowContextMenu (
                              new (
                              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
                              rightClicked
                             );
                             );

+ 10 - 9
Examples/UICatalog/Scenarios/ViewExperiments.cs

@@ -86,18 +86,19 @@ public class ViewExperiments : Scenario
             //App?.Popover!.Visible = true;
             //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<MouseBinding> { 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);
         testFrame.Add (button);
 
 

+ 199 - 0
Examples/UICatalog/Scenarios/WideGlyphs.cs

@@ -0,0 +1,199 @@
+#nullable enable
+
+using System.Text;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("WideGlyphs", "Demonstrates wide glyphs with overlapped views & clipping")]
+[ScenarioCategory ("Unicode")]
+[ScenarioCategory ("Drawing")]
+
+public sealed class WideGlyphs : Scenario
+{
+    private Rune [,]? _codepoints;
+
+    public override void Main ()
+    {
+        // Init
+        Application.Init ();
+
+        // Setup - Create a top-level application window and configure it.
+        Window appWindow = new ()
+        {
+            Title = GetQuitKeyAndName (),
+            BorderStyle = LineStyle.None
+        };
+
+        // Build the array of codepoints once when subviews are laid out
+        appWindow.SubViewsLaidOut += (s, _) =>
+        {
+            View? view = s as View;
+            if (view is null)
+            {
+                return;
+            }
+
+            // Only rebuild if size changed or array is null
+            if (_codepoints is null ||
+                _codepoints.GetLength (0) != view.Viewport.Height ||
+                _codepoints.GetLength (1) != view.Viewport.Width)
+            {
+                _codepoints = new Rune [view.Viewport.Height, view.Viewport.Width];
+
+                for (int r = 0; r < view.Viewport.Height; r++)
+                {
+                    for (int c = 0; c < view.Viewport.Width; c += 2)
+                    {
+                        _codepoints [r, c] = GetRandomWideCodepoint ();
+                    }
+                }
+            }
+        };
+
+        // Fill the window with the pre-built codepoints array
+        // For detailed documentation on the draw code flow from Application.Run to this event,
+        // see WideGlyphs.DrawFlow.md in this directory
+        appWindow.DrawingContent += (s, _) =>
+        {
+            View? view = s as View;
+            if (view is null || _codepoints is null)
+            {
+                return;
+            }
+
+            // Traverse the Viewport, using the pre-built array
+            for (int r = 0; r < view.Viewport.Height && r < _codepoints.GetLength (0); r++)
+            {
+                for (int c = 0; c < view.Viewport.Width && c < _codepoints.GetLength (1); c += 2)
+                {
+                    Rune codepoint = _codepoints [r, c];
+                    if (codepoint != default (Rune))
+                    {
+                        view.AddRune (c, r, codepoint);
+                    }
+                }
+            }
+        };
+
+        Line verticalLineAtEven = new ()
+        {
+            X = 10,
+            Orientation = Orientation.Vertical,
+            Length = Dim.Fill ()
+        };
+        appWindow.Add (verticalLineAtEven);
+
+        Line verticalLineAtOdd = new ()
+        {
+            X = 25,
+            Orientation = Orientation.Vertical,
+            Length = Dim.Fill ()
+        };
+        appWindow.Add (verticalLineAtOdd);
+
+        View arrangeableViewAtEven = new ()
+        {
+            CanFocus = true,
+            Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
+            X = 30,
+            Y = 5,
+            Width = 15,
+            Height = 5,
+            //BorderStyle = LineStyle.Dashed,
+        };
+
+        // Proves it's not LineCanvas related
+        arrangeableViewAtEven!.Border!.Thickness = new (1);
+        arrangeableViewAtEven.Border.Add(new View () { Height = Dim.Auto(), Width = Dim.Auto(), Text = "Even" });
+        appWindow.Add (arrangeableViewAtEven);
+
+        View arrangeableViewAtOdd = new ()
+        {
+            CanFocus = true,
+            Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
+            X = 31,
+            Y = 11,
+            Width = 15,
+            Height = 5,
+            BorderStyle = LineStyle.Dashed,
+        };
+        appWindow.Add (arrangeableViewAtOdd);
+
+        var superView = new View
+        {
+            CanFocus = true,
+            X = 30, // on an even column to start
+            Y = Pos.Center (),
+            Width = Dim.Auto () + 4,
+            Height = Dim.Auto () + 1,
+            BorderStyle = LineStyle.Single,
+            Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable
+        };
+
+        Rune codepoint = Glyphs.Apple;
+
+        superView.DrawingContent += (s, e) =>
+                                    {
+                                        var view = s as View;
+                                        for (var r = 0; r < view!.Viewport.Height; r++)
+                                        {
+                                            for (var c = 0; c < view.Viewport.Width; c += 2)
+                                            {
+                                                if (codepoint != default (Rune))
+                                                {
+                                                    view.AddRune (c, r, codepoint);
+                                                }
+                                            }
+                                        }
+                                        e.DrawContext?.AddDrawnRectangle (view.Viewport);
+                                        e.Cancel = true;
+                                    };
+        appWindow.Add (superView);
+
+        var viewWithBorderAtX0 = new View
+        {
+            Text = "viewWithBorderAtX0",
+            BorderStyle = LineStyle.Dashed,
+            X = 0,
+            Y = 1,
+            Width = Dim.Auto (),
+            Height = 3
+        };
+
+        var viewWithBorderAtX1 = new View
+        {
+            Text = "viewWithBorderAtX1",
+            BorderStyle = LineStyle.Dashed,
+            X = 1,
+            Y = Pos.Bottom (viewWithBorderAtX0) + 1,
+            Width = Dim.Auto (),
+            Height = 3
+        };
+
+        var viewWithBorderAtX2 = new View
+        {
+            Text = "viewWithBorderAtX2",
+            BorderStyle = LineStyle.Dashed,
+            X = 2,
+            Y = Pos.Bottom (viewWithBorderAtX1) + 1,
+            Width = Dim.Auto (),
+            Height = 3
+        };
+
+        superView.Add (viewWithBorderAtX0, viewWithBorderAtX1, viewWithBorderAtX2);
+
+        // Run - Start the application.
+        Application.Run (appWindow);
+        appWindow.Dispose ();
+
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
+    }
+
+    private Rune GetRandomWideCodepoint ()
+    {
+        Random random = new ();
+        int codepoint = random.Next (0x4E00, 0x9FFF);
+        return new (codepoint);
+    }
+}

+ 2 - 0
Terminal.Gui/App/ApplicationImpl.Run.cs

@@ -368,6 +368,8 @@ internal partial class ApplicationImpl
             previousRunnable.RaiseIsModalChangedEvent (true);
             previousRunnable.RaiseIsModalChangedEvent (true);
         }
         }
 
 
+        Mouse?.UngrabMouse ();
+
         runnable.RaiseIsRunningChangedEvent (false);
         runnable.RaiseIsRunningChangedEvent (false);
 
 
         token.Result = runnable.Result;
         token.Result = runnable.Result;

+ 7 - 1
Terminal.Gui/App/Mouse/MouseImpl.cs

@@ -266,11 +266,17 @@ internal class MouseImpl : IMouse, IDisposable
     /// <inheritdoc/>
     /// <inheritdoc/>
     public void GrabMouse (View? view)
     public void GrabMouse (View? view)
     {
     {
-        if (view is null || RaiseGrabbingMouseEvent (view))
+        if (RaiseGrabbingMouseEvent (view))
         {
         {
             return;
             return;
         }
         }
 
 
+        if (view is null)
+        {
+            UngrabMouse();
+            return;
+        }
+
         RaiseGrabbedMouseEvent (view);
         RaiseGrabbedMouseEvent (view);
 
 
         // MouseGrabView is only set if the application is initialized.
         // MouseGrabView is only set if the application is initialized.

+ 2 - 1
Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs

@@ -109,6 +109,7 @@ public class NetOutput : OutputBase, IOutput
     /// <inheritdoc />
     /// <inheritdoc />
     protected override void Write (StringBuilder output)
     protected override void Write (StringBuilder output)
     {
     {
+        base.Write (output);
         try
         try
         {
         {
             Console.Out.Write (output);
             Console.Out.Write (output);
@@ -140,7 +141,7 @@ public class NetOutput : OutputBase, IOutput
             }
             }
             catch (Exception)
             catch (Exception)
             {
             {
-                return false;
+                return true;
             }
             }
         }
         }
 
 

+ 21 - 14
Terminal.Gui/Drivers/DriverImpl.cs

@@ -45,19 +45,19 @@ internal class DriverImpl : IDriver
         ISizeMonitor sizeMonitor
         ISizeMonitor sizeMonitor
     )
     )
     {
     {
-        InputProcessor = inputProcessor;
+        _inputProcessor = inputProcessor;
         _output = output;
         _output = output;
         OutputBuffer = outputBuffer;
         OutputBuffer = outputBuffer;
         _ansiRequestScheduler = ansiRequestScheduler;
         _ansiRequestScheduler = ansiRequestScheduler;
 
 
-        InputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e);
-        InputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
+        GetInputProcessor ().KeyDown += (s, e) => KeyDown?.Invoke (s, e);
+        GetInputProcessor ().KeyUp += (s, e) => KeyUp?.Invoke (s, e);
 
 
-        InputProcessor.MouseEvent += (s, e) =>
-                                     {
-                                         //Logging.Logger.LogTrace ($"Mouse {e.Flags} at x={e.ScreenPosition.X} y={e.ScreenPosition.Y}");
-                                         MouseEvent?.Invoke (s, e);
-                                     };
+        GetInputProcessor ().MouseEvent += (s, e) =>
+                                           {
+                                               //Logging.Logger.LogTrace ($"Mouse {e.Flags} at x={e.ScreenPosition.X} y={e.ScreenPosition.Y}");
+                                               MouseEvent?.Invoke (s, e);
+                                           };
 
 
         SizeMonitor = sizeMonitor;
         SizeMonitor = sizeMonitor;
         SizeMonitor.SizeChanged += OnSizeMonitorOnSizeChanged;
         SizeMonitor.SizeChanged += OnSizeMonitorOnSizeChanged;
@@ -73,15 +73,18 @@ internal class DriverImpl : IDriver
     public void Init () { throw new NotSupportedException (); }
     public void Init () { throw new NotSupportedException (); }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public void Refresh () { _output.Write (OutputBuffer); }
+    public void Refresh ()
+    {
+        _output.Write (OutputBuffer);
+    }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public string? GetName () => InputProcessor.DriverName?.ToLowerInvariant ();
+    public string? GetName () => GetInputProcessor ().DriverName?.ToLowerInvariant ();
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
     public virtual string GetVersionInfo ()
     public virtual string GetVersionInfo ()
     {
     {
-        string type = InputProcessor.DriverName ?? throw new ArgumentNullException (nameof (InputProcessor.DriverName));
+        string type = GetInputProcessor ().DriverName ?? throw new InvalidOperationException ("Driver name is not set.");
 
 
         return type;
         return type;
     }
     }
@@ -143,8 +146,12 @@ internal class DriverImpl : IDriver
 
 
     private readonly IOutput _output;
     private readonly IOutput _output;
 
 
+    public IOutput GetOutput () => _output;
+
+    private readonly IInputProcessor _inputProcessor;
+
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public IInputProcessor InputProcessor { get; }
+    public IInputProcessor GetInputProcessor () => _inputProcessor;
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
     public IOutputBuffer OutputBuffer { get; }
     public IOutputBuffer OutputBuffer { get; }
@@ -157,7 +164,7 @@ internal class DriverImpl : IDriver
 
 
     private void CreateClipboard ()
     private void CreateClipboard ()
     {
     {
-        if (InputProcessor.DriverName is { } && InputProcessor.DriverName.Contains ("fake"))
+        if (GetInputProcessor ().DriverName is { } && GetInputProcessor ()!.DriverName!.Contains ("fake"))
         {
         {
             if (Clipboard is null)
             if (Clipboard is null)
             {
             {
@@ -414,7 +421,7 @@ internal class DriverImpl : IDriver
     public event EventHandler<Key>? KeyUp;
     public event EventHandler<Key>? KeyUp;
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public void EnqueueKeyEvent (Key key) { InputProcessor.EnqueueKeyDownEvent (key); }
+    public void EnqueueKeyEvent (Key key) { GetInputProcessor ().EnqueueKeyDownEvent (key); }
 
 
     #endregion Input Events
     #endregion Input Events
 
 

+ 20 - 20
Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs

@@ -7,29 +7,29 @@ namespace Terminal.Gui.Drivers;
 /// </summary>
 /// </summary>
 public class FakeOutput : OutputBase, IOutput
 public class FakeOutput : OutputBase, IOutput
 {
 {
-    private readonly StringBuilder _output = new ();
+   // private readonly StringBuilder _outputStringBuilder = new ();
     private int _cursorLeft;
     private int _cursorLeft;
     private int _cursorTop;
     private int _cursorTop;
     private Size _consoleSize = new (80, 25);
     private Size _consoleSize = new (80, 25);
+    private IOutputBuffer? _lastBuffer;
 
 
     /// <summary>
     /// <summary>
     /// 
     /// 
     /// </summary>
     /// </summary>
     public FakeOutput ()
     public FakeOutput ()
     {
     {
-        LastBuffer = new OutputBufferImpl ();
-        LastBuffer.SetSize (80, 25);
+        _lastBuffer = new OutputBufferImpl ();
+        _lastBuffer.SetSize (80, 25);
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Gets or sets the last output buffer written.
+    ///     Gets or sets the last output buffer written. The <see cref="IOutputBuffer.Contents"/> contains
+    ///     a reference to the buffer last written with <see cref="Write(IOutputBuffer)"/>.
     /// </summary>
     /// </summary>
-    public IOutputBuffer? LastBuffer { get; set; }
+    public IOutputBuffer? GetLastBuffer () => _lastBuffer;
 
 
-    /// <summary>
-    ///     Gets the captured output as a string.
-    /// </summary>
-    public string Output => _output.ToString ();
+    ///// <inheritdoc cref="IOutput.GetLastOutput"/>
+    //public override string GetLastOutput () => _outputStringBuilder.ToString ();
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public Point GetCursorPosition ()
     public Point GetCursorPosition ()
@@ -61,28 +61,28 @@ public class FakeOutput : OutputBase, IOutput
     /// <inheritdoc/>
     /// <inheritdoc/>
     public void Write (ReadOnlySpan<char> text)
     public void Write (ReadOnlySpan<char> text)
     {
     {
-        _output.Append (text);
+//        _outputStringBuilder.Append (text);
     }
     }
 
 
-    /// <inheritdoc cref="IDriver"/>
+    /// <inheritdoc cref="IOutput.Write(IOutputBuffer)"/>
     public override void Write (IOutputBuffer buffer)
     public override void Write (IOutputBuffer buffer)
     {
     {
-        LastBuffer = buffer;
+        _lastBuffer = buffer;
         base.Write (buffer);
         base.Write (buffer);
     }
     }
 
 
+    ///// <inheritdoc/>
+    //protected override void Write (StringBuilder output)
+    //{
+    //    _outputStringBuilder.Append (output);
+    //}
+
     /// <inheritdoc cref="IDriver"/>
     /// <inheritdoc cref="IDriver"/>
     public override void SetCursorVisibility (CursorVisibility visibility)
     public override void SetCursorVisibility (CursorVisibility visibility)
     {
     {
         // Capture but don't act on it in fake output
         // Capture but don't act on it in fake output
     }
     }
 
 
-    /// <inheritdoc/>
-    public void Dispose ()
-    {
-        // Nothing to dispose
-    }
-
     /// <inheritdoc/>
     /// <inheritdoc/>
     protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
     protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
     {
     {
@@ -123,8 +123,8 @@ public class FakeOutput : OutputBase, IOutput
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    protected override void Write (StringBuilder output)
+    public void Dispose ()
     {
     {
-        _output.Append (output);
+        // Nothing to dispose
     }
     }
 }
 }

+ 6 - 1
Terminal.Gui/Drivers/IDriver.cs

@@ -61,7 +61,12 @@ public interface IDriver : IDisposable
     ///     e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
     ///     e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
     ///     and detecting and processing ansi escape sequences.
     ///     and detecting and processing ansi escape sequences.
     /// </summary>
     /// </summary>
-    IInputProcessor InputProcessor { get; }
+    IInputProcessor GetInputProcessor ();
+
+    /// <summary>
+    ///     Gets the output handler responsible for writing to the terminal.
+    /// </summary>
+    IOutput GetOutput ();
 
 
     /// <summary>Get the operating system clipboard.</summary>
     /// <summary>Get the operating system clipboard.</summary>
     IClipboard? Clipboard { get; }
     IClipboard? Clipboard { get; }

+ 6 - 0
Terminal.Gui/Drivers/IOutput.cs

@@ -65,6 +65,12 @@ public interface IOutput : IDisposable
     /// <param name="buffer"></param>
     /// <param name="buffer"></param>
     void Write (IOutputBuffer buffer);
     void Write (IOutputBuffer buffer);
 
 
+    /// <summary>
+    ///     Gets a string containing the ANSI escape sequences and content most recently written
+    ///     to the terminal via <see cref="Write(IOutputBuffer)"/>
+    /// </summary>
+    string GetLastOutput ();
+
     /// <summary>
     /// <summary>
     ///     Generates an ANSI escape sequence string representation of the given <paramref name="buffer"/> contents.
     ///     Generates an ANSI escape sequence string representation of the given <paramref name="buffer"/> contents.
     ///     This is the same output that would be written to the terminal to recreate the current screen contents.
     ///     This is the same output that would be written to the terminal to recreate the current screen contents.

+ 48 - 25
Terminal.Gui/Drivers/OutputBase.cs

@@ -56,19 +56,27 @@ public abstract class OutputBase
     /// <param name="visibility"></param>
     /// <param name="visibility"></param>
     public abstract void SetCursorVisibility (CursorVisibility visibility);
     public abstract void SetCursorVisibility (CursorVisibility visibility);
 
 
-    /// <inheritdoc cref="IOutput.Write(IOutputBuffer)"/>
+    StringBuilder _lastOutputStringBuilder = new ();
+
+    /// <summary>
+    ///     Writes dirty cells from the buffer to the console. Hides cursor, iterates rows/cols,
+    ///     skips clean cells, batches dirty cells into ANSI sequences, wraps URLs with OSC 8,
+    ///     then renders sixel images. Cursor visibility is managed by <c>ApplicationMainLoop.SetCursor()</c>.
+    /// </summary>
     public virtual void Write (IOutputBuffer buffer)
     public virtual void Write (IOutputBuffer buffer)
     {
     {
-        var top = 0;
-        var left = 0;
+        StringBuilder outputStringBuilder = new ();
+        int top = 0;
+        int left = 0;
         int rows = buffer.Rows;
         int rows = buffer.Rows;
         int cols = buffer.Cols;
         int cols = buffer.Cols;
-        var output = new StringBuilder ();
         Attribute? redrawAttr = null;
         Attribute? redrawAttr = null;
         int lastCol = -1;
         int lastCol = -1;
 
 
+        // Hide cursor during rendering to prevent flicker
         SetCursorVisibility (CursorVisibility.Invisible);
         SetCursorVisibility (CursorVisibility.Invisible);
 
 
+        // Process each row
         for (int row = top; row < rows; row++)
         for (int row = top; row < rows; row++)
         {
         {
             if (!SetCursorPositionImpl (0, row))
             if (!SetCursorPositionImpl (0, row))
@@ -76,20 +84,24 @@ public abstract class OutputBase
                 return;
                 return;
             }
             }
 
 
-            output.Clear ();
+            outputStringBuilder.Clear ();
 
 
+            // Process columns in row
             for (int col = left; col < cols; col++)
             for (int col = left; col < cols; col++)
             {
             {
                 lastCol = -1;
                 lastCol = -1;
                 var outputWidth = 0;
                 var outputWidth = 0;
 
 
+                // Batch consecutive dirty cells
                 for (; col < cols; col++)
                 for (; col < cols; col++)
                 {
                 {
+                    // Skip clean cells - position cursor and continue
                     if (!buffer.Contents! [row, col].IsDirty)
                     if (!buffer.Contents! [row, col].IsDirty)
                     {
                     {
-                        if (output.Length > 0)
+                        if (outputStringBuilder.Length > 0)
                         {
                         {
-                            WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                            // This clears outputStringBuilder
+                            WriteToConsole (outputStringBuilder, ref lastCol, ref outputWidth);
                         }
                         }
                         else if (lastCol == -1)
                         else if (lastCol == -1)
                         {
                         {
@@ -111,27 +123,26 @@ public abstract class OutputBase
                         lastCol = col;
                         lastCol = col;
                     }
                     }
 
 
+                    // Append dirty cell as ANSI and mark clean
                     Cell cell = buffer.Contents [row, col];
                     Cell cell = buffer.Contents [row, col];
-                    AppendCellAnsi (cell, output, ref redrawAttr, ref _redrawTextStyle, cols, ref col);
-
-                    outputWidth++;
-
                     buffer.Contents [row, col].IsDirty = false;
                     buffer.Contents [row, col].IsDirty = false;
+                    AppendCellAnsi (cell, outputStringBuilder, ref redrawAttr, ref _redrawTextStyle, cols, ref col, ref outputWidth);
                 }
                 }
             }
             }
 
 
-            if (output.Length > 0)
+            // Flush buffered output for row
+            if (outputStringBuilder.Length > 0)
             {
             {
                 if (IsLegacyConsole)
                 if (IsLegacyConsole)
                 {
                 {
-                    Write (output);
+                    Write (outputStringBuilder);
                 }
                 }
                 else
                 else
                 {
                 {
                     SetCursorPositionImpl (lastCol, row);
                     SetCursorPositionImpl (lastCol, row);
 
 
-                    // Wrap URLs with OSC 8 hyperlink sequences using the new Osc8UrlLinker
-                    StringBuilder processed = Osc8UrlLinker.WrapOsc8 (output);
+                    // Wrap URLs with OSC 8 hyperlink sequences
+                    StringBuilder processed = Osc8UrlLinker.WrapOsc8 (outputStringBuilder);
                     Write (processed);
                     Write (processed);
                 }
                 }
             }
             }
@@ -142,6 +153,7 @@ public abstract class OutputBase
             return;
             return;
         }
         }
 
 
+        // Render queued sixel images
         foreach (SixelToRender s in GetSixels ())
         foreach (SixelToRender s in GetSixels ())
         {
         {
             if (string.IsNullOrWhiteSpace (s.SixelData))
             if (string.IsNullOrWhiteSpace (s.SixelData))
@@ -153,12 +165,12 @@ public abstract class OutputBase
             Write ((StringBuilder)new (s.SixelData));
             Write ((StringBuilder)new (s.SixelData));
         }
         }
 
 
-
-        // DO NOT restore cursor visibility here - let ApplicationMainLoop.SetCursor() handle it
-        // The old code was saving/restoring visibility which caused flickering because
-        // it would restore to the old value even if the application wanted it hidden
+        // Cursor visibility restored by ApplicationMainLoop.SetCursor() to prevent flicker
     }
     }
 
 
+    /// <inheritdoc cref="IOutput.GetLastOutput" />
+    public virtual string GetLastOutput () => _lastOutputStringBuilder.ToString ();
+
     /// <summary>
     /// <summary>
     ///     Changes the color and text style of the console to the given <paramref name="attr"/> and
     ///     Changes the color and text style of the console to the given <paramref name="attr"/> and
     ///     <paramref name="redrawTextStyle"/>.
     ///     <paramref name="redrawTextStyle"/>.
@@ -183,7 +195,10 @@ public abstract class OutputBase
     ///     Output the contents of the <paramref name="output"/> to the console.
     ///     Output the contents of the <paramref name="output"/> to the console.
     /// </summary>
     /// </summary>
     /// <param name="output"></param>
     /// <param name="output"></param>
-    protected abstract void Write (StringBuilder output);
+    protected virtual void Write (StringBuilder output)
+    {
+        _lastOutputStringBuilder.Append (output);
+    }
 
 
     /// <summary>
     /// <summary>
     ///     Builds ANSI escape sequences for the specified rectangular region of the buffer.
     ///     Builds ANSI escape sequences for the specified rectangular region of the buffer.
@@ -221,7 +236,8 @@ public abstract class OutputBase
                 }
                 }
 
 
                 Cell cell = buffer.Contents! [row, col];
                 Cell cell = buffer.Contents! [row, col];
-                AppendCellAnsi (cell, output, ref lastAttr, ref redrawTextStyle, endCol, ref col);
+                int outputWidth = -1;
+                AppendCellAnsi (cell, output, ref lastAttr, ref redrawTextStyle, endCol, ref col, ref outputWidth);
             }
             }
 
 
             // Add newline at end of row if requested
             // Add newline at end of row if requested
@@ -241,7 +257,8 @@ public abstract class OutputBase
     /// <param name="redrawTextStyle">The current text style for optimization.</param>
     /// <param name="redrawTextStyle">The current text style for optimization.</param>
     /// <param name="maxCol">The maximum column, used for wide character handling.</param>
     /// <param name="maxCol">The maximum column, used for wide character handling.</param>
     /// <param name="currentCol">The current column, updated for wide characters.</param>
     /// <param name="currentCol">The current column, updated for wide characters.</param>
-    protected void AppendCellAnsi (Cell cell, StringBuilder output, ref Attribute? lastAttr, ref TextStyle redrawTextStyle, int maxCol, ref int currentCol)
+    /// <param name="outputWidth">The current output width, updated for wide characters.</param>
+    protected void AppendCellAnsi (Cell cell, StringBuilder output, ref Attribute? lastAttr, ref TextStyle redrawTextStyle, int maxCol, ref int currentCol, ref int outputWidth)
     {
     {
         Attribute? attribute = cell.Attribute;
         Attribute? attribute = cell.Attribute;
 
 
@@ -256,11 +273,13 @@ public abstract class OutputBase
         // Add the grapheme
         // Add the grapheme
         string grapheme = cell.Grapheme;
         string grapheme = cell.Grapheme;
         output.Append (grapheme);
         output.Append (grapheme);
+        outputWidth++;
 
 
         // Handle wide grapheme
         // Handle wide grapheme
         if (grapheme.GetColumns () > 1 && currentCol + 1 < maxCol)
         if (grapheme.GetColumns () > 1 && currentCol + 1 < maxCol)
         {
         {
             currentCol++; // Skip next cell for wide character
             currentCol++; // Skip next cell for wide character
+            outputWidth++;
         }
         }
     }
     }
 
 
@@ -272,7 +291,7 @@ public abstract class OutputBase
     /// <returns>A string containing ANSI escape sequences representing the buffer contents.</returns>
     /// <returns>A string containing ANSI escape sequences representing the buffer contents.</returns>
     public string ToAnsi (IOutputBuffer buffer)
     public string ToAnsi (IOutputBuffer buffer)
     {
     {
-        var output = new StringBuilder ();
+        StringBuilder output = new ();
         Attribute? lastAttr = null;
         Attribute? lastAttr = null;
 
 
         BuildAnsiForRegion (buffer, 0, buffer.Rows, 0, buffer.Cols, output, ref lastAttr);
         BuildAnsiForRegion (buffer, 0, buffer.Rows, 0, buffer.Cols, output, ref lastAttr);
@@ -280,7 +299,11 @@ public abstract class OutputBase
         return output.ToString ();
         return output.ToString ();
     }
     }
 
 
-    private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+    /// <summary>
+    ///     Writes buffered output to console, wrapping URLs with OSC 8 hyperlinks (non-legacy only),
+    ///     then clears the buffer and advances <paramref name="lastCol"/> by <paramref name="outputWidth"/>.
+    /// </summary>
+    private void WriteToConsole (StringBuilder output, ref int lastCol, ref int outputWidth)
     {
     {
         if (IsLegacyConsole)
         if (IsLegacyConsole)
         {
         {
@@ -288,7 +311,7 @@ public abstract class OutputBase
         }
         }
         else
         else
         {
         {
-            // Wrap URLs with OSC 8 hyperlink sequences using the new Osc8UrlLinker
+            // Wrap URLs with OSC 8 hyperlink sequences
             StringBuilder processed = Osc8UrlLinker.WrapOsc8 (output);
             StringBuilder processed = Osc8UrlLinker.WrapOsc8 (output);
             Write (processed);
             Write (processed);
         }
         }

+ 125 - 92
Terminal.Gui/Drivers/OutputBufferImpl.cs

@@ -14,7 +14,7 @@ public class OutputBufferImpl : IOutputBuffer
     ///     UpdateScreen is called.
     ///     UpdateScreen is called.
     ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
     ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
     /// </summary>
     /// </summary>
-    public Cell [,]? Contents { get; set; } = new Cell[0, 0];
+    public Cell [,]? Contents { get; set; } = new Cell [0, 0];
 
 
     private int _cols;
     private int _cols;
     private int _rows;
     private int _rows;
@@ -66,7 +66,7 @@ public class OutputBufferImpl : IOutputBuffer
     public virtual int Top { get; set; } = 0;
     public virtual int Top { get; set; } = 0;
 
 
     /// <summary>
     /// <summary>
-    /// Indicates which lines have been modified and need to be redrawn.
+    ///     Indicates which lines have been modified and need to be redrawn.
     /// </summary>
     /// </summary>
     public bool [] DirtyLines { get; set; } = [];
     public bool [] DirtyLines { get; set; } = [];
 
 
@@ -138,116 +138,149 @@ public class OutputBufferImpl : IOutputBuffer
     {
     {
         foreach (string grapheme in GraphemeHelper.GetGraphemes (str))
         foreach (string grapheme in GraphemeHelper.GetGraphemes (str))
         {
         {
-            string text = grapheme;
+            AddGrapheme (grapheme);
+        }
+    }
 
 
-            if (Contents is null)
-            {
-                return;
-            }
+    /// <summary>
+    ///     Adds a single grapheme to the display at the current cursor position.
+    /// </summary>
+    /// <param name="grapheme">The grapheme to add.</param>
+    private void AddGrapheme (string grapheme)
+    {
+        if (Contents is null)
+        {
+            return;
+        }
 
 
-            Clip ??= new (Screen);
+        Clip ??= new (Screen);
+        Rectangle clipRect = Clip!.GetBounds ();
 
 
-            Rectangle clipRect = Clip!.GetBounds ();
+        string text = grapheme;
+        int textWidth = -1;
 
 
-            int textWidth = -1;
-            bool validLocation = false;
+        lock (Contents)
+        {
+            bool validLocation = IsValidLocation (text, Col, Row);
 
 
-            lock (Contents)
+            if (validLocation)
             {
             {
-                // Validate location inside the lock to prevent race conditions
-                validLocation = IsValidLocation (text, Col, Row);
-
-                if (validLocation)
-                {
-                    text = text.MakePrintable ();
-                    textWidth = text.GetColumns ();
-
-                    Contents [Row, Col].Attribute = CurrentAttribute;
-                    Contents [Row, Col].IsDirty = true;
+                text = text.MakePrintable ();
+                textWidth = text.GetColumns ();
 
 
-                    if (Col > 0)
-                    {
-                        // Check if cell to left has a wide glyph
-                        if (Contents [Row, Col - 1].Grapheme.GetColumns () > 1)
-                        {
-                            // Invalidate cell to left
-                            Contents [Row, Col - 1].Grapheme = Rune.ReplacementChar.ToString ();
-                            Contents [Row, Col - 1].IsDirty = true;
-                        }
-                    }
+                // Set attribute and mark dirty for current cell
+                Contents [Row, Col].Attribute = CurrentAttribute;
+                Contents [Row, Col].IsDirty = true;
 
 
-                    if (textWidth is 0 or 1)
-                    {
-                        Contents [Row, Col].Grapheme = text;
+                InvalidateOverlappedWideGlyph ();
 
 
-                        if (Col < clipRect.Right - 1 && Col + 1 < Cols)
-                        {
-                            Contents [Row, Col + 1].IsDirty = true;
-                        }
-                    }
-                    else if (textWidth == 2)
-                    {
-                        if (!Clip.Contains (Col + 1, Row))
-                        {
-                            // We're at the right edge of the clip, so we can't display a wide character.
-                            Contents [Row, Col].Grapheme = Rune.ReplacementChar.ToString ();
-                        }
-                        else if (!Clip.Contains (Col, Row))
-                        {
-                            // Our 1st column is outside the clip, so we can't display a wide character.
-                            if (Col + 1 < Cols)
-                            {
-                                Contents [Row, Col + 1].Grapheme = Rune.ReplacementChar.ToString ();
-                            }
-                        }
-                        else
-                        {
-                            Contents [Row, Col].Grapheme = text;
-
-                            if (Col < clipRect.Right - 1 && Col + 1 < Cols)
-                            {
-                                // Invalidate cell to right so that it doesn't get drawn
-                                Contents [Row, Col + 1].Grapheme = Rune.ReplacementChar.ToString ();
-                                Contents [Row, Col + 1].IsDirty = true;
-                            }
-                        }
-                    }
-                    else
-                    {
-                        // This is a non-spacing character, so we don't need to do anything
-                        Contents [Row, Col].Grapheme = " ";
-                        Contents [Row, Col].IsDirty = false;
-                    }
+                WriteGraphemeByWidth (text, textWidth, clipRect);
 
 
-                    DirtyLines [Row] = true;
-                }
+                DirtyLines [Row] = true;
             }
             }
 
 
+            // Always advance cursor (even if location was invalid)
+            // Keep Col/Row updates inside the lock to prevent race conditions
             Col++;
             Col++;
 
 
             if (textWidth > 1)
             if (textWidth > 1)
             {
             {
-                Debug.Assert (textWidth <= 2);
+                // Skip the second column of a wide character
+                // IMPORTANT: We do NOT modify column N+1's IsDirty or Attribute here.
+                // See: https://github.com/gui-cs/Terminal.Gui/issues/4258
+                Col++;
+            }
+        }
+    }
 
 
-                if (validLocation)
-                {
-                    lock (Contents!)
-                    {
-                        // Re-validate Col is still in bounds after increment
-                        if (Col < Cols && Row < Rows && Col < clipRect.Right)
-                        {
-                            // This is a double-width character, and we are not at the end of the line.
-                            // Col now points to the second column of the character. Ensure it doesn't
-                            // Get rendered.
-                            Contents [Row, Col].IsDirty = false;
-                            Contents [Row, Col].Attribute = CurrentAttribute;
-                        }
-                    }
-                }
+    /// <summary>
+    ///     If we're writing at an odd column and there's a wide glyph to our left,
+    ///     invalidate it since we're overwriting the second half.
+    /// </summary>
+    private void InvalidateOverlappedWideGlyph ()
+    {
+        if (Col > 0 && Contents! [Row, Col - 1].Grapheme.GetColumns () > 1)
+        {
+            Contents [Row, Col - 1].Grapheme = Rune.ReplacementChar.ToString ();
+            Contents [Row, Col - 1].IsDirty = true;
+        }
+    }
 
 
-                Col++;
+    /// <summary>
+    ///     Writes a grapheme to the buffer based on its width (0, 1, or 2 columns).
+    /// </summary>
+    /// <param name="text">The printable text to write.</param>
+    /// <param name="textWidth">The column width of the text.</param>
+    /// <param name="clipRect">The clipping rectangle.</param>
+    private void WriteGraphemeByWidth (string text, int textWidth, Rectangle clipRect)
+    {
+        switch (textWidth)
+        {
+            case 0:
+            case 1:
+                WriteSingleWidthGrapheme (text, clipRect);
+
+                break;
+
+            case 2:
+                WriteWideGrapheme (text);
+
+                break;
+
+            default:
+                // Negative width or non-spacing character (shouldn't normally occur)
+                Contents! [Row, Col].Grapheme = " ";
+                Contents [Row, Col].IsDirty = false;
+
+                break;
+        }
+    }
+
+    /// <summary>
+    ///     Writes a single-width character (0 or 1 column wide).
+    /// </summary>
+    private void WriteSingleWidthGrapheme (string text, Rectangle clipRect)
+    {
+        Contents! [Row, Col].Grapheme = text;
+
+        // Mark the next cell as dirty to ensure proper rendering of adjacent content
+        if (Col < clipRect.Right - 1 && Col + 1 < Cols)
+        {
+            Contents [Row, Col + 1].IsDirty = true;
+        }
+    }
+
+    /// <summary>
+    ///     Writes a wide character (2 columns wide) handling clipping and partial overlap cases.
+    /// </summary>
+    private void WriteWideGrapheme (string text)
+    {
+        if (!Clip!.Contains (Col + 1, Row))
+        {
+            // Second column is outside clip - can't fit wide char here
+            Contents! [Row, Col].Grapheme = Rune.ReplacementChar.ToString ();
+        }
+        else if (!Clip.Contains (Col, Row))
+        {
+            // First column is outside clip but second isn't
+            // Mark second column as replacement to indicate partial overlap
+            if (Col + 1 < Cols)
+            {
+                Contents! [Row, Col + 1].Grapheme = Rune.ReplacementChar.ToString ();
             }
             }
         }
         }
+        else
+        {
+            // Both columns are in bounds - write the wide character
+            // It will naturally render across both columns when output to the terminal
+            Contents! [Row, Col].Grapheme = text;
+
+            // DO NOT modify column N+1 here!
+            // The wide glyph will naturally render across both columns.
+            // If we set column N+1 to replacement char, we would overwrite
+            // any content that was intentionally drawn there (like borders at odd columns).
+            // See: https://github.com/gui-cs/Terminal.Gui/issues/4258
+        }
     }
     }
 
 
     /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>
     /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>

+ 1 - 0
Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs

@@ -66,6 +66,7 @@ internal class UnixOutput : OutputBase, IOutput
     /// <inheritdoc />
     /// <inheritdoc />
     protected override void Write (StringBuilder output)
     protected override void Write (StringBuilder output)
     {
     {
+        base.Write (output);
         try
         try
         {
         {
             byte [] utf8 = Encoding.UTF8.GetBytes (output.ToString ());
             byte [] utf8 = Encoding.UTF8.GetBytes (output.ToString ());

+ 3 - 1
Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs

@@ -184,7 +184,8 @@ internal partial class WindowsOutput : OutputBase, IOutput
 
 
         if (!WriteConsole (!IsLegacyConsole ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
         if (!WriteConsole (!IsLegacyConsole ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
         {
         {
-            throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
+            // Don't throw in unit tests
+            // throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
         }
         }
     }
     }
 
 
@@ -318,6 +319,7 @@ internal partial class WindowsOutput : OutputBase, IOutput
         {
         {
             return;
             return;
         }
         }
+        base.Write (output);
 
 
         var str = output.ToString ();
         var str = output.ToString ();
 
 

+ 1 - 4
Terminal.Gui/Input/Mouse/MouseEventArgs.cs

@@ -4,10 +4,7 @@ using System.ComponentModel;
 namespace Terminal.Gui.Input;
 namespace Terminal.Gui.Input;
 
 
 /// <summary>
 /// <summary>
-///     Specifies the event arguments for <see cref="MouseEventArgs"/>. This is a higher-level construct than
-///     the wrapped <see cref="MouseEventArgs"/> class and is used for the events defined on
-///     <see cref="View"/> and subclasses
-///     of View (e.g. <see cref="View.MouseEnter"/> and <see cref="View.MouseClick"/>).
+///     Specifies the event arguments for <see cref="MouseEventArgs"/>.
 /// </summary>
 /// </summary>
 public class MouseEventArgs : HandledEventArgs
 public class MouseEventArgs : HandledEventArgs
 {
 {

+ 11 - 12
Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs

@@ -1,4 +1,3 @@
-using System.ComponentModel;
 using System.Diagnostics;
 using System.Diagnostics;
 
 
 namespace Terminal.Gui.ViewBase;
 namespace Terminal.Gui.ViewBase;
@@ -766,6 +765,17 @@ public partial class Border
         }
         }
     }
     }
 
 
+    /// <summary>
+    ///     Cancels <see cref="IMouseGrabHandler.GrabbingMouse"/> events during an active drag to prevent other views from
+    ///     stealing the mouse grab mid-operation.
+    /// </summary>
+    /// <remarks>
+    ///     During an Arrange Mode drag (<see cref="_dragPosition"/> has a value), Border owns the mouse grab and
+    ///     must receive all mouse events until Button1Released. If another view (e.g., scrollbar, slider) were allowed
+    ///     to grab the mouse, the drag would freeze, leaving Border in an inconsistent state with no cleanup.
+    ///     Canceling follows the CWP pattern, ensuring Border maintains exclusive mouse control until it explicitly
+    ///     releases via <see cref="IMouseGrabHandler.UngrabMouse"/> in <see cref="OnMouseEvent"/>.
+    /// </remarks>
     private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e)
     private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e)
     {
     {
         if (App?.Mouse.MouseGrabView == this && _dragPosition.HasValue)
         if (App?.Mouse.MouseGrabView == this && _dragPosition.HasValue)
@@ -774,25 +784,14 @@ public partial class Border
         }
         }
     }
     }
 
 
-    private void Application_UnGrabbingMouse (object? sender, GrabMouseEventArgs e)
-    {
-        if (App?.Mouse.MouseGrabView == this && _dragPosition.HasValue)
-        {
-            e.Cancel = true;
-        }
-    }
-
     #endregion Mouse Support
     #endregion Mouse Support
 
 
-
-
     /// <inheritdoc/>
     /// <inheritdoc/>
     protected override void Dispose (bool disposing)
     protected override void Dispose (bool disposing)
     {
     {
         if (App is { })
         if (App is { })
         {
         {
             App.Mouse.GrabbingMouse -= Application_GrabbingMouse;
             App.Mouse.GrabbingMouse -= Application_GrabbingMouse;
-            App.Mouse.UnGrabbingMouse -= Application_UnGrabbingMouse;
         }
         }
 
 
         _dragPosition = null;
         _dragPosition = null;

+ 1 - 1
Terminal.Gui/ViewBase/Adornment/Border.cs

@@ -111,7 +111,6 @@ public partial class Border : Adornment
         if (App is { })
         if (App is { })
         {
         {
             App.Mouse.GrabbingMouse += Application_GrabbingMouse;
             App.Mouse.GrabbingMouse += Application_GrabbingMouse;
-            App.Mouse.UnGrabbingMouse += Application_UnGrabbingMouse;
         }
         }
 
 
         if (Parent is null)
         if (Parent is null)
@@ -215,6 +214,7 @@ public partial class Border : Adornment
             // TODO: all this.
             // TODO: all this.
             return Parent?.SuperView?.BorderStyle ?? LineStyle.None;
             return Parent?.SuperView?.BorderStyle ?? LineStyle.None;
         }
         }
+        // BUGBUG: Setting LineStyle should SetNeedsDraw
         set => _lineStyle = value;
         set => _lineStyle = value;
     }
     }
 
 

+ 140 - 40
Terminal.Gui/ViewBase/View.Drawing.cs

@@ -6,7 +6,7 @@ namespace Terminal.Gui.ViewBase;
 public partial class View // Drawing APIs
 public partial class View // Drawing APIs
 {
 {
     /// <summary>
     /// <summary>
-    ///     Draws a set of views.
+    ///     Draws a set of peer views (views that share the same SuperView).
     /// </summary>
     /// </summary>
     /// <param name="views">The peer views to draw.</param>
     /// <param name="views">The peer views to draw.</param>
     /// <param name="force">If <see langword="true"/>, <see cref="View.SetNeedsDraw()"/> will be called on each view to force it to be drawn.</param>
     /// <param name="force">If <see langword="true"/>, <see cref="View.SetNeedsDraw()"/> will be called on each view to force it to be drawn.</param>
@@ -39,8 +39,8 @@ public partial class View // Drawing APIs
 
 
         // After all peer views have been drawn and cleared, we can now clear the SuperView's SubViewNeedsDraw flag.
         // After all peer views have been drawn and cleared, we can now clear the SuperView's SubViewNeedsDraw flag.
         // ClearNeedsDraw() does not clear SuperView.SubViewNeedsDraw (by design, to avoid premature clearing
         // ClearNeedsDraw() does not clear SuperView.SubViewNeedsDraw (by design, to avoid premature clearing
-        // when siblings still need drawing), so we must do it here after ALL peers are processed.
-        // We only clear the flag if ALL the SuperView's subviews no longer need drawing.
+        // when peer subviews still need drawing), so we must do it here after ALL peers are processed.
+        // We only clear the flag if ALL the SuperView's SubViews no longer need drawing.
         View? lastSuperView = null;
         View? lastSuperView = null;
         foreach (View view in viewsArray)
         foreach (View view in viewsArray)
         {
         {
@@ -85,8 +85,8 @@ public partial class View // Drawing APIs
         if (NeedsDraw || SubViewNeedsDraw)
         if (NeedsDraw || SubViewNeedsDraw)
         {
         {
             // ------------------------------------
             // ------------------------------------
-            // Draw the Border and Padding.
-            // Note Margin with a Shadow is special-cased and drawn in a separate pass to support
+            // Draw the Border and Padding Adornments.
+            // Note: Margin with a Shadow is special-cased and drawn in a separate pass to support
             // transparent shadows.
             // transparent shadows.
             DoDrawAdornments (originalClip);
             DoDrawAdornments (originalClip);
             SetClip (originalClip);
             SetClip (originalClip);
@@ -106,7 +106,7 @@ public partial class View // Drawing APIs
             DoClearViewport (context);
             DoClearViewport (context);
 
 
             // ------------------------------------
             // ------------------------------------
-            // Draw the subviews first (order matters: SubViews, Text, Content)
+            // Draw the SubViews first (order matters: SubViews, Text, Content)
             if (SubViewNeedsDraw)
             if (SubViewNeedsDraw)
             {
             {
                 DoDrawSubViews (context);
                 DoDrawSubViews (context);
@@ -127,11 +127,11 @@ public partial class View // Drawing APIs
             // because they may draw outside the viewport.
             // because they may draw outside the viewport.
             SetClip (originalClip);
             SetClip (originalClip);
             originalClip = AddFrameToClip ();
             originalClip = AddFrameToClip ();
-            DoRenderLineCanvas ();
+            DoRenderLineCanvas (context);
 
 
             // ------------------------------------
             // ------------------------------------
-            // Re-draw the border and padding subviews
-            // HACK: This is a hack to ensure that the border and padding subviews are drawn after the line canvas.
+            // Re-draw the Border and Padding Adornment SubViews
+            // HACK: This is a hack to ensure that the Border and Padding Adornment SubViews are drawn after the line canvas.
             DoDrawAdornmentsSubViews ();
             DoDrawAdornmentsSubViews ();
 
 
             // ------------------------------------
             // ------------------------------------
@@ -170,15 +170,20 @@ public partial class View // Drawing APIs
         SetClip (originalClip);
         SetClip (originalClip);
 
 
         // ------------------------------------
         // ------------------------------------
-        // We're done drawing - The Clip is reset to what it was before we started.
+        // We're done drawing - The Clip is reset to what it was before we started
+        // But the context contains the region that was drawn by this view
         DoDrawComplete (context);
         DoDrawComplete (context);
+
+        // When DoDrawComplete returns, Driver.Clip has been updated to exclude this view's area.
+        // The next view drawn (earlier in Z-order, typically a peer view or the SuperView) will see
+        // a clip with "holes" where this view (and any SubViews drawn before it) are located.
     }
     }
 
 
     #region DrawAdornments
     #region DrawAdornments
 
 
     private void DoDrawAdornmentsSubViews ()
     private void DoDrawAdornmentsSubViews ()
     {
     {
-        // NOTE: We do not support subviews of Margin?
+        // NOTE: We do not support SubViews of Margin
 
 
         if (Border?.SubViews is { } && Border.Thickness != Thickness.Empty && Border.NeedsDraw)
         if (Border?.SubViews is { } && Border.Thickness != Thickness.Empty && Border.NeedsDraw)
         {
         {
@@ -302,7 +307,7 @@ public partial class View // Drawing APIs
     /// <summary>
     /// <summary>
     ///     Called when the View's Adornments are to be drawn. Prepares <see cref="View.LineCanvas"/>. If
     ///     Called when the View's Adornments are to be drawn. Prepares <see cref="View.LineCanvas"/>. If
     ///     <see cref="SuperViewRendersLineCanvas"/> is true, only the
     ///     <see cref="SuperViewRendersLineCanvas"/> is true, only the
-    ///     <see cref="LineCanvas"/> of this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
+    ///     <see cref="LineCanvas"/> of this view's SubViews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
     ///     false (the default), this method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
     ///     false (the default), this method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
     /// </summary>
     /// </summary>
     /// <returns><see langword="true"/> to stop further drawing of the Adornments.</returns>
     /// <returns><see langword="true"/> to stop further drawing of the Adornments.</returns>
@@ -481,7 +486,7 @@ public partial class View // Drawing APIs
                                  Rectangle.Empty);
                                  Rectangle.Empty);
         }
         }
 
 
-        // We assume that the text has been drawn over the entire area; ensure that the subviews are redrawn.
+        // We assume that the text has been drawn over the entire area; ensure that the SubViews are redrawn.
         SetSubViewNeedsDrawDownHierarchy ();
         SetSubViewNeedsDrawDownHierarchy ();
     }
     }
 
 
@@ -571,7 +576,7 @@ public partial class View // Drawing APIs
     ///         such as <see cref="View.AddRune(int, int, Rune)"/>, <see cref="View.AddStr(string)"/>, and <see cref="View.FillRect(Rectangle, Rune)"/>.
     ///         such as <see cref="View.AddRune(int, int, Rune)"/>, <see cref="View.AddStr(string)"/>, and <see cref="View.FillRect(Rectangle, Rune)"/>.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
-    ///         The event is invoked after <see cref="ClearingViewport"/> and <see cref="Text"/> have been drawn, but before any <see cref="SubViews"/> are drawn.
+    ///         The event is invoked after <see cref="ClearingViewport"/> and <see cref="Text"/> have been drawn, but after <see cref="SubViews"/> have been drawn.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         <b>Transparency Support:</b> If the View has <see cref="ViewportSettings"/> with <see cref="ViewportSettingsFlags.Transparent"/>
     ///         <b>Transparency Support:</b> If the View has <see cref="ViewportSettings"/> with <see cref="ViewportSettingsFlags.Transparent"/>
@@ -650,7 +655,8 @@ public partial class View // Drawing APIs
             return;
             return;
         }
         }
 
 
-        // Draw the subviews in reverse order to leverage clipping.
+        // Draw the SubViews in reverse Z-order to leverage clipping.
+        // SubViews earlier in the collection are drawn last (on top).
         foreach (View view in InternalSubViews.Snapshot ().Where (v => v.Visible).Reverse ())
         foreach (View view in InternalSubViews.Snapshot ().Where (v => v.Visible).Reverse ())
         {
         {
             // TODO: HACK - This forcing of SetNeedsDraw with SuperViewRendersLineCanvas enables auto line join to work, but is brute force.
             // TODO: HACK - This forcing of SetNeedsDraw with SuperViewRendersLineCanvas enables auto line join to work, but is brute force.
@@ -672,8 +678,9 @@ public partial class View // Drawing APIs
 
 
     #region DrawLineCanvas
     #region DrawLineCanvas
 
 
-    private void DoRenderLineCanvas ()
+    private void DoRenderLineCanvas (DrawContext? context)
     {
     {
+        // TODO: Add context to OnRenderingLineCanvas
         if (!NeedsDraw || OnRenderingLineCanvas ())
         if (!NeedsDraw || OnRenderingLineCanvas ())
         {
         {
             return;
             return;
@@ -681,7 +688,7 @@ public partial class View // Drawing APIs
 
 
         // TODO: Add event
         // TODO: Add event
 
 
-        RenderLineCanvas ();
+        RenderLineCanvas (context);
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -690,26 +697,26 @@ public partial class View // Drawing APIs
     /// <returns><see langword="true"/> to stop further drawing of <see cref="LineCanvas"/>.</returns>
     /// <returns><see langword="true"/> to stop further drawing of <see cref="LineCanvas"/>.</returns>
     protected virtual bool OnRenderingLineCanvas () { return false; }
     protected virtual bool OnRenderingLineCanvas () { return false; }
 
 
-    /// <summary>The canvas that any line drawing that is to be shared by subviews of this view should add lines to.</summary>
+    /// <summary>The canvas that any line drawing that is to be shared by SubViews of this view should add lines to.</summary>
     /// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
     /// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
     public LineCanvas LineCanvas { get; } = new ();
     public LineCanvas LineCanvas { get; } = new ();
 
 
     /// <summary>
     /// <summary>
-    ///     Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for rendering any
-    ///     lines. If <see langword="true"/> the rendering of any borders drawn by this Frame will be done by its parent's
+    ///     Gets or sets whether this View will use its SuperView's <see cref="LineCanvas"/> for rendering any
+    ///     lines. If <see langword="true"/> the rendering of any borders drawn by this view will be done by its
     ///     SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawingAdornments"/> method will
     ///     SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawingAdornments"/> method will
-    ///     be
-    ///     called to render the borders.
+    ///     be called to render the borders.
     /// </summary>
     /// </summary>
     public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
     public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
 
 
     /// <summary>
     /// <summary>
     ///     Causes the contents of <see cref="LineCanvas"/> to be drawn.
     ///     Causes the contents of <see cref="LineCanvas"/> to be drawn.
     ///     If <see cref="SuperViewRendersLineCanvas"/> is true, only the
     ///     If <see cref="SuperViewRendersLineCanvas"/> is true, only the
-    ///     <see cref="LineCanvas"/> of this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
+    ///     <see cref="LineCanvas"/> of this view's SubViews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
     ///     false (the default), this method will cause the <see cref="LineCanvas"/> to be rendered.
     ///     false (the default), this method will cause the <see cref="LineCanvas"/> to be rendered.
     /// </summary>
     /// </summary>
-    public void RenderLineCanvas ()
+    /// <param name="context"></param>
+    public void RenderLineCanvas (DrawContext? context)
     {
     {
         if (Driver is null)
         if (Driver is null)
         {
         {
@@ -728,6 +735,9 @@ public partial class View // Drawing APIs
 
 
                     // TODO: #2616 - Support combining sequences that don't normalize
                     // TODO: #2616 - Support combining sequences that don't normalize
                     AddStr (p.Value.Value.Grapheme);
                     AddStr (p.Value.Value.Grapheme);
+
+                    // Add each drawn cell to the context
+                    //context?.AddDrawnRectangle (new Rectangle (p.Key, new (1, 1)) );
                 }
                 }
             }
             }
 
 
@@ -739,63 +749,153 @@ public partial class View // Drawing APIs
 
 
     #region DrawComplete
     #region DrawComplete
 
 
+    /// <summary>
+    ///     Called at the end of <see cref="Draw(DrawContext)"/> to finalize drawing and update the clip region.
+    /// </summary>
+    /// <param name="context">
+    ///     The <see cref="DrawContext"/> tracking what regions were drawn by this view and its subviews.
+    ///     May be <see langword="null"/> if not tracking drawn regions.
+    /// </param>
     private void DoDrawComplete (DrawContext? context)
     private void DoDrawComplete (DrawContext? context)
     {
     {
+        // Phase 1: Notify that drawing is complete
+        // Raise virtual method first, then event. This allows subclasses to override behavior
+        // before subscribers see the event.
         OnDrawComplete (context);
         OnDrawComplete (context);
         DrawComplete?.Invoke (this, new (Viewport, Viewport, context));
         DrawComplete?.Invoke (this, new (Viewport, Viewport, context));
 
 
-        // Now, update the clip to exclude this view (not including Margin)
+        // Phase 2: Update Driver.Clip to exclude this view's drawn area
+        // This prevents views "behind" this one (earlier in draw order/Z-order) from drawing over it.
+        // Adornments (Margin, Border, Padding) are handled by their Adornment.Parent view and don't exclude themselves.
         if (this is not Adornment)
         if (this is not Adornment)
         {
         {
             if (ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent))
             if (ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent))
             {
             {
-                // context!.DrawnRegion is the region that was drawn by this view. It may include regions outside
-                // the Viewport. We need to clip it to the Viewport.
+                // Transparent View Path:
+                // Only exclude the regions that were actually drawn, allowing views beneath
+                // to show through in areas where nothing was drawn.
+
+                // The context.DrawnRegion may include areas outside the Viewport (e.g., if content
+                // was drawn with ViewportSettingsFlags.AllowContentOutsideViewport). We need to clip
+                // it to the Viewport bounds to prevent excluding areas that aren't visible.
                 context!.ClipDrawnRegion (ViewportToScreen (Viewport));
                 context!.ClipDrawnRegion (ViewportToScreen (Viewport));
 
 
-                // Exclude the drawn region from the clip
+                // Exclude the actually-drawn region from Driver.Clip
                 ExcludeFromClip (context.GetDrawnRegion ());
                 ExcludeFromClip (context.GetDrawnRegion ());
 
 
-                // Exclude the Border and Padding from the clip
+                // Border and Padding are always opaque (they draw lines/fills), so exclude them too
                 ExcludeFromClip (Border?.Thickness.AsRegion (Border.FrameToScreen ()));
                 ExcludeFromClip (Border?.Thickness.AsRegion (Border.FrameToScreen ()));
                 ExcludeFromClip (Padding?.Thickness.AsRegion (Padding.FrameToScreen ()));
                 ExcludeFromClip (Padding?.Thickness.AsRegion (Padding.FrameToScreen ()));
-
-                // QUESTION: This makes it so that no nesting of transparent views is possible, but is more correct?
-                context = new DrawContext ();
             }
             }
             else
             else
             {
             {
-                // Exclude this view (not including Margin) from the Clip
+                // Opaque View Path (default):
+                // Exclude the entire view area from Driver.Clip. This is the typical case where
+                // the view is considered fully opaque.
+
+                // Start with the Frame in screen coordinates
                 Rectangle borderFrame = FrameToScreen ();
                 Rectangle borderFrame = FrameToScreen ();
 
 
+                // If there's a Border, use its frame instead (includes the border thickness)
                 if (Border is { })
                 if (Border is { })
                 {
                 {
                     borderFrame = Border.FrameToScreen ();
                     borderFrame = Border.FrameToScreen ();
                 }
                 }
 
 
-                // In the non-transparent (typical case), we want to exclude the entire view area (borderFrame) from the clip
+                // Exclude this view's entire area (Border inward, but not Margin) from the clip.
+                // This prevents any view drawn after this one from drawing in this area.
                 ExcludeFromClip (borderFrame);
                 ExcludeFromClip (borderFrame);
 
 
-                // Update context.DrawnRegion to include the entire view (borderFrame), but clipped to our SuperView's viewport
-                // This enables the SuperView to know what was drawn by this view.
+                // Update the DrawContext to track that we drew this entire rectangle.
+                // This allows our SuperView (if any) to know what area we occupied,
+                // which is important for transparency calculations at higher levels.
                 context?.AddDrawnRectangle (borderFrame);
                 context?.AddDrawnRectangle (borderFrame);
             }
             }
         }
         }
 
 
-        // TODO: Determine if we need another event that conveys the FINAL DrawContext
+        // When this method returns, Driver.Clip has been updated to exclude this view's area.
+        // The next view drawn (earlier in Z-order, typically a peer view or the SuperView) will see
+        // a clip with "holes" where this view (and any SubViews drawn before it) are located.
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Called when the View is completed drawing.
+    ///     Called when the View has completed drawing and is about to update the clip region.
     /// </summary>
     /// </summary>
+    /// <param name="context">
+    ///     The <see cref="DrawContext"/> containing the regions that were drawn by this view and its subviews.
+    ///     May be <see langword="null"/> if not tracking drawn regions.
+    /// </param>
     /// <remarks>
     /// <remarks>
-    ///     The <paramref name="context"/> parameter provides the drawn region of the View.
+    ///     <para>
+    ///         This method is called at the very end of <see cref="Draw(DrawContext)"/>, after all drawing
+    ///         (adornments, content, text, subviews, line canvas) has completed but before the view's area
+    ///         is excluded from <see cref="IDriver.Clip"/>.
+    ///     </para>
+    ///     <para>
+    ///         Use this method to:
+    ///     </para>
+    ///     <list type="bullet">
+    ///         <item>
+    ///             <description>Perform any final drawing operations that need to happen after SubViews are drawn</description>
+    ///         </item>
+    ///         <item>
+    ///             <description>Inspect what was drawn via the <paramref name="context"/> parameter</description>
+    ///         </item>
+    ///         <item>
+    ///             <description>Add additional regions to the <paramref name="context"/> if needed</description>
+    ///         </item>
+    ///     </list>
+    ///     <para>
+    ///         <b>Important:</b> At this point, <see cref="IDriver.Clip"/> has been restored to the state
+    ///         it was in when <see cref="Draw(DrawContext)"/> began. After this method returns, the view's
+    ///         area will be excluded from the clip (see <see cref="DoDrawComplete"/> for details).
+    ///     </para>
+    ///     <para>
+    ///         <b>Transparency Support:</b> If <see cref="ViewportSettings"/> includes
+    ///         <see cref="ViewportSettingsFlags.Transparent"/>, the <paramref name="context"/> parameter
+    ///         contains the actual regions that were drawn. You can inspect this to see what areas
+    ///         will be excluded from the clip, and optionally add more regions if needed.
+    ///     </para>
     /// </remarks>
     /// </remarks>
+    /// <seealso cref="DrawComplete"/>
+    /// <seealso cref="Draw(DrawContext)"/>
+    /// <seealso cref="DoDrawComplete"/>
     protected virtual void OnDrawComplete (DrawContext? context) { }
     protected virtual void OnDrawComplete (DrawContext? context) { }
 
 
-    /// <summary>Raised when the View is completed drawing.</summary>
+    /// <summary>Raised when the View has completed drawing and is about to update the clip region.</summary>
     /// <remarks>
     /// <remarks>
+    ///     <para>
+    ///         This event is raised at the very end of <see cref="Draw(DrawContext)"/>, after all drawing
+    ///         operations have completed but before the view's area is excluded from <see cref="IDriver.Clip"/>.
+    ///     </para>
+    ///     <para>
+    ///         The <see cref="DrawEventArgs.DrawContext"/> property provides information about what regions
+    ///         were drawn by this view and its subviews. This is particularly useful for views with
+    ///         <see cref="ViewportSettingsFlags.Transparent"/> enabled, as it shows exactly which areas
+    ///         will be excluded from the clip.
+    ///     </para>
+    ///     <para>
+    ///         Use this event to:
+    ///     </para>
+    ///     <list type="bullet">
+    ///         <item>
+    ///             <description>Perform any final drawing operations</description>
+    ///         </item>
+    ///         <item>
+    ///             <description>Inspect what was drawn</description>
+    ///         </item>
+    ///         <item>
+    ///             <description>Track drawing statistics or metrics</description>
+    ///         </item>
+    ///     </list>
+    ///     <para>
+    ///         <b>Note:</b> This event fires <i>after</i> <see cref="OnDrawComplete(DrawContext)"/>. If you need
+    ///         to override the behavior, prefer overriding the virtual method in a subclass rather than
+    ///         subscribing to this event.
+    ///     </para>
     /// </remarks>
     /// </remarks>
+    /// <seealso cref="OnDrawComplete(DrawContext)"/>
+    /// <seealso cref="Draw(DrawContext)"/>
     public event EventHandler<DrawEventArgs>? DrawComplete;
     public event EventHandler<DrawEventArgs>? DrawComplete;
 
 
     #endregion DrawComplete
     #endregion DrawComplete

+ 10 - 89
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.
     ///         A view must be both enabled and visible to receive mouse events.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
-    ///         This method raises <see cref="RaiseMouseEvent"/>/<see cref="MouseEvent"/>; if not handled, and one of the
-    ///         mouse buttons was clicked, the <see cref="RaiseMouseClickEvent"/>/<see cref="MouseClick"/> event will be raised
-    ///     </para>
-    ///     <para>
     ///         If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, and the user presses and holds the
     ///         If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, and the user presses and holds the
     ///         mouse button, <see cref="NewMouseEvent"/> will be repeatedly called with the same <see cref="MouseFlags"/> for
     ///         mouse button, <see cref="NewMouseEvent"/> will be repeatedly called with the same <see cref="MouseFlags"/> for
     ///         as long as the mouse button remains pressed.
     ///         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)
         if (mouseEvent.IsWheel)
         {
         {
             return RaiseMouseWheelEvent (mouseEvent);
             return RaiseMouseWheelEvent (mouseEvent);
@@ -333,6 +322,11 @@ public partial class View // Mouse APIs
 
 
         MouseEvent?.Invoke (this, mouseEvent);
         MouseEvent?.Invoke (this, mouseEvent);
 
 
+        if (!mouseEvent.Handled)
+        {
+            mouseEvent.Handled = InvokeCommandsBoundToMouse (mouseEvent) == true;
+        }
+
         return mouseEvent.Handled;
         return mouseEvent.Handled;
     }
     }
 
 
@@ -356,7 +350,7 @@ public partial class View // Mouse APIs
 
 
     #endregion Low Level Mouse Events
     #endregion Low Level Mouse Events
 
 
-    #region Mouse Pressed Events
+    #region WhenGrabbed Handlers
 
 
     /// <summary>
     /// <summary>
     ///     INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the released event
     ///     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;
         return false;
     }
     }
 
 
-    #endregion Mouse Pressed Events
-
-    #region Mouse Click Events
-
-    /// <summary>Raises the <see cref="OnMouseClick"/>/<see cref="MouseClick"/> event.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         Called when the mouse is either clicked or double-clicked.
-    ///     </para>
-    ///     <para>
-    ///         If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, will be invoked on every mouse event
-    ///         where
-    ///         the mouse button is pressed.
-    ///     </para>
-    /// </remarks>
-    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
-    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;
-    }
-
-    /// <summary>
-    ///     Called when a mouse click occurs. Check <see cref="MouseEventArgs.Flags"/> to see which button was clicked.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         Called when the mouse is either clicked or double-clicked.
-    ///     </para>
-    ///     <para>
-    ///         If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, will be called on every mouse event
-    ///         where
-    ///         the mouse button is pressed.
-    ///     </para>
-    /// </remarks>
-    /// <param name="args"></param>
-    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
-    protected virtual bool OnMouseClick (MouseEventArgs args) { return false; }
-
-    /// <summary>Raised when a mouse click occurs.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         Raised when the mouse is either clicked or double-clicked.
-    ///     </para>
-    ///     <para>
-    ///         If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, will be raised on every mouse event
-    ///         where
-    ///         the mouse button is pressed.
-    ///     </para>
-    /// </remarks>
-    public event EventHandler<MouseEventArgs>? MouseClick;
 
 
     /// <summary>
     /// <summary>
     ///     INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the click event
     ///     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 mouse is still in bounds, generate a click
             if (!WantMousePositionReports && Viewport.Contains (mouseEvent.Position))
             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;
             return mouseEvent.Handled = true;
@@ -562,7 +482,8 @@ public partial class View // Mouse APIs
         return false;
         return false;
     }
     }
 
 
-    #endregion Mouse Clicked Events
+
+    #endregion WhenGrabbed Handlers
 
 
     #region Mouse Wheel Events
     #region Mouse Wheel Events
 
 

+ 8 - 11
Terminal.Gui/Views/Button.cs

@@ -75,8 +75,15 @@ public class Button : View, IDesignable
         KeyBindings.Remove (Key.Enter);
         KeyBindings.Remove (Key.Enter);
         KeyBindings.Add (Key.Enter, Command.HotKey);
         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;
         TitleChanged += Button_TitleChanged;
-        MouseClick += Button_MouseClick;
 
 
         base.ShadowStyle = DefaultShadow;
         base.ShadowStyle = DefaultShadow;
         HighlightStates = DefaultHighlightStates;
         HighlightStates = DefaultHighlightStates;
@@ -115,16 +122,6 @@ public class Button : View, IDesignable
 
 
         return false;
         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<KeyBinding> (Command.HotKey, new KeyBinding ([Command.HotKey], this, data: null)) == true;
-    }
 
 
     private void Button_TitleChanged (object sender, EventArgs<string> e)
     private void Button_TitleChanged (object sender, EventArgs<string> e)
     {
     {

+ 14 - 8
Terminal.Gui/Views/FileDialogs/FileDialog.cs

@@ -195,7 +195,7 @@ public class FileDialog : Dialog, IDesignable
         };
         };
         _tableView.CollectionNavigator = new FileDialogCollectionNavigator (this, _tableView);
         _tableView.CollectionNavigator = new FileDialogCollectionNavigator (this, _tableView);
         _tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select);
         _tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select);
-        _tableView.MouseClick += OnTableViewMouseClick;
+        _tableView.Selecting += OnTableViewSelecting;
         Style.TableStyle = _tableView.Style;
         Style.TableStyle = _tableView.Style;
 
 
         ColumnStyle nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0);
         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<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
+        {
+            return;
+        }
+
+        Point? clickedCell = _tableView.ScreenToCell (mouseArgs.Position.X, mouseArgs.Position.Y, out int? clickedCol);
 
 
         if (clickedCol is { })
         if (clickedCol is { })
         {
         {
-            if (e.Flags.HasFlag (MouseFlags.Button1Clicked))
+            if (mouseArgs.Flags.HasFlag (MouseFlags.Button1Clicked))
             {
             {
                 // left click in a header
                 // left click in a header
                 SortColumn (clickedCol.Value);
                 SortColumn (clickedCol.Value);
             }
             }
-            else if (e.Flags.HasFlag (MouseFlags.Button3Clicked))
+            else if (mouseArgs.Flags.HasFlag (MouseFlags.Button3Clicked))
             {
             {
                 // right click in a header
                 // right click in a header
-                ShowHeaderContextMenu (clickedCol.Value, e);
+                ShowHeaderContextMenu (clickedCol.Value, mouseArgs);
             }
             }
         }
         }
         else
         else
         {
         {
-            if (clickedCell is { } && e.Flags.HasFlag (MouseFlags.Button3Clicked))
+            if (clickedCell is { } && mouseArgs.Flags.HasFlag (MouseFlags.Button3Clicked))
             {
             {
                 // right click in rest of table
                 // right click in rest of table
-                ShowCellContextMenu (clickedCell, e);
+                ShowCellContextMenu (clickedCell, mouseArgs);
             }
             }
         }
         }
     }
     }

+ 5 - 3
Terminal.Gui/Views/GraphView/LegendAnnotation.cs

@@ -1,5 +1,5 @@
 #nullable disable
 #nullable disable
-namespace Terminal.Gui.Views;
+namespace Terminal.Gui.Views;
 
 
 /// <summary>
 /// <summary>
 ///     Used by <see cref="GraphView"/> to render smbol definitions in a graph, e.g. colors and their meanings
 ///     Used by <see cref="GraphView"/> to render smbol definitions in a graph, e.g. colors and their meanings
@@ -46,14 +46,16 @@ public class LegendAnnotation : View, IAnnotation
         if (!IsInitialized)
         if (!IsInitialized)
         {
         {
             // BUGBUG: We should be getting a visual role here?
             // BUGBUG: We should be getting a visual role here?
-            SetScheme (new() { Normal = Application.Driver?.GetAttribute () ?? Attribute.Default });
+            SetScheme (new () { Normal = Application.Driver?.GetAttribute () ?? Attribute.Default });
             graph.Add (this);
             graph.Add (this);
         }
         }
 
 
         if (BorderStyle != LineStyle.None)
         if (BorderStyle != LineStyle.None)
         {
         {
+            // BUGBUG: View code should never call Draw directly. This
+            // BUGBUG: needs to be refactored to decouple.
             DrawAdornments ();
             DrawAdornments ();
-            RenderLineCanvas ();
+            RenderLineCanvas (null);
         }
         }
 
 
         var linesDrawn = 0;
         var linesDrawn = 0;

+ 12 - 9
Terminal.Gui/Views/Label.cs

@@ -27,15 +27,6 @@ public class Label : View, IDesignable
         AddCommand (Command.HotKey, InvokeHotKeyOnNextPeer!);
         AddCommand (Command.HotKey, InvokeHotKeyOnNextPeer!);
 
 
         TitleChanged += Label_TitleChanged;
         TitleChanged += Label_TitleChanged;
-        MouseClick += Label_MouseClick;
-    }
-
-    private void Label_MouseClick (object? sender, MouseEventArgs e)
-    {
-        if (!CanFocus)
-        {
-            e.Handled = InvokeCommand<KeyBinding> (Command.HotKey, new ([Command.HotKey], this, this)) == true;
-        }
     }
     }
 
 
     private void Label_TitleChanged (object? sender, EventArgs<string> e)
     private void Label_TitleChanged (object? sender, EventArgs<string> e)
@@ -89,6 +80,18 @@ public class Label : View, IDesignable
         return false;
         return false;
     }
     }
 
 
+    /// <inheritdoc/>
+    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);
+    }
+
     /// <inheritdoc/>
     /// <inheritdoc/>
     bool IDesignable.EnableForDesign ()
     bool IDesignable.EnableForDesign ()
     {
     {

+ 1 - 1
Terminal.Gui/ViewBase/Runnable/Runnable.cs → Terminal.Gui/Views/Runnable/Runnable.cs

@@ -1,4 +1,4 @@
-namespace Terminal.Gui.ViewBase;
+namespace Terminal.Gui.Views;
 
 
 /// <summary>
 /// <summary>
 ///     Base implementation of <see cref="IRunnable"/> for views that can be run as blocking sessions without returning a result.
 ///     Base implementation of <see cref="IRunnable"/> for views that can be run as blocking sessions without returning a result.

+ 1 - 1
Terminal.Gui/ViewBase/Runnable/RunnableTResult.cs → Terminal.Gui/Views/Runnable/RunnableTResult.cs

@@ -1,4 +1,4 @@
-namespace Terminal.Gui.ViewBase;
+namespace Terminal.Gui.Views;
 
 
 /// <summary>
 /// <summary>
 ///     Base implementation of <see cref="IRunnable{TResult}"/> for views that can be run as blocking sessions.
 ///     Base implementation of <see cref="IRunnable{TResult}"/> for views that can be run as blocking sessions.

+ 1 - 1
Terminal.Gui/ViewBase/Runnable/RunnableWrapper.cs → Terminal.Gui/Views/Runnable/RunnableWrapper.cs

@@ -1,4 +1,4 @@
-namespace Terminal.Gui.ViewBase;
+namespace Terminal.Gui.Views;
 
 
 /// <summary>
 /// <summary>
 ///     Wraps any <see cref="View"/> to make it runnable with a typed result, similar to how
 ///     Wraps any <see cref="View"/> to make it runnable with a typed result, similar to how

+ 1 - 1
Terminal.Gui/ViewBase/Runnable/ViewRunnableExtensions.cs → Terminal.Gui/Views/Runnable/ViewRunnableExtensions.cs

@@ -1,4 +1,4 @@
-namespace Terminal.Gui.ViewBase;
+namespace Terminal.Gui.Views;
 
 
 /// <summary>
 /// <summary>
 ///     Extension methods for making any <see cref="View"/> runnable with typed results.
 ///     Extension methods for making any <see cref="View"/> runnable with typed results.

+ 11 - 5
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.
     // TODO: Change this to work OnMouseEvent with continuouse press and grab so it's continous.
     /// <inheritdoc/>
     /// <inheritdoc/>
-    protected override bool OnMouseClick (MouseEventArgs args)
+    protected override bool OnSelecting (CommandEventArgs args)
     {
     {
+        // Only handle mouse clicks
+        if (args.Context is not CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
+        {
+            return base.OnSelecting (args);
+        }
+
         // Check if the mouse click is a single click
         // Check if the mouse click is a single click
-        if (!args.IsSingleClicked)
+        if (!mouseArgs.IsSingleClicked)
         {
         {
-            return false;
+            return base.OnSelecting (args);
         }
         }
 
 
         int sliderCenter;
         int sliderCenter;
@@ -531,12 +537,12 @@ public class ScrollBar : View, IOrientation, IDesignable
         if (Orientation == Orientation.Vertical)
         if (Orientation == Orientation.Vertical)
         {
         {
             sliderCenter = 1 + _slider.Frame.Y + _slider.Frame.Height / 2;
             sliderCenter = 1 + _slider.Frame.Y + _slider.Frame.Height / 2;
-            distanceFromCenter = args.Position.Y - sliderCenter;
+            distanceFromCenter = mouseArgs.Position.Y - sliderCenter;
         }
         }
         else
         else
         {
         {
             sliderCenter = 1 + _slider.Frame.X + _slider.Frame.Width / 2;
             sliderCenter = 1 + _slider.Frame.X + _slider.Frame.Width / 2;
-            distanceFromCenter = args.Position.X - sliderCenter;
+            distanceFromCenter = mouseArgs.Position.X - sliderCenter;
         }
         }
 
 
 #if PROPORTIONAL_SCROLL_JUMP
 #if PROPORTIONAL_SCROLL_JUMP

+ 8 - 2
Terminal.Gui/Views/TabView/TabRow.cs

@@ -24,7 +24,10 @@ internal class TabRow : View
             Visible = false,
             Visible = false,
             Text = Glyphs.RightArrow.ToString ()
             Text = Glyphs.RightArrow.ToString ()
         };
         };
-        _rightScrollIndicator.MouseClick += _host.Tab_MouseClick!;
+        _rightScrollIndicator.Selecting += (s, e) =>
+        {
+            _host.Tab_Selecting (s, e);
+        };
 
 
         _leftScrollIndicator = new View
         _leftScrollIndicator = new View
         {
         {
@@ -34,7 +37,10 @@ internal class TabRow : View
             Visible = false,
             Visible = false,
             Text = Glyphs.LeftArrow.ToString ()
             Text = Glyphs.LeftArrow.ToString ()
         };
         };
-        _leftScrollIndicator.MouseClick += _host.Tab_MouseClick!;
+        _leftScrollIndicator.Selecting += (s, e) =>
+        {
+            _host.Tab_Selecting (s, e);
+        };
 
 
         Add (_rightScrollIndicator, _leftScrollIndicator);
         Add (_rightScrollIndicator, _leftScrollIndicator);
     }
     }

+ 13 - 10
Terminal.Gui/Views/TabView/TabView.cs

@@ -563,8 +563,8 @@ public class TabView : View
             if (maxWidth == 0)
             if (maxWidth == 0)
             {
             {
                 tab.Visible = true;
                 tab.Visible = true;
-                tab.MouseClick += Tab_MouseClick!;
-                tab.Border!.MouseClick += Tab_MouseClick!;
+                tab.Selecting += Tab_Selecting!;
+                tab.Border!.Selecting += Tab_Selecting!;
 
 
                 yield return tab;
                 yield return tab;
 
 
@@ -594,8 +594,8 @@ public class TabView : View
 
 
             // there is enough space!
             // there is enough space!
             tab.Visible = true;
             tab.Visible = true;
-            tab.MouseClick += Tab_MouseClick!;
-            tab.Border!.MouseClick += Tab_MouseClick!;
+            tab.Selecting += Tab_Selecting!;
+            tab.Border!.Selecting += Tab_Selecting!;
 
 
             yield return tab;
             yield return tab;
 
 
@@ -636,9 +636,12 @@ public class TabView : View
         return Style.ShowTopLine ? 3 : 2;
         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<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
+        {
+            e.Handled = _tabsBar.NewMouseEvent (mouseArgs) == true;
+        }
     }
     }
 
 
     private void UnSetCurrentTabs ()
     private void UnSetCurrentTabs ()
@@ -652,8 +655,8 @@ public class TabView : View
 
 
                 if (tab.Visible)
                 if (tab.Visible)
                 {
                 {
-                    tab.MouseClick -= Tab_MouseClick!;
-                    tab.Border!.MouseClick -= Tab_MouseClick!;
+                    tab.Selecting -= Tab_Selecting!;
+                    tab.Border!.Selecting -= Tab_Selecting!;
                     tab.Visible = false;
                     tab.Visible = false;
                 }
                 }
             }
             }
@@ -662,8 +665,8 @@ public class TabView : View
         {
         {
             foreach (Tab tabToRender in _tabLocations)
             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;
                 tabToRender.Visible = false;
             }
             }
 
 

+ 12 - 4
Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs

@@ -30,7 +30,7 @@ public abstract class CheckBoxTableSourceWrapperBase : ITableSource
 
 
         tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select);
         tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select);
 
 
-        tableView.MouseClick += TableView_MouseClick;
+        tableView.Selecting += TableView_Selecting;
         tableView.CellToggled += TableView_CellToggled;
         tableView.CellToggled += TableView_CellToggled;
     }
     }
 
 
@@ -152,15 +152,22 @@ public abstract class CheckBoxTableSourceWrapperBase : ITableSource
         tableView.SetNeedsDraw ();
         tableView.SetNeedsDraw ();
     }
     }
 
 
-    private void TableView_MouseClick (object sender, MouseEventArgs e)
+#nullable enable
+    private void TableView_Selecting (object? sender, CommandEventArgs e)
     {
     {
+        // Only handle mouse clicks, not keyboard selections
+        if (e.Context is not CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
+        {
+            return;
+        }
+
         // we only care about clicks (not movements)
         // we only care about clicks (not movements)
-        if (!e.Flags.HasFlag (MouseFlags.Button1Clicked))
+        if (!mouseArgs.Flags.HasFlag (MouseFlags.Button1Clicked))
         {
         {
             return;
             return;
         }
         }
 
 
-        Point? hit = tableView.ScreenToCell (e.Position.X, e.Position.Y, out int? headerIfAny);
+        Point? hit = tableView.ScreenToCell (mouseArgs.Position.X, mouseArgs.Position.Y, out int? headerIfAny);
 
 
         if (headerIfAny.HasValue && headerIfAny.Value == 0)
         if (headerIfAny.HasValue && headerIfAny.Value == 0)
         {
         {
@@ -191,4 +198,5 @@ public abstract class CheckBoxTableSourceWrapperBase : ITableSource
             tableView.SetNeedsDraw ();
             tableView.SetNeedsDraw ();
         }
         }
     }
     }
+#nullable restore
 }
 }

+ 12 - 4
Terminal.Gui/Views/TableView/TreeTableSource.cs

@@ -42,7 +42,7 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
         _tableView = table;
         _tableView = table;
         _tree = tree;
         _tree = tree;
         _tableView.KeyDown += Table_KeyPress;
         _tableView.KeyDown += Table_KeyPress;
-        _tableView.MouseClick += Table_MouseClick;
+        _tableView.Selecting += Table_Selecting;
 
 
         List<string> colList = subsequentColumns.Keys.ToList ();
         List<string> colList = subsequentColumns.Keys.ToList ();
         colList.Insert (0, firstColumnName);
         colList.Insert (0, firstColumnName);
@@ -56,7 +56,7 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
     public void Dispose ()
     public void Dispose ()
     {
     {
         _tableView.KeyDown -= Table_KeyPress;
         _tableView.KeyDown -= Table_KeyPress;
-        _tableView.MouseClick -= Table_MouseClick;
+        _tableView.Selecting -= Table_Selecting;
         _tree.Dispose ();
         _tree.Dispose ();
     }
     }
 
 
@@ -168,9 +168,16 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, 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<MouseBinding> { 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)
         if (hit is null || headerIfAny is { } || !IsInTreeColumn (hit.Value.X, false) || offsetX is null)
         {
         {
@@ -202,4 +209,5 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
             _tableView.SetNeedsDraw ();
             _tableView.SetNeedsDraw ();
         }
         }
     }
     }
+#nullable restore
 }
 }

+ 2 - 2
Tests/IntegrationTests/FluentTests/GuiTestContextMouseEventTests.cs

@@ -213,7 +213,7 @@ public class GuiTestContextMouseEventTests (ITestOutputHelper outputHelper)
             Height = 5
             Height = 5
         };
         };
 
 
-        view.MouseClick += (s, e) => clickCount++;
+        view.MouseEvent += (s, e) => clickCount++;
 
 
         using GuiTestContext context = With.A<Window> (40, 10, d, _out)
         using GuiTestContext context = With.A<Window> (40, 10, d, _out)
                                            .Add (view);
                                            .Add (view);
@@ -240,7 +240,7 @@ public class GuiTestContextMouseEventTests (ITestOutputHelper outputHelper)
             Height = 5
             Height = 5
         };
         };
 
 
-        view.MouseClick += (s, e) => clickCount++;
+        view.MouseEvent += (s, e) => clickCount++;
 
 
         using GuiTestContext context = With.A<Window> (40, 10, d, _out)
         using GuiTestContext context = With.A<Window> (40, 10, d, _out)
                                            .Add (view)
                                            .Add (view)

+ 1 - 1
Tests/TerminalGuiFluentTesting/GuiTestContext.Input.cs

@@ -64,7 +64,7 @@ public partial class GuiTestContext
             {
             {
                 mouseEvent.Position = mouseEvent.ScreenPosition;
                 mouseEvent.Position = mouseEvent.ScreenPosition;
 
 
-                app.Driver.InputProcessor.EnqueueMouseEvent (app, mouseEvent);
+                app.Driver.GetInputProcessor ().EnqueueMouseEvent (app, mouseEvent);
             }
             }
             else
             else
             {
             {

+ 1 - 1
Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs

@@ -119,7 +119,7 @@ public class ApplicationMouseTests
 
 
         var mouseEvent = new MouseEventArgs { ScreenPosition = new (clickX, clickY), Flags = MouseFlags.Button1Clicked };
         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 (expectedX, e.Position.X);
                                Assert.Equal (expectedY, e.Position.Y);
                                Assert.Equal (expectedY, e.Position.Y);

+ 129 - 1
Tests/UnitTests/DriverAssert.cs

@@ -47,7 +47,7 @@ internal partial class DriverAssert
         {
         {
             driver = Application.Driver;
             driver = Application.Driver;
         }
         }
-        ArgumentNullException.ThrowIfNull(driver);
+        ArgumentNullException.ThrowIfNull (driver);
 
 
         Cell [,] contents = driver!.Contents!;
         Cell [,] contents = driver!.Contents!;
 
 
@@ -193,6 +193,134 @@ internal partial class DriverAssert
         Assert.Equal (expectedLook, actualLook);
         Assert.Equal (expectedLook, actualLook);
     }
     }
 
 
+#pragma warning disable xUnit1013 // Public method should be marked as test
+    /// <summary>Asserts that the driver raw ANSI output matches the expected output.</summary>
+    /// <param name="expectedLook">Expected output with C# escape sequences (e.g., \x1b for ESC)</param>
+    /// <param name="output"></param>
+    /// <param name="driver">The IDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
+    public static void AssertDriverOutputIs (
+        string expectedLook,
+        ITestOutputHelper output,
+        IDriver? driver = null
+    )
+    {
+#pragma warning restore xUnit1013 // Public method should be marked as test
+        if (driver is null && ApplicationImpl.ModelUsage == ApplicationModelUsage.LegacyStatic)
+        {
+            driver = Application.Driver;
+        }
+        ArgumentNullException.ThrowIfNull (driver);
+
+        string? actualLook = driver.GetOutput().GetLastOutput ();
+
+        // Unescape the expected string to convert C# escape sequences like \x1b to actual characters
+        string unescapedExpected = UnescapeString (expectedLook);
+
+        // Trim trailing whitespace from actual (screen padding)
+        actualLook = actualLook.TrimEnd ();
+        unescapedExpected = unescapedExpected.TrimEnd ();
+
+        if (string.Equals (unescapedExpected, actualLook))
+        {
+            return;
+        }
+
+        // If test is about to fail show user what things looked like
+        if (!string.Equals (unescapedExpected, actualLook))
+        {
+            output?.WriteLine ($"Expected (length={unescapedExpected.Length}):" + Environment.NewLine + unescapedExpected);
+            output?.WriteLine ($" But Was (length={actualLook.Length}):" + Environment.NewLine + actualLook);
+
+            // Show the difference at the end
+            int minLen = Math.Min (unescapedExpected.Length, actualLook.Length);
+            output?.WriteLine ($"Lengths: Expected={unescapedExpected.Length}, Actual={actualLook.Length}, MinLen={minLen}");
+            if (actualLook.Length > unescapedExpected.Length)
+            {
+                output?.WriteLine ($"Actual has {actualLook.Length - unescapedExpected.Length} extra characters at the end");
+            }
+        }
+
+        Assert.Equal (unescapedExpected, actualLook);
+    }
+
+    /// <summary>
+    ///     Unescapes a C# string literal by processing escape sequences like \x1b, \n, \r, \t, etc.
+    /// </summary>
+    /// <param name="input">String with C# escape sequences</param>
+    /// <returns>String with escape sequences converted to actual characters</returns>
+    private static string UnescapeString (string input)
+    {
+        if (string.IsNullOrEmpty (input))
+        {
+            return input;
+        }
+
+        var result = new StringBuilder (input.Length);
+        int i = 0;
+
+        while (i < input.Length)
+        {
+            if (input [i] == '\\' && i + 1 < input.Length)
+            {
+                char next = input [i + 1];
+
+                switch (next)
+                {
+                    case 'x' when i + 3 < input.Length:
+                        // Handle \xHH (2-digit hex)
+                        string hex = input.Substring (i + 2, 2);
+                        if (int.TryParse (hex, System.Globalization.NumberStyles.HexNumber, null, out int hexValue))
+                        {
+                            result.Append ((char)hexValue);
+                            i += 4; // Skip \xHH
+                            continue;
+                        }
+                        break;
+
+                    case 'n':
+                        result.Append ('\n');
+                        i += 2;
+                        continue;
+
+                    case 'r':
+                        result.Append ('\r');
+                        i += 2;
+                        continue;
+
+                    case 't':
+                        result.Append ('\t');
+                        i += 2;
+                        continue;
+
+                    case '\\':
+                        result.Append ('\\');
+                        i += 2;
+                        continue;
+
+                    case '"':
+                        result.Append ('"');
+                        i += 2;
+                        continue;
+
+                    case '\'':
+                        result.Append ('\'');
+                        i += 2;
+                        continue;
+
+                    case '0':
+                        result.Append ('\0');
+                        i += 2;
+                        continue;
+                }
+            }
+
+            // Not an escape sequence, add the character as-is
+            result.Append (input [i]);
+            i++;
+        }
+
+        return result.ToString ();
+    }
     /// <summary>
     /// <summary>
     ///     Asserts that the driver contents are equal to the provided string.
     ///     Asserts that the driver contents are equal to the provided string.
     /// </summary>
     /// </summary>

+ 29 - 57
Tests/UnitTests/View/Mouse/MouseTests.cs

@@ -25,7 +25,7 @@ public class MouseTests : TestsAllViews
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void ButtonPressed_In_Border_Starts_Drag (int marginThickness, int borderThickness, int paddingThickness, int xy, bool expectedMoved)
     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,
             CanFocus = true,
             X = 4,
             X = 4,
@@ -38,7 +38,7 @@ public class MouseTests : TestsAllViews
         testView.Border!.Thickness = new (borderThickness);
         testView.Border!.Thickness = new (borderThickness);
         testView.Padding!.Thickness = new (paddingThickness);
         testView.Padding!.Thickness = new (paddingThickness);
 
 
-        var top = new Runnable ();
+        Runnable top = new ();
         top.Add (testView);
         top.Add (testView);
 
 
         SessionToken rs = Application.Begin (top);
         SessionToken rs = Application.Begin (top);
@@ -49,8 +49,10 @@ public class MouseTests : TestsAllViews
 
 
         Application.RaiseMouseEvent (new () { ScreenPosition = new (xy + 1, xy + 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition });
         Application.RaiseMouseEvent (new () { ScreenPosition = new (xy + 1, xy + 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition });
         AutoInitShutdownAttribute.RunIteration ();
         AutoInitShutdownAttribute.RunIteration ();
-
         Assert.Equal (expectedMoved, new Point (5, 5) == testView.Frame.Location);
         Assert.Equal (expectedMoved, new Point (5, 5) == testView.Frame.Location);
+        // The above grabbed the mouse. Need to ungrab.
+        Application.Mouse.UngrabMouse ();
+
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
@@ -61,9 +63,9 @@ public class MouseTests : TestsAllViews
     [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)]
     [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)]
     public void WantContinuousButtonPressed_False_Button_Press_Release_DoesNotClick (MouseFlags pressed, MouseFlags released, MouseFlags clicked)
     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,
             Width = 1,
             Height = 1,
             Height = 1,
@@ -72,7 +74,7 @@ public class MouseTests : TestsAllViews
 
 
         var clickedCount = 0;
         var clickedCount = 0;
 
 
-        view.MouseClick += (s, e) => clickedCount++;
+        view.MouseEvent += (s, e) => clickedCount += e.IsSingleDoubleOrTripleClicked ? 1 : 0;
 
 
         me.Flags = pressed;
         me.Flags = pressed;
         view.NewMouseEvent (me);
         view.NewMouseEvent (me);
@@ -99,36 +101,6 @@ public class MouseTests : TestsAllViews
         Application.ResetState (true);
         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]
     [Theory]
     [InlineData (MouseFlags.Button1Clicked)]
     [InlineData (MouseFlags.Button1Clicked)]
     [InlineData (MouseFlags.Button2Clicked)]
     [InlineData (MouseFlags.Button2Clicked)]
@@ -136,9 +108,9 @@ public class MouseTests : TestsAllViews
     [InlineData (MouseFlags.Button4Clicked)]
     [InlineData (MouseFlags.Button4Clicked)]
     public void WantContinuousButtonPressed_True_Button_Clicked_Raises_Selecting (MouseFlags clicked)
     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,
             Width = 1,
             Height = 1,
             Height = 1,
@@ -166,9 +138,9 @@ public class MouseTests : TestsAllViews
     [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released)]
     [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released)]
     public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Button_Press_Release_Clicks (MouseFlags pressed, MouseFlags released)
     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,
             Width = 1,
             Height = 1,
             Height = 1,
@@ -177,8 +149,8 @@ public class MouseTests : TestsAllViews
         };
         };
 
 
         // Setup components for mouse held down
         // 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);
         view.MouseHeldDown = new MouseHeldDown (view, timed, grab);
 
 
         // Register callback for what to do when the mouse is held down
         // Register callback for what to do when the mouse is held down
@@ -226,9 +198,9 @@ public class MouseTests : TestsAllViews
         MouseFlags clicked
         MouseFlags clicked
     )
     )
     {
     {
-        var me = new MouseEventArgs ();
+        MouseEventArgs me = new ();
 
 
-        var view = new View
+        View view = new ()
         {
         {
             Width = 1,
             Width = 1,
             Height = 1,
             Height = 1,
@@ -237,8 +209,8 @@ public class MouseTests : TestsAllViews
         };
         };
 
 
         // Setup components for mouse held down
         // 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);
         view.MouseHeldDown = new MouseHeldDown (view, timed, grab);
 
 
         // Register callback for what to do when the mouse is held down
         // Register callback for what to do when the mouse is held down
@@ -279,9 +251,9 @@ public class MouseTests : TestsAllViews
     [Fact]
     [Fact]
     public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Move_InViewport_OutOfViewport_Keeps_Counting ()
     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,
             Width = 1,
             Height = 1,
             Height = 1,
@@ -290,8 +262,8 @@ public class MouseTests : TestsAllViews
         };
         };
 
 
         // Setup components for mouse held down
         // 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);
         view.MouseHeldDown = new MouseHeldDown (view, timed, grab);
 
 
         // Register callback for what to do when the mouse is held down
         // Register callback for what to do when the mouse is held down
@@ -352,7 +324,7 @@ public class MouseTests : TestsAllViews
     //[InlineData (true, MouseState.PressedOutside, 0, 0, 0, 1)]
     //[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)
     //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
     //        HighlightStates = highlightFlags
     //    };
     //    };
@@ -389,7 +361,7 @@ public class MouseTests : TestsAllViews
     [InlineData (10)]
     [InlineData (10)]
     public void MouseState_None_Button1_Pressed_Move_No_Changes (int x)
     public void MouseState_None_Button1_Pressed_Move_No_Changes (int x)
     {
     {
-        var testView = new MouseEventTestView
+        MouseEventTestView testView = new ()
         {
         {
             HighlightStates = MouseState.None
             HighlightStates = MouseState.None
         };
         };
@@ -403,7 +375,7 @@ public class MouseTests : TestsAllViews
         Assert.Equal (0, testView.MouseStatePressedOutsideCount);
         Assert.Equal (0, testView.MouseStatePressedOutsideCount);
         Assert.Equal (0, testView.MouseStateNoneCount);
         Assert.Equal (0, testView.MouseStateNoneCount);
 
 
-        // Move to x,0 
+        // Move to x,0
         testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (x, 0) });
         testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (x, 0) });
 
 
         if (inViewport)
         if (inViewport)
@@ -421,7 +393,7 @@ public class MouseTests : TestsAllViews
             Assert.Equal (0, testView.MouseStateNoneCount);
             Assert.Equal (0, testView.MouseStateNoneCount);
         }
         }
 
 
-        // Move backto 0,0 ; in viewport
+        // Move back to 0,0 ; in viewport
         testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
         testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
 
 
         if (inViewport)
         if (inViewport)
@@ -451,7 +423,7 @@ public class MouseTests : TestsAllViews
     [InlineData (10)]
     [InlineData (10)]
     public void MouseState_Pressed_Button1_Pressed_Move_Keeps_Pressed (int x)
     public void MouseState_Pressed_Button1_Pressed_Move_Keeps_Pressed (int x)
     {
     {
-        var testView = new MouseEventTestView
+        MouseEventTestView testView = new ()
         {
         {
             HighlightStates = MouseState.Pressed
             HighlightStates = MouseState.Pressed
         };
         };
@@ -513,7 +485,7 @@ public class MouseTests : TestsAllViews
     [InlineData (10)]
     [InlineData (10)]
     public void MouseState_PressedOutside_Button1_Pressed_Move_Raises_PressedOutside (int x)
     public void MouseState_PressedOutside_Button1_Pressed_Move_Raises_PressedOutside (int x)
     {
     {
-        var testView = new MouseEventTestView
+        MouseEventTestView testView = new ()
         {
         {
             HighlightStates = MouseState.PressedOutside,
             HighlightStates = MouseState.PressedOutside,
             WantContinuousButtonPressed = false
             WantContinuousButtonPressed = false
@@ -576,7 +548,7 @@ public class MouseTests : TestsAllViews
     [InlineData (10)]
     [InlineData (10)]
     public void MouseState_PressedOutside_Button1_Pressed_Move_Raises_PressedOutside_WantContinuousButtonPressed (int x)
     public void MouseState_PressedOutside_Button1_Pressed_Move_Raises_PressedOutside_WantContinuousButtonPressed (int x)
     {
     {
-        var testView = new MouseEventTestView
+        MouseEventTestView testView = new ()
         {
         {
             HighlightStates = MouseState.PressedOutside,
             HighlightStates = MouseState.PressedOutside,
             WantContinuousButtonPressed = true
             WantContinuousButtonPressed = true

+ 3 - 0
Tests/UnitTests/View/ViewCommandTests.cs

@@ -152,6 +152,9 @@ public class ViewCommandTests
         Assert.Equal (1, btnAcceptedCount);
         Assert.Equal (1, btnAcceptedCount);
         Assert.Equal (0, wAcceptedCount);
         Assert.Equal (0, wAcceptedCount);
 
 
+        // The above grabbed the mouse. Need to ungrab.
+        Application.Mouse.UngrabMouse ();
+
         w.Dispose ();
         w.Dispose ();
         Application.ResetState (true);
         Application.ResetState (true);
     }
     }

+ 3 - 3
Tests/UnitTests/Views/ShortcutTests.cs

@@ -55,9 +55,9 @@ public class ShortcutTests
     //  0123456789
     //  0123456789
     // " C  0  A "
     // " C  0  A "
     [InlineData (-1, 0, 0, 0, 0)]
     [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 (3, 0, 1, 1, 1)]
     [InlineData (4, 0, 1, 1, 1)]
     [InlineData (4, 0, 1, 1, 1)]
     [InlineData (5, 0, 1, 1, 1)]
     [InlineData (5, 0, 1, 1, 1)]

+ 1 - 1
Tests/UnitTests/Views/TextFieldTests.cs

@@ -917,7 +917,7 @@ public class TextFieldTests (ITestOutputHelper output)
 
 
         var tf = new TextField { Width = 10 };
         var tf = new TextField { Width = 10 };
         var clickCounter = 0;
         var clickCounter = 0;
-        tf.MouseClick += (s, m) => { clickCounter++; };
+        tf.MouseEvent += (s, m) => { clickCounter++; };
 
 
         var top = new Runnable ();
         var top = new Runnable ();
         top.Add (tf);
         top.Add (tf);

+ 1 - 1
Tests/UnitTestsParallelizable/Application/Application.NavigationTests.cs

@@ -1,6 +1,6 @@
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace ApplicationTests;
+namespace ApplicationTests.Navigation;
 
 
 public class ApplicationNavigationTests (ITestOutputHelper output)
 public class ApplicationNavigationTests (ITestOutputHelper output)
 {
 {

+ 60 - 92
Tests/UnitTestsParallelizable/Application/ApplicationImplTests.cs

@@ -5,6 +5,66 @@ namespace ApplicationTests;
 
 
 public class ApplicationImplTests
 public class ApplicationImplTests
 {
 {
+
+    [Fact]
+    public void Internal_Properties_Correct ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        Assert.True (app.Initialized);
+        Assert.Null (app.TopRunnableView);
+        SessionToken? rs = app.Begin (new Runnable<bool> ());
+        Assert.Equal (app.TopRunnable, rs!.Runnable);
+        Assert.Null (app.Mouse.MouseGrabView); // public
+
+        app.Dispose ();
+    }
+
+
+    #region DisposeTests
+
+    [Fact]
+    public async Task Dispose_Allows_Async ()
+    {
+        var isCompletedSuccessfully = false;
+
+        async Task TaskWithAsyncContinuation ()
+        {
+            await Task.Yield ();
+            await Task.Yield ();
+
+            isCompletedSuccessfully = true;
+        }
+
+        IApplication app = Application.Create ();
+        app.Dispose ();
+
+        Assert.False (isCompletedSuccessfully);
+        await TaskWithAsyncContinuation ();
+        Thread.Sleep (100);
+        Assert.True (isCompletedSuccessfully);
+    }
+
+    [Fact]
+    public void Dispose_Resets_SyncContext ()
+    {
+        IApplication app = Application.Create ();
+        app.Dispose ();
+        Assert.Null (SynchronizationContext.Current);
+    }
+
+    [Fact]
+    public void Dispose_Alone_Does_Nothing ()
+    {
+        IApplication app = Application.Create ();
+        app.Dispose ();
+    }
+
+
+    #endregion
+
+
     /// <summary>
     /// <summary>
     ///     Crates a new ApplicationImpl instance for testing. The input, output, and size monitor components are mocked.
     ///     Crates a new ApplicationImpl instance for testing. The input, output, and size monitor components are mocked.
     /// </summary>
     /// </summary>
@@ -44,21 +104,6 @@ public class ApplicationImplTests
                 .Verifiable (Times.Once);
                 .Verifiable (Times.Once);
     }
     }
 
 
-    [Fact]
-    public void Init_CreatesKeybindings ()
-    {
-        IApplication app = NewMockedApplicationImpl ();
-
-        app.Keyboard.KeyBindings.Clear ();
-
-        Assert.Empty (app.Keyboard.KeyBindings.GetBindings ());
-
-        app.Init ("fake");
-
-        Assert.NotEmpty (app.Keyboard.KeyBindings.GetBindings ());
-
-        app.Dispose ();
-    }
 
 
     [Fact]
     [Fact]
     public void NoInitThrowOnRun ()
     public void NoInitThrowOnRun ()
@@ -480,81 +525,4 @@ public class ApplicationImplTests
         Assert.Null (v2.TopRunnableView);
         Assert.Null (v2.TopRunnableView);
         Assert.Empty (v2.SessionStack!);
         Assert.Empty (v2.SessionStack!);
     }
     }
-
-    [Fact]
-    public void Init_Begin_End_Cleans_Up ()
-    {
-        IApplication? app = Application.Create ();
-
-        SessionToken? newSessionToken = null;
-
-        EventHandler<SessionTokenEventArgs> newSessionTokenFn = (s, e) =>
-                                                                {
-                                                                    Assert.NotNull (e.State);
-                                                                    newSessionToken = e.State;
-                                                                };
-        app.SessionBegun += newSessionTokenFn;
-
-        Runnable<bool> runnable = new ();
-        SessionToken sessionToken = app.Begin (runnable)!;
-        Assert.NotNull (sessionToken);
-        Assert.NotNull (newSessionToken);
-        Assert.Equal (sessionToken, newSessionToken);
-
-        // Assert.Equal (runnable, Application.TopRunnable);
-
-        app.SessionBegun -= newSessionTokenFn;
-        app.End (newSessionToken);
-
-        Assert.Null (app.TopRunnable);
-        Assert.Null (app.Driver);
-
-        runnable.Dispose ();
-    }
-
-    [Fact]
-    public void Run_RequestStop_Stops ()
-    {
-        IApplication? app = Application.Create ();
-        app.Init ("fake");
-
-        var top = new Runnable ();
-        SessionToken? sessionToken = app.Begin (top);
-        Assert.NotNull (sessionToken);
-
-        app.Iteration += OnApplicationOnIteration;
-        app.Run (top);
-        app.Iteration -= OnApplicationOnIteration;
-
-        top.Dispose ();
-
-        return;
-
-        void OnApplicationOnIteration (object? s, EventArgs<IApplication?> a) { app.RequestStop (); }
-    }
-
-    [Fact]
-    public void Run_T_Init_Driver_Cleared_with_Runnable_Throws ()
-    {
-        IApplication? app = Application.Create ();
-
-        app.Init ("fake");
-        app.Driver = null;
-
-        app.StopAfterFirstIteration = true;
-
-        // Init has been called, but Driver has been set to null. Bad.
-        Assert.Throws<InvalidOperationException> (() => app.Run<Runnable> ());
-    }
-
-    [Fact]
-    public void Init_Unbalanced_Throws ()
-    {
-        IApplication? app = Application.Create ();
-        app.Init ("fake");
-
-        Assert.Throws<InvalidOperationException> (() =>
-                                                      app.Init ("fake")
-                                                 );
-    }
 }
 }

+ 0 - 510
Tests/UnitTestsParallelizable/Application/ApplicationTests.cs

@@ -1,510 +0,0 @@
-#nullable enable
-using Xunit.Abstractions;
-
-namespace ApplicationTests;
-
-/// <summary>
-///     Parallelizable tests for IApplication that don't require the main event loop.
-///     Tests using the modern non-static IApplication API.
-/// </summary>
-public class ApplicationTests (ITestOutputHelper output)
-{
-    private readonly ITestOutputHelper _output = output;
-
-
-    [Fact]
-    public void Begin_Null_Runnable_Throws ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-
-        // Test null Runnable
-        Assert.Throws<ArgumentNullException> (() => app.Begin (null!));
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Begin_Sets_Application_Top_To_Console_Size ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-
-        Assert.Null (app.TopRunnableView);
-        app.Driver!.SetScreenSize (80, 25);
-        Runnable top = new ();
-        SessionToken? token = app.Begin (top);
-        Assert.Equal (new (0, 0, 80, 25), app.TopRunnableView!.Frame);
-        app.Driver!.SetScreenSize (5, 5);
-        app.LayoutAndDraw ();
-        Assert.Equal (new (0, 0, 5, 5), app.TopRunnableView!.Frame);
-        
-        if (token is { })
-        {
-            app.End (token);
-        }
-        top.Dispose ();
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Init_Null_Driver_Should_Pick_A_Driver ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ();
-
-        Assert.NotNull (app.Driver);
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Init_Dispose_Cleans_Up ()
-    {
-        IApplication app = Application.Create ();
-
-        app.Init ("fake");
-
-        app.Dispose ();
-
-#if DEBUG_IDISPOSABLE
-        // Validate there are no outstanding Responder-based instances 
-        // after cleanup
-        // Note: We can't check View.Instances in parallel tests as it's a static field
-        // that would be shared across parallel test runs
-#endif
-    }
-
-    [Fact]
-    public void Init_Dispose_Fire_InitializedChanged ()
-    {
-        var initialized = false;
-        var Dispose = false;
-
-        IApplication app = Application.Create ();
-
-        app.InitializedChanged += OnApplicationOnInitializedChanged;
-
-        app.Init (driverName: "fake");
-        Assert.True (initialized);
-        Assert.False (Dispose);
-
-        app.Dispose ();
-        Assert.True (initialized);
-        Assert.True (Dispose);
-
-        app.InitializedChanged -= OnApplicationOnInitializedChanged;
-
-        return;
-
-        void OnApplicationOnInitializedChanged (object? s, EventArgs<bool> a)
-        {
-            if (a.Value)
-            {
-                initialized = true;
-            }
-            else
-            {
-                Dispose = true;
-            }
-        }
-    }
-
-    [Fact]
-    public void Init_KeyBindings_Are_Not_Reset ()
-    {
-        IApplication app = Application.Create ();
-
-        // Set via Keyboard property (modern API)
-        app.Keyboard.QuitKey = Key.Q;
-        Assert.Equal (Key.Q, app.Keyboard.QuitKey);
-
-        app.Init ("fake");
-
-        Assert.Equal (Key.Q, app.Keyboard.QuitKey);
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Init_NoParam_ForceDriver_Works ()
-    {
-        using IApplication app = Application.Create ();
-
-        app.ForceDriver = "fake";
-        // Note: Init() without params picks up driver configuration
-        app.Init ();
-
-        Assert.Equal ("fake", app.Driver!.GetName ());
-    }
-
-    [Fact]
-    public void Init_Dispose_Resets_Instance_Properties ()
-    {
-        IApplication app = Application.Create ();
-
-        // Init the app
-        app.Init (driverName: "fake");
-
-        // Verify initialized
-        Assert.True (app.Initialized);
-        Assert.NotNull (app.Driver);
-
-        // Dispose cleans up
-        app.Dispose ();
-
-        // Check reset state on the instance
-        CheckReset (app);
-
-        // Create a new instance and set values
-        app = Application.Create ();
-        app.Init ("fake");
-
-        app.StopAfterFirstIteration = true;
-        app.Keyboard.PrevTabGroupKey = Key.A;
-        app.Keyboard.NextTabGroupKey = Key.B;
-        app.Keyboard.QuitKey = Key.C;
-        app.Keyboard.KeyBindings.Add (Key.D, Command.Cancel);
-
-        app.Mouse.CachedViewsUnderMouse.Clear ();
-        app.Mouse.LastMousePosition = new Point (1, 1);
-
-        // Dispose and check reset
-        app.Dispose ();
-        CheckReset (app);
-
-        return;
-
-        void CheckReset (IApplication application)
-        {
-            // Check that all fields and properties are reset on the instance
-
-            // Public Properties
-            Assert.Null (application.TopRunnableView);
-            Assert.Null (application.Mouse.MouseGrabView);
-            Assert.Null (application.Driver);
-            Assert.False (application.StopAfterFirstIteration);
-
-            // Internal properties
-            Assert.False (application.Initialized);
-            Assert.Null (application.MainThreadId);
-            Assert.Empty (application.Mouse.CachedViewsUnderMouse);
-        }
-    }
-
-    [Fact]
-    public void Internal_Properties_Correct ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-
-        Assert.True (app.Initialized);
-        Assert.Null (app.TopRunnableView);
-        SessionToken? rs = app.Begin (new Runnable<bool> ());
-        Assert.Equal (app.TopRunnable, rs!.Runnable);
-        Assert.Null (app.Mouse.MouseGrabView); // public
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Invoke_Adds_Idle ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-
-        Runnable top = new ();
-        SessionToken? rs = app.Begin (top);
-
-        var actionCalled = 0;
-        app.Invoke ((_) => { actionCalled++; });
-        app.TimedEvents!.RunTimers ();
-        Assert.Equal (1, actionCalled);
-        top.Dispose ();
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Run_Iteration_Fires ()
-    {
-        var iteration = 0;
-
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-
-        app.Iteration += Application_Iteration;
-        app.Run<Runnable> ();
-        app.Iteration -= Application_Iteration;
-
-        Assert.Equal (1, iteration);
-        app.Dispose ();
-
-        return;
-
-        void Application_Iteration (object? sender, EventArgs<IApplication?> e)
-        {
-
-            iteration++;
-            app.RequestStop ();
-        }
-    }
-
-    [Fact]
-    public void Screen_Size_Changes ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-
-        IDriver? driver = app.Driver;
-
-        app.Driver!.SetScreenSize (80, 25);
-
-        Assert.Equal (new (0, 0, 80, 25), driver!.Screen);
-        Assert.Equal (new (0, 0, 80, 25), app.Screen);
-
-        // TODO: Should not be possible to manually change these at whim!
-        driver.Cols = 100;
-        driver.Rows = 30;
-
-        app.Driver!.SetScreenSize (100, 30);
-
-        Assert.Equal (new (0, 0, 100, 30), driver.Screen);
-
-        app.Screen = new (0, 0, driver.Cols, driver.Rows);
-        Assert.Equal (new (0, 0, 100, 30), driver.Screen);
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Dispose_Alone_Does_Nothing ()
-    {
-        IApplication app = Application.Create ();
-        app.Dispose ();
-    }
-
-    #region RunTests
-
-    [Fact]
-    public void Run_T_After_InitWithDriver_with_Runnable_and_Driver_Does_Not_Throw ()
-    {
-        IApplication app = Application.Create ();
-        app.StopAfterFirstIteration = true;
-
-        // Run<Runnable<bool>> when already initialized or not with a Driver will not throw (because Window is derived from Runnable)
-        // Using another type not derived from Runnable will throws at compile time
-        app.Run<Window> (null, "fake");
-
-        // Run<Runnable<bool>> when already initialized or not with a Driver will not throw (because Dialog is derived from Runnable)
-        app.Run<Dialog> (null, "fake");
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Run_T_After_Init_Does_Not_Disposes_Application_Top ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-
-        // Init doesn't create a Runnable and assigned it to app.TopRunnable
-        // but Begin does
-        var initTop = new Runnable ();
-
-        app.Iteration += OnApplicationOnIteration;
-
-        app.Run<Runnable> ();
-        app.Iteration -= OnApplicationOnIteration;
-
-#if DEBUG_IDISPOSABLE
-        Assert.False (initTop.WasDisposed);
-        initTop.Dispose ();
-        Assert.True (initTop.WasDisposed);
-#endif
-        initTop.Dispose ();
-
-        app.Dispose ();
-
-        return;
-
-        void OnApplicationOnIteration (object? s, EventArgs<IApplication?> a)
-        {
-            Assert.NotEqual (initTop, app.TopRunnableView);
-#if DEBUG_IDISPOSABLE
-            Assert.False (initTop.WasDisposed);
-#endif
-            app.RequestStop ();
-        }
-    }
-
-    [Fact]
-    public void Run_T_After_InitWithDriver_with_TestRunnable_DoesNotThrow ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-        app.StopAfterFirstIteration = true;
-
-        // Init has been called and we're passing no driver to Run<TestRunnable>. This is ok.
-        app.Run<Window> ();
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Run_T_After_InitNullDriver_with_TestRunnable_DoesNotThrow ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-        app.StopAfterFirstIteration = true;
-
-        // Init has been called, selecting FakeDriver; we're passing no driver to Run<TestRunnable>. Should be fine.
-        app.Run<Window> ();
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Run_T_NoInit_DoesNotThrow ()
-    {
-        IApplication app = Application.Create ();
-        app.StopAfterFirstIteration = true;
-
-        app.Run<Window> ();
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Run_T_NoInit_WithDriver_DoesNotThrow ()
-    {
-        IApplication app = Application.Create ();
-        app.StopAfterFirstIteration = true;
-
-        // Init has NOT been called and we're passing a valid driver to Run<TestRunnable>. This is ok.
-        app.Run<Runnable> (null, "fake");
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Run_Sets_Running_True ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-
-        var top = new Runnable ();
-        SessionToken? rs = app.Begin (top);
-        Assert.NotNull (rs);
-
-        app.Iteration += OnApplicationOnIteration;
-        app.Run (top);
-        app.Iteration -= OnApplicationOnIteration;
-
-        top.Dispose ();
-
-        app.Dispose ();
-
-        return;
-
-        void OnApplicationOnIteration (object? s, EventArgs<IApplication?> a)
-        {
-            Assert.True (top.IsRunning);
-            top.RequestStop ();
-        }
-    }
-
-    [Fact]
-    public void Run_A_Modal_Runnable_Refresh_Background_On_Moving ()
-    {
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-
-        // Don't use Dialog here as it has more layout logic. Use Window instead.
-        var w = new Window
-        {
-            Width = 5, Height = 5,
-            Arrangement = ViewArrangement.Movable
-        };
-        app.Driver!.SetScreenSize (10, 10);
-        SessionToken? rs = app.Begin (w);
-
-        // Don't use visuals to test as style of border can change over time.
-        Assert.Equal (new (0, 0), w.Frame.Location);
-
-        app.Mouse.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
-        Assert.Equal (w.Border, app.Mouse.MouseGrabView);
-        Assert.Equal (new (0, 0), w.Frame.Location);
-
-        // Move down and to the right.
-        app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (1, 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition });
-        Assert.Equal (new (1, 1), w.Frame.Location);
-
-        app.End (rs!);
-        w.Dispose ();
-
-        app.Dispose ();
-    }
-
-    [Fact]
-    public void Run_T_Creates_Top_Without_Init ()
-    {
-        IApplication app = Application.Create ();
-        app.StopAfterFirstIteration = true;
-
-        app.SessionEnded += OnApplicationOnSessionEnded;
-
-        app.Run<Window> (null, "fake");
-
-        Assert.Null (app.TopRunnableView);
-
-        app.Dispose ();
-        Assert.Null (app.TopRunnableView);
-
-        return;
-
-        void OnApplicationOnSessionEnded (object? sender, SessionTokenEventArgs e)
-        {
-            app.SessionEnded -= OnApplicationOnSessionEnded;
-            e.State.Result = (e.State.Runnable as IRunnable<object?>)?.Result;
-        }
-    }
-
-    #endregion
-
-    #region DisposeTests
-
-    [Fact]
-    public async Task Dispose_Allows_Async ()
-    {
-        var isCompletedSuccessfully = false;
-
-        async Task TaskWithAsyncContinuation ()
-        {
-            await Task.Yield ();
-            await Task.Yield ();
-
-            isCompletedSuccessfully = true;
-        }
-
-        IApplication app = Application.Create ();
-        app.Dispose ();
-
-        Assert.False (isCompletedSuccessfully);
-        await TaskWithAsyncContinuation ();
-        Thread.Sleep (100);
-        Assert.True (isCompletedSuccessfully);
-    }
-
-    [Fact]
-    public void Dispose_Resets_SyncContext ()
-    {
-        IApplication app = Application.Create ();
-        app.Dispose ();
-        Assert.Null (SynchronizationContext.Current);
-    }
-
-    #endregion
-
-}

+ 69 - 1
Tests/UnitTestsParallelizable/Application/ApplicationImplBeginEndTests.cs → Tests/UnitTestsParallelizable/Application/BeginEndTests.cs

@@ -1,6 +1,6 @@
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace ApplicationTests;
+namespace ApplicationTests.BeginEnd;
 
 
 /// <summary>
 /// <summary>
 ///     Comprehensive tests for ApplicationImpl.Begin/End logic that manages Current and SessionStack.
 ///     Comprehensive tests for ApplicationImpl.Begin/End logic that manages Current and SessionStack.
@@ -11,6 +11,74 @@ public class ApplicationImplBeginEndTests (ITestOutputHelper output)
 {
 {
     private readonly ITestOutputHelper _output = output;
     private readonly ITestOutputHelper _output = output;
 
 
+
+    [Fact]
+    public void Init_Begin_End_Cleans_Up ()
+    {
+        IApplication? app = Application.Create ();
+
+        SessionToken? newSessionToken = null;
+
+        EventHandler<SessionTokenEventArgs> newSessionTokenFn = (s, e) =>
+                                                                {
+                                                                    Assert.NotNull (e.State);
+                                                                    newSessionToken = e.State;
+                                                                };
+        app.SessionBegun += newSessionTokenFn;
+
+        Runnable<bool> runnable = new ();
+        SessionToken sessionToken = app.Begin (runnable)!;
+        Assert.NotNull (sessionToken);
+        Assert.NotNull (newSessionToken);
+        Assert.Equal (sessionToken, newSessionToken);
+
+        // Assert.Equal (runnable, Application.TopRunnable);
+
+        app.SessionBegun -= newSessionTokenFn;
+        app.End (newSessionToken);
+
+        Assert.Null (app.TopRunnable);
+        Assert.Null (app.Driver);
+
+        runnable.Dispose ();
+    }
+
+    [Fact]
+    public void Begin_Null_Runnable_Throws ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        // Test null Runnable
+        Assert.Throws<ArgumentNullException> (() => app.Begin (null!));
+
+        app.Dispose ();
+    }
+
+    [Fact]
+    public void Begin_Sets_Application_Top_To_Console_Size ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        Assert.Null (app.TopRunnableView);
+        app.Driver!.SetScreenSize (80, 25);
+        Runnable top = new ();
+        SessionToken? token = app.Begin (top);
+        Assert.Equal (new (0, 0, 80, 25), app.TopRunnableView!.Frame);
+        app.Driver!.SetScreenSize (5, 5);
+        app.LayoutAndDraw ();
+        Assert.Equal (new (0, 0, 5, 5), app.TopRunnableView!.Frame);
+
+        if (token is { })
+        {
+            app.End (token);
+        }
+        top.Dispose ();
+
+        app.Dispose ();
+    }
+
     [Fact]
     [Fact]
     public void Begin_WithNullRunnable_ThrowsArgumentNullException ()
     public void Begin_WithNullRunnable_ThrowsArgumentNullException ()
     {
     {

+ 56 - 49
Tests/UnitTestsParallelizable/Application/CWP/ResultEventArgsTests.cs

@@ -1,7 +1,3 @@
-#nullable enable
-using System;
-using Terminal.Gui.App;
-using Xunit;
 namespace ApplicationTests;
 namespace ApplicationTests;
 
 
 public class ResultEventArgsTests
 public class ResultEventArgsTests
@@ -9,7 +5,7 @@ public class ResultEventArgsTests
     [Fact]
     [Fact]
     public void DefaultConstructor_InitializesProperties ()
     public void DefaultConstructor_InitializesProperties ()
     {
     {
-        var args = new ResultEventArgs<string> ();
+        ResultEventArgs<string> args = new ();
 
 
         Assert.Null (args.Result);
         Assert.Null (args.Result);
         Assert.False (args.Handled);
         Assert.False (args.Handled);
@@ -18,7 +14,7 @@ public class ResultEventArgsTests
     [Fact]
     [Fact]
     public void Constructor_WithResult_SetsResult ()
     public void Constructor_WithResult_SetsResult ()
     {
     {
-        var args = new ResultEventArgs<int> (42);
+        ResultEventArgs<int> args = new (42);
 
 
         Assert.Equal (42, args.Result);
         Assert.Equal (42, args.Result);
         Assert.False (args.Handled);
         Assert.False (args.Handled);
@@ -27,7 +23,7 @@ public class ResultEventArgsTests
     [Fact]
     [Fact]
     public void Constructor_WithNullResult_AllowsNull ()
     public void Constructor_WithNullResult_AllowsNull ()
     {
     {
-        var args = new ResultEventArgs<string?> (null);
+        ResultEventArgs<string?> args = new (null);
 
 
         Assert.Null (args.Result);
         Assert.Null (args.Result);
         Assert.False (args.Handled);
         Assert.False (args.Handled);
@@ -36,7 +32,7 @@ public class ResultEventArgsTests
     [Fact]
     [Fact]
     public void Result_CanBeSetAndRetrieved ()
     public void Result_CanBeSetAndRetrieved ()
     {
     {
-        var args = new ResultEventArgs<string> ();
+        ResultEventArgs<string> args = new ();
         args.Result = "foo";
         args.Result = "foo";
 
 
         Assert.Equal ("foo", args.Result);
         Assert.Equal ("foo", args.Result);
@@ -48,7 +44,7 @@ public class ResultEventArgsTests
     [Fact]
     [Fact]
     public void Handled_CanBeSetAndRetrieved ()
     public void Handled_CanBeSetAndRetrieved ()
     {
     {
-        var args = new ResultEventArgs<object> ();
+        ResultEventArgs<object> args = new ();
         Assert.False (args.Handled);
         Assert.False (args.Handled);
 
 
         args.Handled = true;
         args.Handled = true;
@@ -61,7 +57,7 @@ public class ResultEventArgsTests
     [Fact]
     [Fact]
     public void WorksWithValueTypes ()
     public void WorksWithValueTypes ()
     {
     {
-        var args = new ResultEventArgs<int> ();
+        ResultEventArgs<int> args = new ();
         Assert.Equal (0, args.Result); // default(int) is 0
         Assert.Equal (0, args.Result); // default(int) is 0
 
 
         args.Result = 123;
         args.Result = 123;
@@ -72,7 +68,7 @@ public class ResultEventArgsTests
     public void WorksWithReferenceTypes ()
     public void WorksWithReferenceTypes ()
     {
     {
         var obj = new object ();
         var obj = new object ();
-        var args = new ResultEventArgs<object> (obj);
+        ResultEventArgs<object> args = new (obj);
 
 
         Assert.Same (obj, args.Result);
         Assert.Same (obj, args.Result);
 
 
@@ -87,7 +83,8 @@ public class ResultEventArgsTests
     public void EventHandler_CanChangeResult_AndCallerSeesChange ()
     public void EventHandler_CanChangeResult_AndCallerSeesChange ()
     {
     {
         // Arrange
         // Arrange
-        var args = new ResultEventArgs<string> ("initial");
+        ResultEventArgs<string> args = new ("initial");
+
         StringResultEvent += (sender, e) =>
         StringResultEvent += (sender, e) =>
                              {
                              {
                                  // Handler changes the result
                                  // Handler changes the result
@@ -101,17 +98,12 @@ public class ResultEventArgsTests
         Assert.Equal ("changed by handler", args.Result);
         Assert.Equal ("changed by handler", args.Result);
     }
     }
 
 
-
-
     [Fact]
     [Fact]
     public void EventHandler_CanSetResultToNull ()
     public void EventHandler_CanSetResultToNull ()
     {
     {
         // Arrange
         // Arrange
-        var args = new ResultEventArgs<string> ("not null");
-        StringResultEvent += (sender, e) =>
-                             {
-                                 e.Result = null;
-                             };
+        ResultEventArgs<string> args = new ("not null");
+        StringResultEvent += (sender, e) => { e.Result = null; };
 
 
         // Act
         // Act
         StringResultEvent?.Invoke (this, args);
         StringResultEvent?.Invoke (this, args);
@@ -124,7 +116,7 @@ public class ResultEventArgsTests
     public void MultipleHandlers_LastHandlerWins ()
     public void MultipleHandlers_LastHandlerWins ()
     {
     {
         // Arrange
         // Arrange
-        var args = new ResultEventArgs<int> (1);
+        ResultEventArgs<int> args = new (1);
         EventHandler<ResultEventArgs<int>>? intEvent = null;
         EventHandler<ResultEventArgs<int>>? intEvent = null;
         intEvent += (s, e) => e.Result = 2;
         intEvent += (s, e) => e.Result = 2;
         intEvent += (s, e) => e.Result = 3;
         intEvent += (s, e) => e.Result = 3;
@@ -141,7 +133,7 @@ public class ResultEventArgsTests
     public void EventHandler_CanChangeResult_Int ()
     public void EventHandler_CanChangeResult_Int ()
     {
     {
         EventHandler<ResultEventArgs<int>> handler = (s, e) => e.Result = 99;
         EventHandler<ResultEventArgs<int>> handler = (s, e) => e.Result = 99;
-        var args = new ResultEventArgs<int> (1);
+        ResultEventArgs<int> args = new (1);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (99, args.Result);
         Assert.Equal (99, args.Result);
     }
     }
@@ -151,7 +143,7 @@ public class ResultEventArgsTests
     public void EventHandler_CanChangeResult_Double ()
     public void EventHandler_CanChangeResult_Double ()
     {
     {
         EventHandler<ResultEventArgs<double>> handler = (s, e) => e.Result = 2.718;
         EventHandler<ResultEventArgs<double>> handler = (s, e) => e.Result = 2.718;
-        var args = new ResultEventArgs<double> (3.14);
+        ResultEventArgs<double> args = new (3.14);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (2.718, args.Result);
         Assert.Equal (2.718, args.Result);
     }
     }
@@ -161,29 +153,39 @@ public class ResultEventArgsTests
     public void EventHandler_CanChangeResult_Bool ()
     public void EventHandler_CanChangeResult_Bool ()
     {
     {
         EventHandler<ResultEventArgs<bool>> handler = (s, e) => e.Result = false;
         EventHandler<ResultEventArgs<bool>> handler = (s, e) => e.Result = false;
-        var args = new ResultEventArgs<bool> (true);
+        ResultEventArgs<bool> args = new (true);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.False (args.Result);
         Assert.False (args.Result);
     }
     }
 
 
     // Enum
     // Enum
-    enum MyEnum { A, B, C }
+    private enum MyEnum
+    {
+        A,
+        B,
+        C
+    }
+
     [Fact]
     [Fact]
     public void EventHandler_CanChangeResult_Enum ()
     public void EventHandler_CanChangeResult_Enum ()
     {
     {
         EventHandler<ResultEventArgs<MyEnum>> handler = (s, e) => e.Result = MyEnum.C;
         EventHandler<ResultEventArgs<MyEnum>> handler = (s, e) => e.Result = MyEnum.C;
-        var args = new ResultEventArgs<MyEnum> (MyEnum.A);
+        ResultEventArgs<MyEnum> args = new (MyEnum.A);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (MyEnum.C, args.Result);
         Assert.Equal (MyEnum.C, args.Result);
     }
     }
 
 
     // Struct
     // Struct
-    struct MyStruct { public int X; }
+    private struct MyStruct
+    {
+        public int X;
+    }
+
     [Fact]
     [Fact]
     public void EventHandler_CanChangeResult_Struct ()
     public void EventHandler_CanChangeResult_Struct ()
     {
     {
-        EventHandler<ResultEventArgs<MyStruct>> handler = (s, e) => e.Result = new MyStruct { X = 42 };
-        var args = new ResultEventArgs<MyStruct> (new MyStruct { X = 1 });
+        EventHandler<ResultEventArgs<MyStruct>> handler = (s, e) => e.Result = new() { X = 42 };
+        ResultEventArgs<MyStruct> args = new (new() { X = 1 });
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (42, args.Result.X);
         Assert.Equal (42, args.Result.X);
     }
     }
@@ -193,7 +195,7 @@ public class ResultEventArgsTests
     public void EventHandler_CanChangeResult_String ()
     public void EventHandler_CanChangeResult_String ()
     {
     {
         EventHandler<ResultEventArgs<string>> handler = (s, e) => e.Result = "changed";
         EventHandler<ResultEventArgs<string>> handler = (s, e) => e.Result = "changed";
-        var args = new ResultEventArgs<string> ("original");
+        ResultEventArgs<string> args = new ("original");
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal ("changed", args.Result);
         Assert.Equal ("changed", args.Result);
     }
     }
@@ -204,7 +206,7 @@ public class ResultEventArgsTests
     {
     {
         var newObj = new object ();
         var newObj = new object ();
         EventHandler<ResultEventArgs<object>> handler = (s, e) => e.Result = newObj;
         EventHandler<ResultEventArgs<object>> handler = (s, e) => e.Result = newObj;
-        var args = new ResultEventArgs<object> (new object ());
+        ResultEventArgs<object> args = new (new ());
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Same (newObj, args.Result);
         Assert.Same (newObj, args.Result);
     }
     }
@@ -214,7 +216,7 @@ public class ResultEventArgsTests
     public void EventHandler_CanChangeResult_NullableInt ()
     public void EventHandler_CanChangeResult_NullableInt ()
     {
     {
         EventHandler<ResultEventArgs<int?>> handler = (s, e) => e.Result = null;
         EventHandler<ResultEventArgs<int?>> handler = (s, e) => e.Result = null;
-        var args = new ResultEventArgs<int?> (42);
+        ResultEventArgs<int?> args = new (42);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Null (args.Result);
         Assert.Null (args.Result);
     }
     }
@@ -225,7 +227,7 @@ public class ResultEventArgsTests
     {
     {
         var newArr = new [] { "x", "y" };
         var newArr = new [] { "x", "y" };
         EventHandler<ResultEventArgs<string []>> handler = (s, e) => e.Result = newArr;
         EventHandler<ResultEventArgs<string []>> handler = (s, e) => e.Result = newArr;
-        var args = new ResultEventArgs<string []> (new [] { "a", "b" });
+        ResultEventArgs<string []> args = new (new [] { "a", "b" });
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (newArr, args.Result);
         Assert.Equal (newArr, args.Result);
     }
     }
@@ -234,9 +236,9 @@ public class ResultEventArgsTests
     [Fact]
     [Fact]
     public void EventHandler_CanChangeResult_List ()
     public void EventHandler_CanChangeResult_List ()
     {
     {
-        var newList = new List<int> { 1, 2, 3 };
+        List<int> newList = new() { 1, 2, 3 };
         EventHandler<ResultEventArgs<List<int>>> handler = (s, e) => e.Result = newList;
         EventHandler<ResultEventArgs<List<int>>> handler = (s, e) => e.Result = newList;
-        var args = new ResultEventArgs<List<int>> (new List<int> { 9 });
+        ResultEventArgs<List<int>> args = new (new() { 9 });
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (newList, args.Result);
         Assert.Equal (newList, args.Result);
     }
     }
@@ -245,21 +247,22 @@ public class ResultEventArgsTests
     [Fact]
     [Fact]
     public void EventHandler_CanChangeResult_Dictionary ()
     public void EventHandler_CanChangeResult_Dictionary ()
     {
     {
-        var newDict = new Dictionary<string, int> { ["a"] = 1 };
+        Dictionary<string, int> newDict = new() { ["a"] = 1 };
         EventHandler<ResultEventArgs<Dictionary<string, int>>> handler = (s, e) => e.Result = newDict;
         EventHandler<ResultEventArgs<Dictionary<string, int>>> handler = (s, e) => e.Result = newDict;
-        var args = new ResultEventArgs<Dictionary<string, int>> (new Dictionary<string, int> ());
+        ResultEventArgs<Dictionary<string, int>> args = new (new ());
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (newDict, args.Result);
         Assert.Equal (newDict, args.Result);
     }
     }
 
 
     // Record
     // Record
     public record MyRecord (int Id, string Name);
     public record MyRecord (int Id, string Name);
+
     [Fact]
     [Fact]
     public void EventHandler_CanChangeResult_Record ()
     public void EventHandler_CanChangeResult_Record ()
     {
     {
         var rec = new MyRecord (1, "foo");
         var rec = new MyRecord (1, "foo");
         EventHandler<ResultEventArgs<MyRecord>> handler = (s, e) => e.Result = rec;
         EventHandler<ResultEventArgs<MyRecord>> handler = (s, e) => e.Result = rec;
-        var args = new ResultEventArgs<MyRecord> (null);
+        ResultEventArgs<MyRecord> args = new (null);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (rec, args.Result);
         Assert.Equal (rec, args.Result);
     }
     }
@@ -269,12 +272,12 @@ public class ResultEventArgsTests
     public void EventHandler_CanChangeResult_NullableInt_ToValue_AndNull ()
     public void EventHandler_CanChangeResult_NullableInt_ToValue_AndNull ()
     {
     {
         EventHandler<ResultEventArgs<int?>> handler = (s, e) => e.Result = 123;
         EventHandler<ResultEventArgs<int?>> handler = (s, e) => e.Result = 123;
-        var args = new ResultEventArgs<int?> (null);
+        ResultEventArgs<int?> args = new (null);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (123, args.Result);
         Assert.Equal (123, args.Result);
 
 
         handler = (s, e) => e.Result = null;
         handler = (s, e) => e.Result = null;
-        args = new ResultEventArgs<int?> (456);
+        args = new (456);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Null (args.Result);
         Assert.Null (args.Result);
     }
     }
@@ -284,12 +287,12 @@ public class ResultEventArgsTests
     public void EventHandler_CanChangeResult_NullableDouble_ToValue_AndNull ()
     public void EventHandler_CanChangeResult_NullableDouble_ToValue_AndNull ()
     {
     {
         EventHandler<ResultEventArgs<double?>> handler = (s, e) => e.Result = 3.14;
         EventHandler<ResultEventArgs<double?>> handler = (s, e) => e.Result = 3.14;
-        var args = new ResultEventArgs<double?> (null);
+        ResultEventArgs<double?> args = new (null);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (3.14, args.Result);
         Assert.Equal (3.14, args.Result);
 
 
         handler = (s, e) => e.Result = null;
         handler = (s, e) => e.Result = null;
-        args = new ResultEventArgs<double?> (2.71);
+        args = new (2.71);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Null (args.Result);
         Assert.Null (args.Result);
     }
     }
@@ -299,12 +302,12 @@ public class ResultEventArgsTests
     public void EventHandler_CanChangeResult_NullableStruct_ToValue_AndNull ()
     public void EventHandler_CanChangeResult_NullableStruct_ToValue_AndNull ()
     {
     {
         EventHandler<ResultEventArgs<MyStruct?>> handler = (s, e) => e.Result = new MyStruct { X = 7 };
         EventHandler<ResultEventArgs<MyStruct?>> handler = (s, e) => e.Result = new MyStruct { X = 7 };
-        var args = new ResultEventArgs<MyStruct?> (null);
+        ResultEventArgs<MyStruct?> args = new (null);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal (7, args.Result?.X);
         Assert.Equal (7, args.Result?.X);
 
 
         handler = (s, e) => e.Result = null;
         handler = (s, e) => e.Result = null;
-        args = new ResultEventArgs<MyStruct?> (new MyStruct { X = 8 });
+        args = new (new MyStruct { X = 8 });
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Null (args.Result);
         Assert.Null (args.Result);
     }
     }
@@ -314,29 +317,33 @@ public class ResultEventArgsTests
     public void EventHandler_CanChangeResult_NullableString_ToValue_AndNull ()
     public void EventHandler_CanChangeResult_NullableString_ToValue_AndNull ()
     {
     {
         EventHandler<ResultEventArgs<string?>> handler = (s, e) => e.Result = "hello";
         EventHandler<ResultEventArgs<string?>> handler = (s, e) => e.Result = "hello";
-        var args = new ResultEventArgs<string?> (null);
+        ResultEventArgs<string?> args = new (null);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Equal ("hello", args.Result);
         Assert.Equal ("hello", args.Result);
 
 
         handler = (s, e) => e.Result = null;
         handler = (s, e) => e.Result = null;
-        args = new ResultEventArgs<string?> ("world");
+        args = new ("world");
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Null (args.Result);
         Assert.Null (args.Result);
     }
     }
 
 
     // Nullable custom class
     // Nullable custom class
-    class MyClass { public int Y { get; set; } }
+    private class MyClass
+    {
+        public int Y { get; set; }
+    }
+
     [Fact]
     [Fact]
     public void EventHandler_CanChangeResult_NullableClass_ToValue_AndNull ()
     public void EventHandler_CanChangeResult_NullableClass_ToValue_AndNull ()
     {
     {
-        EventHandler<ResultEventArgs<MyClass?>> handler = (s, e) => e.Result = new MyClass { Y = 42 };
-        var args = new ResultEventArgs<MyClass?> (null);
+        EventHandler<ResultEventArgs<MyClass?>> handler = (s, e) => e.Result = new() { Y = 42 };
+        ResultEventArgs<MyClass?> args = new (null);
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.NotNull (args.Result);
         Assert.NotNull (args.Result);
         Assert.Equal (42, args.Result?.Y);
         Assert.Equal (42, args.Result?.Y);
 
 
         handler = (s, e) => e.Result = null;
         handler = (s, e) => e.Result = null;
-        args = new ResultEventArgs<MyClass?> (new MyClass { Y = 99 });
+        args = new (new() { Y = 99 });
         handler.Invoke (this, args);
         handler.Invoke (this, args);
         Assert.Null (args.Result);
         Assert.Null (args.Result);
     }
     }

+ 170 - 0
Tests/UnitTestsParallelizable/Application/InitTests.cs

@@ -0,0 +1,170 @@
+using Xunit.Abstractions;
+
+namespace ApplicationTests.Init;
+
+/// <summary>
+///     Comprehensive tests for ApplicationImpl.Begin/End logic that manages Current and SessionStack.
+///     These tests ensure the fragile state management logic is robust and catches regressions.
+///     Tests work directly with ApplicationImpl instances to avoid global Application state issues.
+/// </summary>
+public class InitTests (ITestOutputHelper output)
+{
+    private readonly ITestOutputHelper _output = output;
+    
+    [Fact]
+    public void Init_Unbalanced_Throws ()
+    {
+        IApplication? app = Application.Create ();
+        app.Init ("fake");
+
+        Assert.Throws<InvalidOperationException> (() =>
+                                                      app.Init ("fake")
+                                                 );
+    }
+
+    [Fact]
+    public void Init_Null_Driver_Should_Pick_A_Driver ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ();
+
+        Assert.NotNull (app.Driver);
+
+        app.Dispose ();
+    }
+
+    [Fact]
+    public void Init_Dispose_Cleans_Up ()
+    {
+        IApplication app = Application.Create ();
+
+        app.Init ("fake");
+
+        app.Dispose ();
+
+#if DEBUG_IDISPOSABLE
+        // Validate there are no outstanding Responder-based instances 
+        // after cleanup
+        // Note: We can't check View.Instances in parallel tests as it's a static field
+        // that would be shared across parallel test runs
+#endif
+    }
+
+    [Fact]
+    public void Init_Dispose_Fire_InitializedChanged ()
+    {
+        var initialized = false;
+        var Dispose = false;
+
+        IApplication app = Application.Create ();
+
+        app.InitializedChanged += OnApplicationOnInitializedChanged;
+
+        app.Init (driverName: "fake");
+        Assert.True (initialized);
+        Assert.False (Dispose);
+
+        app.Dispose ();
+        Assert.True (initialized);
+        Assert.True (Dispose);
+
+        app.InitializedChanged -= OnApplicationOnInitializedChanged;
+
+        return;
+
+        void OnApplicationOnInitializedChanged (object? s, EventArgs<bool> a)
+        {
+            if (a.Value)
+            {
+                initialized = true;
+            }
+            else
+            {
+                Dispose = true;
+            }
+        }
+    }
+
+    [Fact]
+    public void Init_KeyBindings_Are_Not_Reset ()
+    {
+        IApplication app = Application.Create ();
+
+        // Set via Keyboard property (modern API)
+        app.Keyboard.QuitKey = Key.Q;
+        Assert.Equal (Key.Q, app.Keyboard.QuitKey);
+
+        app.Init ("fake");
+
+        Assert.Equal (Key.Q, app.Keyboard.QuitKey);
+
+        app.Dispose ();
+    }
+
+    [Fact]
+    public void Init_NoParam_ForceDriver_Works ()
+    {
+        using IApplication app = Application.Create ();
+
+        app.ForceDriver = "fake";
+        // Note: Init() without params picks up driver configuration
+        app.Init ();
+
+        Assert.Equal ("fake", app.Driver!.GetName ());
+    }
+
+    [Fact]
+    public void Init_Dispose_Resets_Instance_Properties ()
+    {
+        IApplication app = Application.Create ();
+
+        // Init the app
+        app.Init (driverName: "fake");
+
+        // Verify initialized
+        Assert.True (app.Initialized);
+        Assert.NotNull (app.Driver);
+
+        // Dispose cleans up
+        app.Dispose ();
+
+        // Check reset state on the instance
+        CheckReset (app);
+
+        // Create a new instance and set values
+        app = Application.Create ();
+        app.Init ("fake");
+
+        app.StopAfterFirstIteration = true;
+        app.Keyboard.PrevTabGroupKey = Key.A;
+        app.Keyboard.NextTabGroupKey = Key.B;
+        app.Keyboard.QuitKey = Key.C;
+        app.Keyboard.KeyBindings.Add (Key.D, Command.Cancel);
+
+        app.Mouse.CachedViewsUnderMouse.Clear ();
+        app.Mouse.LastMousePosition = new Point (1, 1);
+
+        // Dispose and check reset
+        app.Dispose ();
+        CheckReset (app);
+
+        return;
+
+        void CheckReset (IApplication application)
+        {
+            // Check that all fields and properties are reset on the instance
+
+            // Public Properties
+            Assert.Null (application.TopRunnableView);
+            Assert.Null (application.Mouse.MouseGrabView);
+            Assert.Null (application.Driver);
+            Assert.False (application.StopAfterFirstIteration);
+
+            // Internal properties
+            Assert.False (application.Initialized);
+            Assert.Null (application.MainThreadId);
+            Assert.Empty (application.Mouse.CachedViewsUnderMouse);
+        }
+    }
+
+}

+ 1 - 1
Tests/UnitTestsParallelizable/Application/KeyboardImplThreadSafetyTests.cs → Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardImplThreadSafetyTests.cs

@@ -1,7 +1,7 @@
 // ReSharper disable AccessToDisposedClosure
 // ReSharper disable AccessToDisposedClosure
 
 
 #nullable enable
 #nullable enable
-namespace ApplicationTests;
+namespace ApplicationTests.Keyboard;
 
 
 /// <summary>
 /// <summary>
 ///     Tests to verify that KeyboardImpl is thread-safe for concurrent access scenarios.
 ///     Tests to verify that KeyboardImpl is thread-safe for concurrent access scenarios.

+ 20 - 3
Tests/UnitTestsParallelizable/Application/KeyboardTests.cs → Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardTests.cs

@@ -1,7 +1,7 @@
 #nullable enable
 #nullable enable
 using Terminal.Gui.App;
 using Terminal.Gui.App;
 
 
-namespace ApplicationTests;
+namespace ApplicationTests.Keyboard;
 
 
 /// <summary>
 /// <summary>
 ///     Parallelizable tests for keyboard handling.
 ///     Parallelizable tests for keyboard handling.
@@ -9,6 +9,23 @@ namespace ApplicationTests;
 /// </summary>
 /// </summary>
 public class KeyboardTests
 public class KeyboardTests
 {
 {
+
+    [Fact]
+    public void Init_CreatesKeybindings ()
+    {
+        IApplication app = Application.Create ();
+
+        app.Keyboard.KeyBindings.Clear ();
+
+        Assert.Empty (app.Keyboard.KeyBindings.GetBindings ());
+
+        app.Init ("fake");
+
+        Assert.NotEmpty (app.Keyboard.KeyBindings.GetBindings ());
+
+        app.Dispose ();
+    }
+
     [Fact]
     [Fact]
     public void Constructor_InitializesKeyBindings ()
     public void Constructor_InitializesKeyBindings ()
     {
     {
@@ -245,7 +262,7 @@ public class KeyboardTests
     }
     }
 
 
     // Migrated from UnitTests/Application/KeyboardTests.cs
     // Migrated from UnitTests/Application/KeyboardTests.cs
-    
+
     [Fact]
     [Fact]
     public void KeyBindings_Add_Adds ()
     public void KeyBindings_Add_Adds ()
     {
     {
@@ -465,7 +482,7 @@ public class KeyboardTests
 
 
         // Get the commands from the old binding
         // Get the commands from the old binding
         Assert.True (keyboard.KeyBindings.TryGet (oldKey, out KeyBinding oldBinding));
         Assert.True (keyboard.KeyBindings.TryGet (oldKey, out KeyBinding oldBinding));
-        Command[] oldCommands = oldBinding.Commands.ToArray ();
+        Command [] oldCommands = oldBinding.Commands.ToArray ();
 
 
         // Act
         // Act
         keyboard.KeyBindings.Replace (oldKey, newKey);
         keyboard.KeyBindings.Replace (oldKey, newKey);

+ 1 - 1
Tests/UnitTestsParallelizable/Application/ApplicationMouseEnterLeaveTests.cs → Tests/UnitTestsParallelizable/Application/Mouse/ApplicationMouseEnterLeaveTests.cs

@@ -1,7 +1,7 @@
 #nullable enable
 #nullable enable
 using System.ComponentModel;
 using System.ComponentModel;
 
 
-namespace ApplicationTests;
+namespace ApplicationTests.Mouse;
 
 
 [Trait ("Category", "Input")]
 [Trait ("Category", "Input")]
 public class ApplicationMouseEnterLeaveTests
 public class ApplicationMouseEnterLeaveTests

+ 24 - 26
Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs → Tests/UnitTestsParallelizable/Application/Mouse/MouseInterfaceTests.cs

@@ -1,8 +1,6 @@
-#nullable enable
-using Terminal.Gui.App;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace ApplicationTests;
+namespace ApplicationTests.Mouse;
 
 
 /// <summary>
 /// <summary>
 ///     Parallelizable tests for IMouse interface.
 ///     Parallelizable tests for IMouse interface.
@@ -93,14 +91,14 @@ public class MouseInterfaceTests (ITestOutputHelper output)
         MouseEventArgs? capturedArgs = null;
         MouseEventArgs? capturedArgs = null;
 
 
         mouse.MouseEvent += (sender, args) =>
         mouse.MouseEvent += (sender, args) =>
-        {
-            eventFired = true;
-            capturedArgs = args;
-        };
+                            {
+                                eventFired = true;
+                                capturedArgs = args;
+                            };
 
 
         MouseEventArgs testEvent = new ()
         MouseEventArgs testEvent = new ()
         {
         {
-            ScreenPosition = new Point (5, 10),
+            ScreenPosition = new (5, 10),
             Flags = MouseFlags.Button1Pressed
             Flags = MouseFlags.Button1Pressed
         };
         };
 
 
@@ -121,13 +119,13 @@ public class MouseInterfaceTests (ITestOutputHelper output)
         MouseImpl mouse = new ();
         MouseImpl mouse = new ();
         var eventCount = 0;
         var eventCount = 0;
 
 
-        void Handler (object? sender, MouseEventArgs args) => eventCount++;
+        void Handler (object? sender, MouseEventArgs args) { eventCount++; }
 
 
         mouse.MouseEvent += Handler;
         mouse.MouseEvent += Handler;
 
 
         MouseEventArgs testEvent = new ()
         MouseEventArgs testEvent = new ()
         {
         {
-            ScreenPosition = new Point (0, 0),
+            ScreenPosition = new (0, 0),
             Flags = MouseFlags.Button1Pressed
             Flags = MouseFlags.Button1Pressed
         };
         };
 
 
@@ -157,7 +155,7 @@ public class MouseInterfaceTests (ITestOutputHelper output)
 
 
         MouseEventArgs testEvent = new ()
         MouseEventArgs testEvent = new ()
         {
         {
-            ScreenPosition = new Point (0, 0),
+            ScreenPosition = new (0, 0),
             Flags = MouseFlags.Button1Pressed
             Flags = MouseFlags.Button1Pressed
         };
         };
 
 
@@ -185,7 +183,7 @@ public class MouseInterfaceTests (ITestOutputHelper output)
 
 
         MouseEventArgs testEvent = new ()
         MouseEventArgs testEvent = new ()
         {
         {
-            ScreenPosition = new Point (5, 5),
+            ScreenPosition = new (5, 5),
             Flags = flags
             Flags = flags
         };
         };
 
 
@@ -231,7 +229,7 @@ public class MouseInterfaceTests (ITestOutputHelper output)
 
 
         MouseEventArgs testEvent = new ()
         MouseEventArgs testEvent = new ()
         {
         {
-            ScreenPosition = new Point (0, 0),
+            ScreenPosition = new (0, 0),
             Flags = MouseFlags.Button1Pressed
             Flags = MouseFlags.Button1Pressed
         };
         };
 
 
@@ -300,7 +298,7 @@ public class MouseInterfaceTests (ITestOutputHelper output)
 
 
         MouseEventArgs testEvent = new ()
         MouseEventArgs testEvent = new ()
         {
         {
-            ScreenPosition = new Point (0, 0),
+            ScreenPosition = new (0, 0),
             Flags = MouseFlags.Button1Pressed
             Flags = MouseFlags.Button1Pressed
         };
         };
 
 
@@ -380,10 +378,10 @@ public class MouseInterfaceTests (ITestOutputHelper output)
         var eventFired = false;
         var eventFired = false;
 
 
         mouse.GrabbingMouse += (sender, args) =>
         mouse.GrabbingMouse += (sender, args) =>
-        {
-            eventFired = true;
-            args.Cancel = true;
-        };
+                               {
+                                   eventFired = true;
+                                   args.Cancel = true;
+                               };
 
 
         // Act
         // Act
         mouse.GrabMouse (testView);
         mouse.GrabMouse (testView);
@@ -403,10 +401,10 @@ public class MouseInterfaceTests (ITestOutputHelper output)
         View? eventView = null;
         View? eventView = null;
 
 
         mouse.GrabbedMouse += (sender, args) =>
         mouse.GrabbedMouse += (sender, args) =>
-        {
-            eventFired = true;
-            eventView = args.View;
-        };
+                              {
+                                  eventFired = true;
+                                  eventView = args.View;
+                              };
 
 
         // Act
         // Act
         mouse.GrabMouse (testView);
         mouse.GrabMouse (testView);
@@ -428,10 +426,10 @@ public class MouseInterfaceTests (ITestOutputHelper output)
         View? eventView = null;
         View? eventView = null;
 
 
         mouse.UnGrabbedMouse += (sender, args) =>
         mouse.UnGrabbedMouse += (sender, args) =>
-        {
-            eventFired = true;
-            eventView = args.View;
-        };
+                                {
+                                    eventFired = true;
+                                    eventView = args.View;
+                                };
 
 
         // Act
         // Act
         mouse.UngrabMouse ();
         mouse.UngrabMouse ();

+ 35 - 37
Tests/UnitTestsParallelizable/Application/MouseTests.cs → Tests/UnitTestsParallelizable/Application/Mouse/MouseTests.cs

@@ -1,6 +1,4 @@
-using Xunit.Abstractions;
-
-namespace ApplicationTests;
+namespace ApplicationTests.Mouse;
 
 
 /// <summary>
 /// <summary>
 ///     Tests for the <see cref="IMouse"/> interface and <see cref="MouseImpl"/> implementation.
 ///     Tests for the <see cref="IMouse"/> interface and <see cref="MouseImpl"/> implementation.
@@ -128,46 +126,46 @@ public class MouseTests
     [Theory]
     [Theory]
 
 
     // click on border
     // 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
     // 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
     // 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
     // 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
     // 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
     // view is offset from origin ; click inside border
     // our view is 10x10, but has a border, so it's bounds is 8x8
     // 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
     //01234567890123456789
     // |12345678|
     // |12345678|
@@ -178,13 +176,13 @@ public class MouseTests
         int clickY,
         int clickY,
         int expectedX,
         int expectedX,
         int expectedY,
         int expectedY,
-        bool expectedClicked
+        int expectedClickedCount
     )
     )
     {
     {
         Size size = new (10, 10);
         Size size = new (10, 10);
         Point pos = new (offset, offset);
         Point pos = new (offset, offset);
 
 
-        var clicked = false;
+        int clickedCount = 0;
 
 
         using IApplication? application = Application.Create ();
         using IApplication? application = Application.Create ();
 
 
@@ -208,14 +206,14 @@ public class MouseTests
 
 
         var mouseEvent = new MouseEventArgs { Position = new (clickX, clickY), ScreenPosition = new (clickX, clickY), Flags = MouseFlags.Button1Clicked };
         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 (expectedX, e.Position.X);
             Assert.Equal (expectedY, e.Position.Y);
             Assert.Equal (expectedY, e.Position.Y);
-            clicked = true;
+            clickedCount += e.IsSingleDoubleOrTripleClicked ? 1 : 0;
         };
         };
 
 
         application.Mouse.RaiseMouseEvent (mouseEvent);
         application.Mouse.RaiseMouseEvent (mouseEvent);
-        Assert.Equal (expectedClicked, clicked);
+        Assert.Equal (expectedClickedCount, clickedCount);
     }
     }
 }
 }

+ 1 - 1
Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs → Tests/UnitTestsParallelizable/Application/Popover/Application.PopoverTests.cs

@@ -2,7 +2,7 @@
 using Moq;
 using Moq;
 using Terminal.Gui.App;
 using Terminal.Gui.App;
 
 
-namespace ApplicationTests;
+namespace ApplicationTests.Popover;
 
 
 public class ApplicationPopoverTests
 public class ApplicationPopoverTests
 {
 {

+ 4 - 8
Tests/UnitTestsParallelizable/Application/PopoverBaseImplTests.cs → Tests/UnitTestsParallelizable/Application/Popover/PopoverBaseImplTests.cs

@@ -1,13 +1,10 @@
-using System;
-using Terminal.Gui;
-using Terminal.Gui.App;
-using Xunit;
-namespace ApplicationTests;
+namespace ApplicationTests.Popover;
 
 
 public class PopoverBaseImplTests
 public class PopoverBaseImplTests
 {
 {
     // Minimal concrete implementation for testing
     // Minimal concrete implementation for testing
-    private class TestPopover : PopoverBaseImpl { }
+    private class TestPopover : PopoverBaseImpl
+    { }
 
 
     [Fact]
     [Fact]
     public void Constructor_SetsDefaults ()
     public void Constructor_SetsDefaults ()
@@ -40,12 +37,11 @@ public class PopoverBaseImplTests
         popover.ViewportSettings = ViewportSettingsFlags.None; // Remove required flags
         popover.ViewportSettings = ViewportSettingsFlags.None; // Remove required flags
 
 
         var popoverManager = new ApplicationPopover ();
         var popoverManager = new ApplicationPopover ();
+
         // Test missing Transparent flags
         // Test missing Transparent flags
         Assert.ThrowsAny<Exception> (() => popoverManager.Show (popover));
         Assert.ThrowsAny<Exception> (() => popoverManager.Show (popover));
-
     }
     }
 
 
-
     [Fact]
     [Fact]
     public void Show_ThrowsIfPopoverMissingQuitCommand ()
     public void Show_ThrowsIfPopoverMissingQuitCommand ()
     {
     {

+ 252 - 0
Tests/UnitTestsParallelizable/Application/RunTests.cs

@@ -0,0 +1,252 @@
+#nullable enable
+using Xunit.Abstractions;
+
+namespace ApplicationTests;
+
+public class RunTests
+{
+    [Fact]
+    public void Run_RequestStop_Stops ()
+    {
+        IApplication? app = Application.Create ();
+        app.Init ("fake");
+
+        var top = new Runnable ();
+        SessionToken? sessionToken = app.Begin (top);
+        Assert.NotNull (sessionToken);
+
+        app.Iteration += OnApplicationOnIteration;
+        app.Run (top);
+        app.Iteration -= OnApplicationOnIteration;
+
+        top.Dispose ();
+
+        return;
+
+        void OnApplicationOnIteration (object? s, EventArgs<IApplication?> a) { app.RequestStop (); }
+    }
+
+    [Fact]
+    public void Run_T_Init_Driver_Cleared_with_Runnable_Throws ()
+    {
+        IApplication? app = Application.Create ();
+
+        app.Init ("fake");
+        app.Driver = null;
+
+        app.StopAfterFirstIteration = true;
+
+        // Init has been called, but Driver has been set to null. Bad.
+        Assert.Throws<InvalidOperationException> (() => app.Run<Runnable> ());
+    }
+
+    [Fact]
+    public void Run_Iteration_Fires ()
+    {
+        var iteration = 0;
+
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        app.Iteration += Application_Iteration;
+        app.Run<Runnable> ();
+        app.Iteration -= Application_Iteration;
+
+        Assert.Equal (1, iteration);
+        app.Dispose ();
+
+        return;
+
+        void Application_Iteration (object? sender, EventArgs<IApplication?> e)
+        {
+
+            iteration++;
+            app.RequestStop ();
+        }
+    }
+
+
+    [Fact]
+    public void Run_T_After_InitWithDriver_with_Runnable_and_Driver_Does_Not_Throw ()
+    {
+        IApplication app = Application.Create ();
+        app.StopAfterFirstIteration = true;
+
+        // Run<Runnable<bool>> when already initialized or not with a Driver will not throw (because Window is derived from Runnable)
+        // Using another type not derived from Runnable will throws at compile time
+        app.Run<Window> (null, "fake");
+
+        // Run<Runnable<bool>> when already initialized or not with a Driver will not throw (because Dialog is derived from Runnable)
+        app.Run<Dialog> (null, "fake");
+
+        app.Dispose ();
+    }
+
+    [Fact]
+    public void Run_T_After_Init_Does_Not_Disposes_Application_Top ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        // Init doesn't create a Runnable and assigned it to app.TopRunnable
+        // but Begin does
+        var initTop = new Runnable ();
+
+        app.Iteration += OnApplicationOnIteration;
+
+        app.Run<Runnable> ();
+        app.Iteration -= OnApplicationOnIteration;
+
+#if DEBUG_IDISPOSABLE
+        Assert.False (initTop.WasDisposed);
+        initTop.Dispose ();
+        Assert.True (initTop.WasDisposed);
+#endif
+        initTop.Dispose ();
+
+        app.Dispose ();
+
+        return;
+
+        void OnApplicationOnIteration (object? s, EventArgs<IApplication?> a)
+        {
+            Assert.NotEqual (initTop, app.TopRunnableView);
+#if DEBUG_IDISPOSABLE
+            Assert.False (initTop.WasDisposed);
+#endif
+            app.RequestStop ();
+        }
+    }
+
+    [Fact]
+    public void Run_T_After_InitWithDriver_with_TestRunnable_DoesNotThrow ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+        app.StopAfterFirstIteration = true;
+
+        // Init has been called and we're passing no driver to Run<TestRunnable>. This is ok.
+        app.Run<Window> ();
+
+        app.Dispose ();
+    }
+
+    [Fact]
+    public void Run_T_After_InitNullDriver_with_TestRunnable_DoesNotThrow ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+        app.StopAfterFirstIteration = true;
+
+        // Init has been called, selecting FakeDriver; we're passing no driver to Run<TestRunnable>. Should be fine.
+        app.Run<Window> ();
+
+        app.Dispose ();
+    }
+
+    [Fact]
+    public void Run_T_NoInit_DoesNotThrow ()
+    {
+        IApplication app = Application.Create ();
+        app.StopAfterFirstIteration = true;
+
+        app.Run<Window> ();
+
+        app.Dispose ();
+    }
+
+    [Fact]
+    public void Run_T_NoInit_WithDriver_DoesNotThrow ()
+    {
+        IApplication app = Application.Create ();
+        app.StopAfterFirstIteration = true;
+
+        // Init has NOT been called and we're passing a valid driver to Run<TestRunnable>. This is ok.
+        app.Run<Runnable> (null, "fake");
+
+        app.Dispose ();
+    }
+
+    [Fact]
+    public void Run_Sets_Running_True ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        var top = new Runnable ();
+        SessionToken? rs = app.Begin (top);
+        Assert.NotNull (rs);
+
+        app.Iteration += OnApplicationOnIteration;
+        app.Run (top);
+        app.Iteration -= OnApplicationOnIteration;
+
+        top.Dispose ();
+
+        app.Dispose ();
+
+        return;
+
+        void OnApplicationOnIteration (object? s, EventArgs<IApplication?> a)
+        {
+            Assert.True (top.IsRunning);
+            top.RequestStop ();
+        }
+    }
+
+    [Fact]
+    public void Run_A_Modal_Runnable_Refresh_Background_On_Moving ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        // Don't use Dialog here as it has more layout logic. Use Window instead.
+        var w = new Window
+        {
+            Width = 5, Height = 5,
+            Arrangement = ViewArrangement.Movable
+        };
+        app.Driver!.SetScreenSize (10, 10);
+        SessionToken? rs = app.Begin (w);
+
+        // Don't use visuals to test as style of border can change over time.
+        Assert.Equal (new (0, 0), w.Frame.Location);
+
+        app.Mouse.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
+        Assert.Equal (w.Border, app.Mouse.MouseGrabView);
+        Assert.Equal (new (0, 0), w.Frame.Location);
+
+        // Move down and to the right.
+        app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (1, 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition });
+        Assert.Equal (new (1, 1), w.Frame.Location);
+
+        app.End (rs!);
+        w.Dispose ();
+
+        app.Dispose ();
+    }
+
+    [Fact]
+    public void Run_T_Creates_Top_Without_Init ()
+    {
+        IApplication app = Application.Create ();
+        app.StopAfterFirstIteration = true;
+
+        app.SessionEnded += OnApplicationOnSessionEnded;
+
+        app.Run<Window> (null, "fake");
+
+        Assert.Null (app.TopRunnableView);
+
+        app.Dispose ();
+        Assert.Null (app.TopRunnableView);
+
+        return;
+
+        void OnApplicationOnSessionEnded (object? sender, SessionTokenEventArgs e)
+        {
+            app.SessionEnded -= OnApplicationOnSessionEnded;
+            e.State.Result = (e.State.Runnable as IRunnable<object?>)?.Result;
+        }
+    }
+}

+ 2 - 3
Tests/UnitTestsParallelizable/Application/Runnable/RunnableEdgeCasesTests.cs

@@ -1,7 +1,6 @@
-#nullable enable
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace ApplicationTests;
+namespace ApplicationTests.RunnableTests;
 
 
 /// <summary>
 /// <summary>
 ///     Tests for edge cases and error conditions in IRunnable implementation.
 ///     Tests for edge cases and error conditions in IRunnable implementation.
@@ -9,7 +8,7 @@ namespace ApplicationTests;
 public class RunnableEdgeCasesTests (ITestOutputHelper output)
 public class RunnableEdgeCasesTests (ITestOutputHelper output)
 {
 {
     private readonly ITestOutputHelper _output = output;
     private readonly ITestOutputHelper _output = output;
-    
+
     [Fact]
     [Fact]
     public void Runnable_MultipleEventSubscribers_AllInvoked ()
     public void Runnable_MultipleEventSubscribers_AllInvoked ()
     {
     {

+ 44 - 40
Tests/UnitTestsParallelizable/Application/Runnable/RunnableIntegrationTests.cs

@@ -1,28 +1,19 @@
 #nullable enable
 #nullable enable
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace ApplicationTests;
+namespace ApplicationTests.RunnableTests;
 
 
 /// <summary>
 /// <summary>
 ///     Integration tests for IApplication's IRunnable support.
 ///     Integration tests for IApplication's IRunnable support.
 ///     Tests the full lifecycle of IRunnable instances through Application methods.
 ///     Tests the full lifecycle of IRunnable instances through Application methods.
 /// </summary>
 /// </summary>
-public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : IDisposable
+public class ApplicationRunnableIntegrationTests
 {
 {
-    private readonly ITestOutputHelper _output = output;
-    private IApplication? _app;
-
-    public void Dispose ()
-    {
-        _app?.Dispose ();
-        _app = null;
-    }
-
     [Fact]
     [Fact]
     public void Begin_AddsRunnableToStack ()
     public void Begin_AddsRunnableToStack ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
         int stackCountBefore = app.SessionStack?.Count ?? 0;
         int stackCountBefore = app.SessionStack?.Count ?? 0;
 
 
@@ -43,7 +34,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void Begin_CanBeCanceled_ByIsRunningChanging ()
     public void Begin_CanBeCanceled_ByIsRunningChanging ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         CancelableRunnable runnable = new () { CancelStart = true };
         CancelableRunnable runnable = new () { CancelStart = true };
 
 
         // Act
         // Act
@@ -60,7 +51,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void Begin_RaisesIsModalChangedEvent ()
     public void Begin_RaisesIsModalChangedEvent ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
         var isModalChangedRaised = false;
         var isModalChangedRaised = false;
         bool? receivedValue = null;
         bool? receivedValue = null;
@@ -86,7 +77,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void Begin_RaisesIsRunningChangedEvent ()
     public void Begin_RaisesIsRunningChangedEvent ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
         var isRunningChangedRaised = false;
         var isRunningChangedRaised = false;
         bool? receivedValue = null;
         bool? receivedValue = null;
@@ -112,7 +103,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void Begin_RaisesIsRunningChangingEvent ()
     public void Begin_RaisesIsRunningChangingEvent ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
         var isRunningChangingRaised = false;
         var isRunningChangingRaised = false;
         bool? oldValue = null;
         bool? oldValue = null;
@@ -141,7 +132,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void Begin_SetsIsModalToTrue ()
     public void Begin_SetsIsModalToTrue ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
 
 
         // Act
         // Act
@@ -158,7 +149,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void Begin_SetsIsRunningToTrue ()
     public void Begin_SetsIsRunningToTrue ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
 
 
         // Act
         // Act
@@ -175,7 +166,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void Begin_ThrowsOnNullRunnable ()
     public void Begin_ThrowsOnNullRunnable ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
 
 
         // Act & Assert
         // Act & Assert
         Assert.Throws<ArgumentNullException> (() => app.Begin ((IRunnable)null!));
         Assert.Throws<ArgumentNullException> (() => app.Begin ((IRunnable)null!));
@@ -185,7 +176,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void End_CanBeCanceled_ByIsRunningChanging ()
     public void End_CanBeCanceled_ByIsRunningChanging ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         CancelableRunnable runnable = new () { CancelStop = true };
         CancelableRunnable runnable = new () { CancelStop = true };
         SessionToken? token = app.Begin (runnable);
         SessionToken? token = app.Begin (runnable);
         runnable.CancelStop = true; // Enable cancellation
         runnable.CancelStop = true; // Enable cancellation
@@ -205,7 +196,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void End_ClearsTokenRunnable ()
     public void End_ClearsTokenRunnable ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
         SessionToken? token = app.Begin (runnable);
         SessionToken? token = app.Begin (runnable);
 
 
@@ -220,7 +211,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void End_RaisesIsRunningChangedEvent ()
     public void End_RaisesIsRunningChangedEvent ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
         SessionToken? token = app.Begin (runnable);
         SessionToken? token = app.Begin (runnable);
         var isRunningChangedRaised = false;
         var isRunningChangedRaised = false;
@@ -244,7 +235,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void End_RaisesIsRunningChangingEvent ()
     public void End_RaisesIsRunningChangingEvent ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
         SessionToken? token = app.Begin (runnable);
         SessionToken? token = app.Begin (runnable);
         var isRunningChangingRaised = false;
         var isRunningChangingRaised = false;
@@ -271,7 +262,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void End_RemovesRunnableFromStack ()
     public void End_RemovesRunnableFromStack ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
         SessionToken? token = app.Begin (runnable);
         SessionToken? token = app.Begin (runnable);
         int stackCountBefore = app.SessionStack?.Count ?? 0;
         int stackCountBefore = app.SessionStack?.Count ?? 0;
@@ -287,7 +278,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void End_SetsIsModalToFalse ()
     public void End_SetsIsModalToFalse ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
         SessionToken? token = app.Begin (runnable);
         SessionToken? token = app.Begin (runnable);
 
 
@@ -302,7 +293,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void End_SetsIsRunningToFalse ()
     public void End_SetsIsRunningToFalse ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable = new ();
         Runnable<int> runnable = new ();
         SessionToken? token = app.Begin (runnable);
         SessionToken? token = app.Begin (runnable);
 
 
@@ -317,17 +308,33 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void End_ThrowsOnNullToken ()
     public void End_ThrowsOnNullToken ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
 
 
         // Act & Assert
         // Act & Assert
         Assert.Throws<ArgumentNullException> (() => app.End ((SessionToken)null!));
         Assert.Throws<ArgumentNullException> (() => app.End ((SessionToken)null!));
     }
     }
 
 
+    [Fact]
+    public void End_ClearsMouseGrabView ()
+    {
+        // Arrange
+        IApplication app = CreateAndInitApp ();
+
+        Runnable<int> runnable = new ();
+        SessionToken? token = app.Begin (runnable);
+        app.Mouse.GrabMouse (runnable);
+        app.End (token!);
+
+        Assert.Null (app.Mouse.MouseGrabView);
+
+        runnable.Dispose ();
+        app.Dispose ();
+    }
+
     [Fact]
     [Fact]
     public void MultipleRunnables_IndependentResults ()
     public void MultipleRunnables_IndependentResults ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
         Runnable<int> runnable1 = new ();
         Runnable<int> runnable1 = new ();
         Runnable<string> runnable2 = new ();
         Runnable<string> runnable2 = new ();
 
 
@@ -344,7 +351,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void NestedBegin_MaintainsStackOrder ()
     public void NestedBegin_MaintainsStackOrder ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable1 = new () { Id = "1" };
         Runnable<int> runnable1 = new () { Id = "1" };
         Runnable<int> runnable2 = new () { Id = "2" };
         Runnable<int> runnable2 = new () { Id = "2" };
 
 
@@ -367,7 +374,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void NestedEnd_RestoresPreviousModal ()
     public void NestedEnd_RestoresPreviousModal ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable1 = new () { Id = "1" };
         Runnable<int> runnable1 = new () { Id = "1" };
         Runnable<int> runnable2 = new () { Id = "2" };
         Runnable<int> runnable2 = new () { Id = "2" };
         SessionToken token1 = app.Begin (runnable1)!;
         SessionToken token1 = app.Begin (runnable1)!;
@@ -390,7 +397,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void RequestStop_WithIRunnable_WorksCorrectly ()
     public void RequestStop_WithIRunnable_WorksCorrectly ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         StoppableRunnable runnable = new ();
         StoppableRunnable runnable = new ();
         SessionToken? token = app.Begin (runnable);
         SessionToken? token = app.Begin (runnable);
 
 
@@ -409,7 +416,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void RequestStop_WithNull_UsesTopRunnable ()
     public void RequestStop_WithNull_UsesTopRunnable ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         StoppableRunnable runnable = new ();
         StoppableRunnable runnable = new ();
         SessionToken? token = app.Begin (runnable);
         SessionToken? token = app.Begin (runnable);
 
 
@@ -427,7 +434,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void RunGeneric_CreatesAndReturnsRunnable ()
     public void RunGeneric_CreatesAndReturnsRunnable ()
     {
     {
         // Arrange
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         app.StopAfterFirstIteration = true;
         app.StopAfterFirstIteration = true;
 
 
         // Act - With fluent API, Run<T>() returns IApplication for chaining
         // Act - With fluent API, Run<T>() returns IApplication for chaining
@@ -456,15 +463,12 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
         app.Dispose ();
         app.Dispose ();
     }
     }
 
 
-    private IApplication GetApp ()
+    private IApplication CreateAndInitApp ()
     {
     {
-        if (_app is null)
-        {
-            _app = Application.Create ();
-            _app.Init ("fake");
-        }
+        IApplication app = Application.Create ();
+        app.Init ("fake");
 
 
-        return _app;
+        return app;
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 29 - 2
Tests/UnitTestsParallelizable/Application/IApplicationScreenChangedTests.cs → Tests/UnitTestsParallelizable/Application/ScreeenTests.cs

@@ -1,17 +1,44 @@
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace ApplicationTests;
+namespace ApplicationTests.Screen;
 
 
 /// <summary>
 /// <summary>
 ///     Parallelizable tests for IApplication.ScreenChanged event and Screen property.
 ///     Parallelizable tests for IApplication.ScreenChanged event and Screen property.
 ///     Tests using the modern instance-based IApplication API.
 ///     Tests using the modern instance-based IApplication API.
 /// </summary>
 /// </summary>
-public class IApplicationScreenChangedTests (ITestOutputHelper output)
+public class ScreenTests (ITestOutputHelper output)
 {
 {
     private readonly ITestOutputHelper _output = output;
     private readonly ITestOutputHelper _output = output;
 
 
     #region ScreenChanged Event Tests
     #region ScreenChanged Event Tests
 
 
+    [Fact]
+    public void Screen_Size_Changes ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        IDriver? driver = app.Driver;
+
+        app.Driver!.SetScreenSize (80, 25);
+
+        Assert.Equal (new (0, 0, 80, 25), driver!.Screen);
+        Assert.Equal (new (0, 0, 80, 25), app.Screen);
+
+        // TODO: Should not be possible to manually change these at whim!
+        driver.Cols = 100;
+        driver.Rows = 30;
+
+        app.Driver!.SetScreenSize (100, 30);
+
+        Assert.Equal (new (0, 0, 100, 30), driver.Screen);
+
+        app.Screen = new (0, 0, driver.Cols, driver.Rows);
+        Assert.Equal (new (0, 0, 100, 30), driver.Screen);
+
+        app.Dispose ();
+    }
+
     [Fact]
     [Fact]
     public void ScreenChanged_Event_Fires_When_Driver_Size_Changes ()
     public void ScreenChanged_Event_Fires_When_Driver_Size_Changes ()
     {
     {

+ 1 - 1
Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs → Tests/UnitTestsParallelizable/Application/Timeouts/LogarithmicTimeoutTests.cs

@@ -1,4 +1,4 @@
-namespace ApplicationTests;
+namespace ApplicationTests.Timeout;
 
 
 public class LogarithmicTimeoutTests
 public class LogarithmicTimeoutTests
 {
 {

+ 35 - 32
Tests/UnitTestsParallelizable/Application/NestedRunTimeoutTests.cs → Tests/UnitTestsParallelizable/Application/Timeouts/NestedRunTimeoutTests.cs

@@ -19,7 +19,7 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
         List<string> executionOrder = new ();
         List<string> executionOrder = new ();
 
 
         var mainWindow = new Window { Title = "Main Window" };
         var mainWindow = new Window { Title = "Main Window" };
-        var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new() { Text = "Ok" }] };
+        var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new () { Text = "Ok" }] };
         var nestedRunCompleted = false;
         var nestedRunCompleted = false;
 
 
         // Use iteration counter for safety instead of time-based timeout
         // Use iteration counter for safety instead of time-based timeout
@@ -158,17 +158,17 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
         var mainWindow = new Window { Title = "Main Window" };
         var mainWindow = new Window { Title = "Main Window" };
 
 
         // Create a dialog for the nested run loop
         // Create a dialog for the nested run loop
-        var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new() { Text = "Ok" }] };
+        var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new () { Text = "Ok" }] };
 
 
         // Schedule a safety timeout that will ensure the app quits if test hangs
         // Schedule a safety timeout that will ensure the app quits if test hangs
-        var requestStopTimeoutFired = false;
+        var safetyRequestStopTimeoutFired = false;
 
 
         app.AddTimeout (
         app.AddTimeout (
                         TimeSpan.FromMilliseconds (10000),
                         TimeSpan.FromMilliseconds (10000),
                         () =>
                         () =>
                         {
                         {
-                            output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
-                            requestStopTimeoutFired = true;
+                            output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long - Assuming slow environment. Skipping assertions.");
+                            safetyRequestStopTimeoutFired = true;
                             app.RequestStop ();
                             app.RequestStop ();
 
 
                             return false;
                             return false;
@@ -217,12 +217,13 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
         // Act - Start the main run loop
         // Act - Start the main run loop
         app.Run (mainWindow);
         app.Run (mainWindow);
 
 
-        // Assert
-        Assert.True (nestedRunStarted, "Nested run should have started");
-        Assert.True (timeoutFired, "Timeout should have fired during nested run");
-        Assert.True (nestedRunEnded, "Nested run should have ended");
-
-        Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
+        if (!safetyRequestStopTimeoutFired)
+        {
+            // Assert
+            Assert.True (nestedRunStarted, "Nested run should have started");
+            Assert.True (timeoutFired, "Timeout should have fired during nested run");
+            Assert.True (nestedRunEnded, "Nested run should have ended");
+        }
 
 
         dialog.Dispose ();
         dialog.Dispose ();
         mainWindow.Dispose ();
         mainWindow.Dispose ();
@@ -273,14 +274,14 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
         app.Init ("FakeDriver");
         app.Init ("FakeDriver");
 
 
         // Schedule a safety timeout that will ensure the app quits if test hangs
         // Schedule a safety timeout that will ensure the app quits if test hangs
-        var requestStopTimeoutFired = false;
+        var safetyRequestStopTimeoutFired = false;
 
 
         app.AddTimeout (
         app.AddTimeout (
                         TimeSpan.FromMilliseconds (10000),
                         TimeSpan.FromMilliseconds (10000),
                         () =>
                         () =>
                         {
                         {
-                            output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
-                            requestStopTimeoutFired = true;
+                            output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long - Assuming slow environment. Skipping assertions.");
+                            safetyRequestStopTimeoutFired = true;
                             app.RequestStop ();
                             app.RequestStop ();
 
 
                             return false;
                             return false;
@@ -288,7 +289,7 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
                        );
                        );
 
 
         var mainWindow = new Window { Title = "Main Window" };
         var mainWindow = new Window { Title = "Main Window" };
-        var dialog = new Dialog { Title = "Dialog", Buttons = [new() { Text = "Ok" }] };
+        var dialog = new Dialog { Title = "Dialog", Buttons = [new () { Text = "Ok" }] };
 
 
         var initialTimeoutCount = 0;
         var initialTimeoutCount = 0;
         var timeoutCountDuringNestedRun = 0;
         var timeoutCountDuringNestedRun = 0;
@@ -349,12 +350,13 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
         // Assert
         // Assert
         output.WriteLine ($"Final counts - Initial: {initialTimeoutCount}, During: {timeoutCountDuringNestedRun}, After: {timeoutCountAfterNestedRun}");
         output.WriteLine ($"Final counts - Initial: {initialTimeoutCount}, During: {timeoutCountDuringNestedRun}, After: {timeoutCountAfterNestedRun}");
 
 
-        // The timeout queue should have pending timeouts throughout
-        Assert.True (initialTimeoutCount >= 0, "Should have timeouts in queue initially");
-        Assert.True (timeoutCountDuringNestedRun >= 0, "Should have timeouts in queue during nested run");
-        Assert.True (timeoutCountAfterNestedRun >= 0, "Should have timeouts in queue after nested run");
-
-        Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
+        if (!safetyRequestStopTimeoutFired)
+        {
+            // The timeout queue should have pending timeouts throughout
+            Assert.True (initialTimeoutCount >= 0, "Should have timeouts in queue initially");
+            Assert.True (timeoutCountDuringNestedRun >= 0, "Should have timeouts in queue during nested run");
+            Assert.True (timeoutCountAfterNestedRun >= 0, "Should have timeouts in queue after nested run");
+        }
 
 
         dialog.Dispose ();
         dialog.Dispose ();
         mainWindow.Dispose ();
         mainWindow.Dispose ();
@@ -378,17 +380,17 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
         var messageBoxClosed = false;
         var messageBoxClosed = false;
 
 
         var mainWindow = new Window { Title = "Login Window" };
         var mainWindow = new Window { Title = "Login Window" };
-        var messageBox = new Dialog { Title = "Success", Buttons = [new() { Text = "Ok" }] };
+        var messageBox = new Dialog { Title = "Success", Buttons = [new () { Text = "Ok" }] };
 
 
         // Schedule a safety timeout that will ensure the app quits if test hangs
         // Schedule a safety timeout that will ensure the app quits if test hangs
-        var requestStopTimeoutFired = false;
+        var safetyRequestStopTimeoutFired = false;
 
 
         app.AddTimeout (
         app.AddTimeout (
                         TimeSpan.FromMilliseconds (10000),
                         TimeSpan.FromMilliseconds (10000),
                         () =>
                         () =>
                         {
                         {
-                            output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
-                            requestStopTimeoutFired = true;
+                            output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long - Assuming slow environment. Skipping assertions.");
+                            safetyRequestStopTimeoutFired = true;
                             app.RequestStop ();
                             app.RequestStop ();
 
 
                             return false;
                             return false;
@@ -448,13 +450,14 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
         // Act
         // Act
         app.Run (mainWindow);
         app.Run (mainWindow);
 
 
-        // Assert
-        Assert.True (enterFired, "Enter timeout should have fired");
-        Assert.True (messageBoxShown, "MessageBox should have been shown");
-        Assert.True (escFired, "ESC timeout should have fired during MessageBox"); // THIS WAS THE BUG - NOW FIXED!
-        Assert.True (messageBoxClosed, "MessageBox should have been closed");
-
-        Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
+        if (!safetyRequestStopTimeoutFired)
+        {
+            // Assert
+            Assert.True (enterFired, "Enter timeout should have fired");
+            Assert.True (messageBoxShown, "MessageBox should have been shown");
+            Assert.True (escFired, "ESC timeout should have fired during MessageBox"); // THIS WAS THE BUG - NOW FIXED!
+            Assert.True (messageBoxClosed, "MessageBox should have been closed");
+        }
 
 
         messageBox.Dispose ();
         messageBox.Dispose ();
         mainWindow.Dispose ();
         mainWindow.Dispose ();

+ 1 - 2
Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs → Tests/UnitTestsParallelizable/Application/Timeouts/SmoothAcceleratingTimeoutTests.cs

@@ -1,5 +1,4 @@
-namespace ApplicationTests;
-
+namespace ApplicationTests.Timeout;
 
 
 public class SmoothAcceleratingTimeoutTests
 public class SmoothAcceleratingTimeoutTests
 {
 {

+ 19 - 0
Tests/UnitTestsParallelizable/Application/TimeoutTests.cs → Tests/UnitTestsParallelizable/Application/Timeouts/TimeoutTests.cs

@@ -853,4 +853,23 @@ public class TimeoutTests
             }
             }
         }
         }
     }
     }
+
+
+    [Fact]
+    public void Invoke_Adds_Idle ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        Runnable top = new ();
+        SessionToken? rs = app.Begin (top);
+
+        var actionCalled = 0;
+        app.Invoke ((_) => { actionCalled++; });
+        app.TimedEvents!.RunTimers ();
+        Assert.Equal (1, actionCalled);
+        top.Dispose ();
+
+        app.Dispose ();
+    }
 }
 }

+ 32 - 4
Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs

@@ -3,14 +3,10 @@ using System.Text;
 using UnitTests;
 using UnitTests;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-// Alias Console to MockConsole so we don't accidentally use Console
-
 namespace DriverTests;
 namespace DriverTests;
 
 
 public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
 public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
 {
 {
-    private readonly ITestOutputHelper _output = output;
-
     [Fact]
     [Fact]
     public void AddRune ()
     public void AddRune ()
     {
     {
@@ -179,4 +175,36 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
 
 
         driver.Dispose ();
         driver.Dispose ();
     }
     }
+
+    [Fact]
+    public void AddStr_Glyph_On_Second_Cell_Of_Wide_Glyph_Outputs_Correctly ()
+    {
+        IDriver? driver = CreateFakeDriver ();
+        driver.SetScreenSize (6, 3);
+
+        driver!.Clip = new (driver.Screen);
+
+        driver.Move (1, 0);
+        driver.AddStr ("┌");
+        driver.Move (2, 0);
+        driver.AddStr ("─");
+        driver.Move (3, 0);
+        driver.AddStr ("┐");
+        driver.Clip.Exclude (new Region (new (1, 0, 3, 1)));
+
+        driver.Move (0, 0);
+        driver.AddStr ("🍎🍎🍎🍎");
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              �┌─┐🍎
+                                              """,
+                                              output,
+                                              driver);
+
+        driver.Refresh ();
+
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m�┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
+                                           output, driver);
+    }
 }
 }

+ 1 - 0
Tests/UnitTestsParallelizable/Drivers/ClipRegionTests.cs

@@ -1,4 +1,5 @@
 #nullable enable
 #nullable enable
+using System.Text;
 using UnitTests;
 using UnitTests;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 

+ 45 - 0
Tests/UnitTestsParallelizable/Drivers/DriverTests.cs

@@ -92,6 +92,51 @@ public class DriverTests (ITestOutputHelper output) : FakeDriverBase
 
 
         app.Dispose ();
         app.Dispose ();
     }
     }
+
+    // Tests fix for https://github.com/gui-cs/Terminal.Gui/issues/4258
+    [Theory]
+    [InlineData ("fake")]
+    [InlineData ("windows")]
+    [InlineData ("dotnet")]
+    [InlineData ("unix")]
+    public void All_Drivers_When_Clipped_AddStr_Glyph_On_Second_Cell_Of_Wide_Glyph_Outputs_Correctly (string driverName)
+    {
+        IApplication? app = Application.Create ();
+        app.Init (driverName);
+        IDriver driver = app.Driver!;
+
+        // Need to force "windows" driver to override legacy console mode for this test
+        driver.IsLegacyConsole = false;
+        driver.Force16Colors = false;
+
+        driver.SetScreenSize (6, 3);
+
+        driver!.Clip = new (driver.Screen);
+
+        driver.Move (1, 0);
+        driver.AddStr ("┌");
+        driver.Move (2, 0);
+        driver.AddStr ("─");
+        driver.Move (3, 0);
+        driver.AddStr ("┐");
+        driver.Clip.Exclude (new Region (new (1, 0, 3, 1)));
+
+        driver.Move (0, 0);
+        driver.AddStr ("🍎🍎🍎🍎");
+
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              �┌─┐🍎
+                                              """,
+                                              output,
+                                              driver);
+
+        driver.Refresh ();
+
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m�┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
+                                           output, driver);
+    }
 }
 }
 
 
 public class TestTop : Runnable
 public class TestTop : Runnable

+ 98 - 31
Tests/UnitTestsParallelizable/Drivers/OutputBaseTests.cs

@@ -1,6 +1,4 @@
-#nullable enable
-
-namespace DriverTests;
+namespace DriverTests;
 
 
 public class OutputBaseTests
 public class OutputBaseTests
 {
 {
@@ -9,7 +7,7 @@ public class OutputBaseTests
     {
     {
         // Arrange
         // Arrange
         var output = new FakeOutput ();
         var output = new FakeOutput ();
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (1, 1);
         buffer.SetSize (1, 1);
 
 
         // Act
         // Act
@@ -32,21 +30,21 @@ public class OutputBaseTests
 
 
         // Create DriverImpl and associate it with the FakeOutput to test Sixel output
         // Create DriverImpl and associate it with the FakeOutput to test Sixel output
         IDriver driver = new DriverImpl (
         IDriver driver = new DriverImpl (
-                                 new FakeInputProcessor (null!),
-                                 new OutputBufferImpl (),
-                                 output,
-                                 new (new AnsiResponseParser ()),
-                                 new SizeMonitorImpl (output));
+                                         new FakeInputProcessor (null!),
+                                         new OutputBufferImpl (),
+                                         output,
+                                         new (new AnsiResponseParser ()),
+                                         new SizeMonitorImpl (output));
 
 
         driver.Force16Colors = force16Colors;
         driver.Force16Colors = force16Colors;
 
 
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (1, 1);
         buffer.SetSize (1, 1);
 
 
         // Use a known RGB color and attribute
         // Use a known RGB color and attribute
         var fg = new Color (1, 2, 3);
         var fg = new Color (1, 2, 3);
         var bg = new Color (4, 5, 6);
         var bg = new Color (4, 5, 6);
-        buffer.CurrentAttribute = new Attribute (fg, bg);
+        buffer.CurrentAttribute = new (fg, bg);
         buffer.AddStr ("X");
         buffer.AddStr ("X");
 
 
         // Act
         // Act
@@ -59,7 +57,7 @@ public class OutputBaseTests
         }
         }
         else if (!isLegacyConsole && force16Colors)
         else if (!isLegacyConsole && force16Colors)
         {
         {
-            var expected16 = EscSeqUtils.CSI_SetForegroundColor (fg.GetAnsiColorCode ());
+            string expected16 = EscSeqUtils.CSI_SetForegroundColor (fg.GetAnsiColorCode ());
             Assert.Contains (expected16, ansi);
             Assert.Contains (expected16, ansi);
         }
         }
         else
         else
@@ -78,7 +76,7 @@ public class OutputBaseTests
     {
     {
         // Arrange
         // Arrange
         var output = new FakeOutput ();
         var output = new FakeOutput ();
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (2, 1);
         buffer.SetSize (2, 1);
 
 
         // Mark two characters as dirty by writing them into the buffer
         // Mark two characters as dirty by writing them into the buffer
@@ -92,7 +90,7 @@ public class OutputBaseTests
         output.Write (buffer); // calls OutputBase.Write via FakeOutput
         output.Write (buffer); // calls OutputBase.Write via FakeOutput
 
 
         // Assert: content was written to the fake output and dirty flags cleared
         // Assert: content was written to the fake output and dirty flags cleared
-        Assert.Contains ("AB", output.Output);
+        Assert.Contains ("AB", output.GetLastOutput ());
         Assert.False (buffer.Contents! [0, 0].IsDirty);
         Assert.False (buffer.Contents! [0, 0].IsDirty);
         Assert.False (buffer.Contents! [0, 1].IsDirty);
         Assert.False (buffer.Contents! [0, 1].IsDirty);
     }
     }
@@ -105,7 +103,7 @@ public class OutputBaseTests
         // Arrange
         // Arrange
         // FakeOutput exposes this because it's in test scope
         // FakeOutput exposes this because it's in test scope
         var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
         var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (3, 1);
         buffer.SetSize (3, 1);
 
 
         // Write 'A' at col 0 and 'C' at col 2; leave col 1 untouched (not dirty)
         // Write 'A' at col 0 and 'C' at col 2; leave col 1 untouched (not dirty)
@@ -122,15 +120,15 @@ public class OutputBaseTests
         output.Write (buffer);
         output.Write (buffer);
 
 
         // Assert: both characters were written (use Contains to avoid CI side effects)
         // Assert: both characters were written (use Contains to avoid CI side effects)
-        Assert.Contains ("A", output.Output);
-        Assert.Contains ("C", output.Output);
+        Assert.Contains ("A", output.GetLastOutput ());
+        Assert.Contains ("C", output.GetLastOutput ());
 
 
         // Dirty flags cleared for the written cells
         // Dirty flags cleared for the written cells
         Assert.False (buffer.Contents! [0, 0].IsDirty);
         Assert.False (buffer.Contents! [0, 0].IsDirty);
         Assert.False (buffer.Contents! [0, 2].IsDirty);
         Assert.False (buffer.Contents! [0, 2].IsDirty);
 
 
         // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
         // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
-        Assert.Equal (new Point (0, 0), output.GetCursorPosition ());
+        Assert.Equal (new (0, 0), output.GetCursorPosition ());
 
 
         // Now write 'X' at col 0 to verify subsequent writes also work
         // Now write 'X' at col 0 to verify subsequent writes also work
         buffer.Move (0, 0);
         buffer.Move (0, 0);
@@ -143,15 +141,84 @@ public class OutputBaseTests
         output.Write (buffer);
         output.Write (buffer);
 
 
         // Assert: both characters were written (use Contains to avoid CI side effects)
         // Assert: both characters were written (use Contains to avoid CI side effects)
-        Assert.Contains ("A", output.Output);
-        Assert.Contains ("C", output.Output);
+        Assert.Contains ("A", output.GetLastOutput ());
+        Assert.Contains ("C", output.GetLastOutput ());
+
+        // Dirty flags cleared for the written cells
+        Assert.False (buffer.Contents! [0, 0].IsDirty);
+        Assert.False (buffer.Contents! [0, 2].IsDirty);
+
+        // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
+        Assert.Equal (new (2, 0), output.GetCursorPosition ());
+    }
+
+    [Theory]
+    [InlineData (true)]
+    [InlineData (false)]
+    public void Write_Virtual_Or_NonVirtual_Uses_WriteToConsole_And_Clears_Dirty_Flags_Mixed_Graphemes (bool isLegacyConsole)
+    {
+        // Arrange
+        // FakeOutput exposes this because it's in test scope
+        var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
+        buffer.SetSize (3, 1);
+
+        // Write '🦮' at col 0 and 'A' at col 2
+        buffer.Move (0, 0);
+        buffer.AddStr ("🦮A");
+
+        // After the fix for https://github.com/gui-cs/Terminal.Gui/issues/4258:
+        // Writing a wide glyph at column 0 no longer sets column 1 to IsDirty = false.
+        // Column 1 retains whatever state it had (in this case, it was initialized as dirty
+        // by ClearContents, but may have been cleared by a previous Write call).
+        //
+        // What we care about is that wide glyphs work correctly and don't prevent
+        // other content from being drawn at odd columns.
+        Assert.True (buffer.Contents! [0, 0].IsDirty);
+
+        // Column 1 state depends on whether it was cleared by a previous Write - don't assert
+        Assert.True (buffer.Contents! [0, 2].IsDirty);
+
+        // Act
+        output.Write (buffer);
+
+        Assert.Contains ("🦮", output.GetLastOutput ());
+        Assert.Contains ("A", output.GetLastOutput ());
 
 
         // Dirty flags cleared for the written cells
         // Dirty flags cleared for the written cells
+        // Column 0 was written (wide glyph)
         Assert.False (buffer.Contents! [0, 0].IsDirty);
         Assert.False (buffer.Contents! [0, 0].IsDirty);
+
+        // Column 1 was skipped by OutputBase.Write because column 0 had a wide glyph
+        // So its dirty flag remains true (it was initialized as dirty by ClearContents)
+        Assert.True (buffer.Contents! [0, 1].IsDirty);
+
+        // Column 2 was written ('A')
+        Assert.False (buffer.Contents! [0, 2].IsDirty);
+
+        Assert.Equal (new (0, 0), output.GetCursorPosition ());
+
+        // Now write 'X' at col 1 which invalidates the wide glyph at col 0
+        buffer.Move (1, 0);
+        buffer.AddStr ("X");
+
+        // Confirm dirtiness state before to write
+        Assert.True (buffer.Contents! [0, 0].IsDirty); // Invalidated by writing at col 1
+        Assert.True (buffer.Contents! [0, 1].IsDirty); // Just written
+        Assert.True (buffer.Contents! [0, 2].IsDirty); // Marked dirty by writing at col 1
+
+        output.Write (buffer);
+
+        Assert.Contains ("�", output.GetLastOutput ());
+        Assert.Contains ("X", output.GetLastOutput ());
+
+        // Dirty flags cleared for the written cells
+        Assert.False (buffer.Contents! [0, 0].IsDirty);
+        Assert.False (buffer.Contents! [0, 1].IsDirty);
         Assert.False (buffer.Contents! [0, 2].IsDirty);
         Assert.False (buffer.Contents! [0, 2].IsDirty);
 
 
         // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
         // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
-        Assert.Equal (new Point (2, 0), output.GetCursorPosition ());
+        Assert.Equal (new (0, 0), output.GetCursorPosition ());
     }
     }
 
 
     [Theory]
     [Theory]
@@ -161,7 +228,7 @@ public class OutputBaseTests
     {
     {
         // Arrange
         // Arrange
         var output = new FakeOutput ();
         var output = new FakeOutput ();
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (1, 1);
         buffer.SetSize (1, 1);
 
 
         // Ensure the buffer has some content so Write traverses rows
         // Ensure the buffer has some content so Write traverses rows
@@ -171,16 +238,16 @@ public class OutputBaseTests
         var s = new SixelToRender
         var s = new SixelToRender
         {
         {
             SixelData = "SIXEL-DATA",
             SixelData = "SIXEL-DATA",
-            ScreenPosition = new Point (4, 2)
+            ScreenPosition = new (4, 2)
         };
         };
 
 
         // Create DriverImpl and associate it with the FakeOutput to test Sixel output
         // Create DriverImpl and associate it with the FakeOutput to test Sixel output
         IDriver driver = new DriverImpl (
         IDriver driver = new DriverImpl (
-                                 new FakeInputProcessor (null!),
-                                 new OutputBufferImpl (),
-                                 output,
-                                 new (new AnsiResponseParser ()),
-                                 new SizeMonitorImpl (output));
+                                         new FakeInputProcessor (null!),
+                                         new OutputBufferImpl (),
+                                         output,
+                                         new (new AnsiResponseParser ()),
+                                         new SizeMonitorImpl (output));
 
 
         // Add the Sixel to the driver
         // Add the Sixel to the driver
         driver.GetSixels ().Enqueue (s);
         driver.GetSixels ().Enqueue (s);
@@ -194,7 +261,7 @@ public class OutputBaseTests
         if (!isLegacyConsole)
         if (!isLegacyConsole)
         {
         {
             // Assert: Sixel data was emitted (use Contains to avoid equality/side-effects)
             // Assert: Sixel data was emitted (use Contains to avoid equality/side-effects)
-            Assert.Contains ("SIXEL-DATA", output.Output);
+            Assert.Contains ("SIXEL-DATA", output.GetLastOutput ());
 
 
             // Cursor was moved to Sixel position
             // Cursor was moved to Sixel position
             Assert.Equal (s.ScreenPosition, output.GetCursorPosition ());
             Assert.Equal (s.ScreenPosition, output.GetCursorPosition ());
@@ -202,7 +269,7 @@ public class OutputBaseTests
         else
         else
         {
         {
             // Assert: Sixel data was NOT emitted
             // Assert: Sixel data was NOT emitted
-            Assert.DoesNotContain ("SIXEL-DATA", output.Output);
+            Assert.DoesNotContain ("SIXEL-DATA", output.GetLastOutput ());
 
 
             // Cursor was NOT moved to Sixel position
             // Cursor was NOT moved to Sixel position
             Assert.NotEqual (s.ScreenPosition, output.GetCursorPosition ());
             Assert.NotEqual (s.ScreenPosition, output.GetCursorPosition ());
@@ -215,4 +282,4 @@ public class OutputBaseTests
 
 
         app.Dispose ();
         app.Dispose ();
     }
     }
-}
+}

+ 3 - 3
Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawTextAndLineCanvasTests.cs

@@ -240,7 +240,7 @@ public class ViewDrawTextAndLineCanvasTests () : FakeDriverBase
         Point screenPos = new Point (15, 15);
         Point screenPos = new Point (15, 15);
         view.LineCanvas.AddLine (screenPos, 5, Orientation.Horizontal, LineStyle.Single);
         view.LineCanvas.AddLine (screenPos, 5, Orientation.Horizontal, LineStyle.Single);
 
 
-        view.RenderLineCanvas ();
+        view.RenderLineCanvas (null);
 
 
         // Verify the line was drawn (check for horizontal line character)
         // Verify the line was drawn (check for horizontal line character)
         for (int i = 0; i < 5; i++)
         for (int i = 0; i < 5; i++)
@@ -272,7 +272,7 @@ public class ViewDrawTextAndLineCanvasTests () : FakeDriverBase
 
 
         Assert.NotEqual (Rectangle.Empty, view.LineCanvas.Bounds);
         Assert.NotEqual (Rectangle.Empty, view.LineCanvas.Bounds);
 
 
-        view.RenderLineCanvas ();
+        view.RenderLineCanvas (null);
 
 
         // LineCanvas should be cleared after rendering
         // LineCanvas should be cleared after rendering
         Assert.Equal (Rectangle.Empty, view.LineCanvas.Bounds);
         Assert.Equal (Rectangle.Empty, view.LineCanvas.Bounds);
@@ -302,7 +302,7 @@ public class ViewDrawTextAndLineCanvasTests () : FakeDriverBase
 
 
         Rectangle boundsBefore = view.LineCanvas.Bounds;
         Rectangle boundsBefore = view.LineCanvas.Bounds;
 
 
-        view.RenderLineCanvas ();
+        view.RenderLineCanvas (null);
 
 
         // LineCanvas should NOT be cleared when SuperViewRendersLineCanvas is true
         // LineCanvas should NOT be cleared when SuperViewRendersLineCanvas is true
         Assert.Equal (boundsBefore, view.LineCanvas.Bounds);
         Assert.Equal (boundsBefore, view.LineCanvas.Bounds);

+ 337 - 74
Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs

@@ -1,19 +1,18 @@
-#nullable enable
+using System.Text;
 using UnitTests;
 using UnitTests;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
 namespace ViewBaseTests.Drawing;
 namespace ViewBaseTests.Drawing;
 
 
-public class ViewDrawingClippingTests () : FakeDriverBase
+public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBase
 {
 {
     #region GetClip / SetClip Tests
     #region GetClip / SetClip Tests
 
 
-
     [Fact]
     [Fact]
     public void GetClip_ReturnsDriverClip ()
     public void GetClip_ReturnsDriverClip ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var region = new Region (new Rectangle (10, 10, 20, 20));
+        IDriver driver = CreateFakeDriver ();
+        var region = new Region (new (10, 10, 20, 20));
         driver.Clip = region;
         driver.Clip = region;
         View view = new () { Driver = driver };
         View view = new () { Driver = driver };
 
 
@@ -26,8 +25,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void SetClip_NullRegion_DoesNothing ()
     public void SetClip_NullRegion_DoesNothing ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var original = new Region (new Rectangle (5, 5, 10, 10));
+        IDriver driver = CreateFakeDriver ();
+        var original = new Region (new (5, 5, 10, 10));
         driver.Clip = original;
         driver.Clip = original;
 
 
         View view = new () { Driver = driver };
         View view = new () { Driver = driver };
@@ -40,8 +39,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void SetClip_ValidRegion_SetsDriverClip ()
     public void SetClip_ValidRegion_SetsDriverClip ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var region = new Region (new Rectangle (10, 10, 30, 30));
+        IDriver driver = CreateFakeDriver ();
+        var region = new Region (new (10, 10, 30, 30));
         View view = new () { Driver = driver };
         View view = new () { Driver = driver };
 
 
         view.SetClip (region);
         view.SetClip (region);
@@ -56,8 +55,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void SetClipToScreen_ReturnsPreviousClip ()
     public void SetClipToScreen_ReturnsPreviousClip ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var original = new Region (new Rectangle (5, 5, 10, 10));
+        IDriver driver = CreateFakeDriver ();
+        var original = new Region (new (5, 5, 10, 10));
         driver.Clip = original;
         driver.Clip = original;
         View view = new () { Driver = driver };
         View view = new () { Driver = driver };
 
 
@@ -70,7 +69,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void SetClipToScreen_SetsClipToScreen ()
     public void SetClipToScreen_SetsClipToScreen ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
+        IDriver driver = CreateFakeDriver ();
         View view = new () { Driver = driver };
         View view = new () { Driver = driver };
 
 
         view.SetClipToScreen ();
         view.SetClipToScreen ();
@@ -87,15 +86,15 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     public void ExcludeFromClip_Rectangle_NullDriver_DoesNotThrow ()
     public void ExcludeFromClip_Rectangle_NullDriver_DoesNotThrow ()
     {
     {
         View view = new () { Driver = null };
         View view = new () { Driver = null };
-        var exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (5, 5, 10, 10)));
+        Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (5, 5, 10, 10)));
         Assert.Null (exception);
         Assert.Null (exception);
     }
     }
 
 
     [Fact]
     [Fact]
     public void ExcludeFromClip_Rectangle_ExcludesArea ()
     public void ExcludeFromClip_Rectangle_ExcludesArea ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (new Rectangle (0, 0, 80, 25));
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (new (0, 0, 80, 25));
         View view = new () { Driver = driver };
         View view = new () { Driver = driver };
 
 
         var toExclude = new Rectangle (10, 10, 20, 20);
         var toExclude = new Rectangle (10, 10, 20, 20);
@@ -111,19 +110,18 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     {
     {
         View view = new () { Driver = null };
         View view = new () { Driver = null };
 
 
-        var exception = Record.Exception (() => view.ExcludeFromClip (new Region (new Rectangle (5, 5, 10, 10))));
+        Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Region (new (5, 5, 10, 10))));
         Assert.Null (exception);
         Assert.Null (exception);
     }
     }
 
 
     [Fact]
     [Fact]
     public void ExcludeFromClip_Region_ExcludesArea ()
     public void ExcludeFromClip_Region_ExcludesArea ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (new Rectangle (0, 0, 80, 25));
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (new (0, 0, 80, 25));
         View view = new () { Driver = driver };
         View view = new () { Driver = driver };
 
 
-
-        var toExclude = new Region (new Rectangle (10, 10, 20, 20));
+        var toExclude = new Region (new (10, 10, 20, 20));
         view.ExcludeFromClip (toExclude);
         view.ExcludeFromClip (toExclude);
 
 
         // Verify the region was excluded
         // Verify the region was excluded
@@ -150,8 +148,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void AddFrameToClip_IntersectsWithFrame ()
     public void AddFrameToClip_IntersectsWithFrame ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
@@ -171,7 +169,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
         Assert.NotNull (driver.Clip);
         Assert.NotNull (driver.Clip);
 
 
         // The clip should now be the intersection of the screen and the view's frame
         // The clip should now be the intersection of the screen and the view's frame
-        Rectangle expectedBounds = new Rectangle (1, 1, 20, 20);
+        var expectedBounds = new Rectangle (1, 1, 20, 20);
         Assert.Equal (expectedBounds, driver.Clip.GetBounds ());
         Assert.Equal (expectedBounds, driver.Clip.GetBounds ());
     }
     }
 
 
@@ -194,8 +192,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void AddViewportToClip_IntersectsWithViewport ()
     public void AddViewportToClip_IntersectsWithViewport ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
@@ -222,8 +220,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void AddViewportToClip_WithClipContentOnly_LimitsToVisibleContent ()
     public void AddViewportToClip_WithClipContentOnly_LimitsToVisibleContent ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
@@ -260,7 +258,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     public void ClipRegions_StackCorrectly_WithNestedViews ()
     public void ClipRegions_StackCorrectly_WithNestedViews ()
     {
     {
         IDriver driver = CreateFakeDriver (100, 100);
         IDriver driver = CreateFakeDriver (100, 100);
-        driver.Clip = new Region (driver.Screen);
+        driver.Clip = new (driver.Screen);
 
 
         var superView = new View
         var superView = new View
         {
         {
@@ -278,7 +276,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
             X = 5,
             X = 5,
             Y = 5,
             Y = 5,
             Width = 30,
             Width = 30,
-            Height = 30,
+            Height = 30
         };
         };
         superView.Add (view);
         superView.Add (view);
         superView.LayoutSubViews ();
         superView.LayoutSubViews ();
@@ -296,14 +294,15 @@ public class ViewDrawingClippingTests () : FakeDriverBase
 
 
         // Restore superView clip
         // Restore superView clip
         view.SetClip (superViewClip);
         view.SetClip (superViewClip);
+
         //   Assert.Equal (superViewBounds, driver.Clip.GetBounds ());
         //   Assert.Equal (superViewBounds, driver.Clip.GetBounds ());
     }
     }
 
 
     [Fact]
     [Fact]
     public void ClipRegions_RespectPreviousClip ()
     public void ClipRegions_RespectPreviousClip ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var initialClip = new Region (new Rectangle (20, 20, 40, 40));
+        IDriver driver = CreateFakeDriver ();
+        var initialClip = new Region (new (20, 20, 40, 40));
         driver.Clip = initialClip;
         driver.Clip = initialClip;
 
 
         var view = new View
         var view = new View
@@ -322,9 +321,9 @@ public class ViewDrawingClippingTests () : FakeDriverBase
 
 
         // The new clip should be the intersection of the initial clip and the view's frame
         // The new clip should be the intersection of the initial clip and the view's frame
         Rectangle expected = Rectangle.Intersect (
         Rectangle expected = Rectangle.Intersect (
-                                                   initialClip.GetBounds (),
-                                                   view.FrameToScreen ()
-                                                  );
+                                                  initialClip.GetBounds (),
+                                                  view.FrameToScreen ()
+                                                 );
 
 
         Assert.Equal (expected, driver.Clip.GetBounds ());
         Assert.Equal (expected, driver.Clip.GetBounds ());
 
 
@@ -340,8 +339,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void AddFrameToClip_EmptyFrame_WorksCorrectly ()
     public void AddFrameToClip_EmptyFrame_WorksCorrectly ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
@@ -364,18 +363,18 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void AddViewportToClip_EmptyViewport_WorksCorrectly ()
     public void AddViewportToClip_EmptyViewport_WorksCorrectly ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
             X = 1,
             X = 1,
             Y = 1,
             Y = 1,
-            Width = 1,  // Minimal size to have adornments
+            Width = 1, // Minimal size to have adornments
             Height = 1,
             Height = 1,
             Driver = driver
             Driver = driver
         };
         };
-        view.Border!.Thickness = new Thickness (1);
+        view.Border!.Thickness = new (1);
         view.BeginInit ();
         view.BeginInit ();
         view.EndInit ();
         view.EndInit ();
         view.LayoutSubViews ();
         view.LayoutSubViews ();
@@ -391,12 +390,12 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void ClipRegions_OutOfBounds_HandledCorrectly ()
     public void ClipRegions_OutOfBounds_HandledCorrectly ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
-            X = 100,  // Outside screen bounds
+            X = 100, // Outside screen bounds
             Y = 100,
             Y = 100,
             Width = 20,
             Width = 20,
             Height = 20,
             Height = 20,
@@ -409,6 +408,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
         Region? previous = view.AddFrameToClip ();
         Region? previous = view.AddFrameToClip ();
 
 
         Assert.NotNull (previous);
         Assert.NotNull (previous);
+
         // The clip should be empty since the view is outside the screen
         // The clip should be empty since the view is outside the screen
         Assert.True (driver.Clip.IsEmpty () || !driver.Clip.Contains (100, 100));
         Assert.True (driver.Clip.IsEmpty () || !driver.Clip.Contains (100, 100));
     }
     }
@@ -420,8 +420,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void Clip_Set_BeforeDraw_ClipsDrawing ()
     public void Clip_Set_BeforeDraw_ClipsDrawing ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var clip = new Region (new Rectangle (10, 10, 10, 10));
+        IDriver driver = CreateFakeDriver ();
+        var clip = new Region (new (10, 10, 10, 10));
         driver.Clip = clip;
         driver.Clip = clip;
 
 
         var view = new View
         var view = new View
@@ -445,8 +445,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void Draw_UpdatesDriverClip ()
     public void Draw_UpdatesDriverClip ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
@@ -464,14 +464,15 @@ public class ViewDrawingClippingTests () : FakeDriverBase
 
 
         // Clip should be updated to exclude the drawn view
         // Clip should be updated to exclude the drawn view
         Assert.NotNull (driver.Clip);
         Assert.NotNull (driver.Clip);
+
         // Assert.False (driver.Clip.Contains (15, 15)); // Point inside the view should be excluded
         // Assert.False (driver.Clip.Contains (15, 15)); // Point inside the view should be excluded
     }
     }
 
 
     [Fact]
     [Fact]
     public void Draw_WithSubViews_ClipsCorrectly ()
     public void Draw_WithSubViews_ClipsCorrectly ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var superView = new View
         var superView = new View
         {
         {
@@ -491,13 +492,277 @@ public class ViewDrawingClippingTests () : FakeDriverBase
 
 
         // Both superView and view should be excluded from clip
         // Both superView and view should be excluded from clip
         Assert.NotNull (driver.Clip);
         Assert.NotNull (driver.Clip);
+
         //    Assert.False (driver.Clip.Contains (15, 15)); // Point in superView should be excluded
         //    Assert.False (driver.Clip.Contains (15, 15)); // Point in superView should be excluded
     }
     }
 
 
+    /// <summary>
+    /// Tests that wide glyphs (🍎) are correctly clipped when overlapped by bordered subviews
+    /// at different column alignments (even vs odd). Demonstrates:
+    /// 1. Full clipping at even columns (X=0, X=2)
+    /// 2. Partial clipping at odd columns (X=1) resulting in half-glyphs (�)
+    /// 3. The recursive draw flow and clip exclusion mechanism
+    /// 
+    /// For detailed draw flow documentation, see ViewDrawingClippingTests.DrawFlow.md
+    /// </summary>
+    [Fact]
+    public void Draw_WithBorderSubView_DrawsCorrectly ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+        IDriver driver = app!.Driver!;
+        driver.SetScreenSize (30, 20);
+
+        driver!.Clip = new (driver.Screen);
+
+        var superView = new Runnable ()
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto () + 4,
+            Height = Dim.Auto () + 1,
+            Driver = driver
+        };
+
+        Rune codepoint = Glyphs.Apple;
+
+        superView.DrawingContent += (s, e) =>
+                                    {
+                                        var view = s as View;
+                                        for (var r = 0; r < view!.Viewport.Height; r++)
+                                        {
+                                            for (var c = 0; c < view.Viewport.Width; c += 2)
+                                            {
+                                                if (codepoint != default (Rune))
+                                                {
+                                                    view.AddRune (c, r, codepoint);
+                                                }
+                                            }
+                                        }
+                                        e.DrawContext?.AddDrawnRectangle (view.Viewport);
+                                        e.Cancel = true;
+                                    };
+
+        var viewWithBorderAtX0 = new View
+        {
+            Text = "viewWithBorderAtX0",
+            BorderStyle = LineStyle.Dashed,
+            X = 0,
+            Y = 1,
+            Width = Dim.Auto (),
+            Height = 3
+        };
+
+        var viewWithBorderAtX1 = new View
+        {
+            Text = "viewWithBorderAtX1",
+            BorderStyle = LineStyle.Dashed,
+            X = 1,
+            Y = Pos.Bottom (viewWithBorderAtX0) + 1,
+            Width = Dim.Auto (),
+            Height = 3
+        };
+
+        var viewWithBorderAtX2 = new View
+        {
+            Text = "viewWithBorderAtX2",
+            BorderStyle = LineStyle.Dashed,
+            X = 2,
+            Y = Pos.Bottom (viewWithBorderAtX1) + 1,
+            Width = Dim.Auto (),
+            Height = 3
+        };
+
+        superView.Add (viewWithBorderAtX0, viewWithBorderAtX1, viewWithBorderAtX2);
+        app.Begin (superView);
+        // Begin calls LayoutAndDraw, so no need to call it again here
+        // app.LayoutAndDraw();
+
+        DriverAssert.AssertDriverContentsAre (
+                                                       """
+                                                       🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                                       ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎
+                                                       ┆viewWithBorderAtX0┆🍎🍎🍎
+                                                       └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎
+                                                       🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                                       �┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎
+                                                       �┆viewWithBorderAtX1┆ 🍎🍎
+                                                       �└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎
+                                                       🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                                       🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎
+                                                       🍎┆viewWithBorderAtX2┆🍎🍎
+                                                       🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎
+                                                       🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                                       """,
+                                                       output,
+                                                       driver);
+
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┆viewWithBorderAtX0┆🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m�┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m�┆viewWithBorderAtX1┆ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m�└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┆viewWithBorderAtX2┆🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
+                                           output, driver);
+
+        DriverImpl? driverImpl = driver as DriverImpl;
+        FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
+
+        output.WriteLine ("Driver Output After Redraw:\n" + driver.GetOutput().GetLastOutput());
+
+        // BUGBUG: Border.set_LineStyle does not call SetNeedsDraw
+        viewWithBorderAtX1!.Border!.LineStyle = LineStyle.Single;
+        viewWithBorderAtX1.Border!.SetNeedsDraw ();
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                              ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎
+                                              ┆viewWithBorderAtX0┆🍎🍎🍎
+                                              └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎
+                                              🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                              �┌──────────────────┐ 🍎🍎
+                                              �│viewWithBorderAtX1│ 🍎🍎
+                                              �└──────────────────┘ 🍎🍎
+                                              🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                              🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎
+                                              🍎┆viewWithBorderAtX2┆🍎🍎
+                                              🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎
+                                              🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                              """,
+                                              output,
+                                              driver);
+
+
+    }
+
+    [Fact]
+    public void Draw_WithBorderSubView_At_Col1_In_WideGlyph_DrawsCorrectly ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+        IDriver driver = app!.Driver!;
+        driver.SetScreenSize (6, 3);  // Minimal: 6 cols wide (3 for content + 2 for border + 1), 3 rows high (1 for content + 2 for border)
+
+        driver!.Clip = new (driver.Screen);
+
+        var superView = new Runnable ()
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            Driver = driver
+        };
+
+        Rune codepoint = Glyphs.Apple;
+
+        superView.DrawingContent += (s, e) =>
+                                    {
+                                        View? view = s as View;
+                                        view?.AddStr (0, 0, "🍎🍎🍎🍎");
+                                        view?.AddStr (0, 1, "🍎🍎🍎🍎");
+                                        view?.AddStr (0, 2, "🍎🍎🍎🍎");
+                                        e.DrawContext?.AddDrawnRectangle (view!.Viewport);
+                                        e.Cancel = true;
+                                    };
+
+        // Minimal border at X=1 (odd column), Width=3, Height=3 (includes border)
+        var viewWithBorder = new View
+        {
+            Text = "X",
+            BorderStyle = LineStyle.Single,
+            X = 1,
+            Y = 0,
+            Width = 3,
+            Height = 3
+        };
+
+        superView.Add (viewWithBorder);
+        app.Begin (superView);
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              �┌─┐🍎
+                                              �│X│🍎
+                                              �└─┘🍎
+                                              """,
+                                              output,
+                                              driver);
+
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m�┌─┐🍎�│X│🍎�└─┘🍎",
+            output, driver);
+
+        DriverImpl? driverImpl = driver as DriverImpl;
+        FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
+
+        output.WriteLine ("Driver Output:\n" + fakeOutput!.GetLastOutput ());
+    }
+
+
+    [Fact]
+    public void Draw_WithBorderSubView_At_Col3_In_WideGlyph_DrawsCorrectly ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+        IDriver driver = app!.Driver!;
+        driver.SetScreenSize (6, 3);  // Screen: 6 cols wide, 3 rows high; enough for 3x3 border subview at col 3 plus content on the left
+
+        driver!.Clip = new (driver.Screen);
+
+        var superView = new Runnable ()
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            Driver = driver
+        };
+
+        Rune codepoint = Glyphs.Apple;
+
+        superView.DrawingContent += (s, e) =>
+        {
+            View? view = s as View;
+            view?.AddStr (0, 0, "🍎🍎🍎🍎");
+            view?.AddStr (0, 1, "🍎🍎🍎🍎");
+            view?.AddStr (0, 2, "🍎🍎🍎🍎");
+            e.DrawContext?.AddDrawnRectangle (view!.Viewport);
+            e.Cancel = true;
+        };
+
+        // Minimal border at X=3 (odd column), Width=3, Height=3 (includes border)
+        var viewWithBorder = new View
+        {
+            Text = "X",
+            BorderStyle = LineStyle.Single,
+            X = 3,
+            Y = 0,
+            Width = 3,
+            Height = 3
+        };
+
+        superView.Add (viewWithBorder);
+        app.Begin (superView);
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              🍎�┌─┐
+                                              🍎�│X│
+                                              🍎�└─┘
+                                              """,
+                                              output,
+                                              driver);
+
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎�┌─┐🍎�│X│🍎�└─┘",
+            output, driver);
+
+        DriverImpl? driverImpl = driver as DriverImpl;
+        FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
+
+        output.WriteLine ("Driver Output:\n" + fakeOutput!.GetLastOutput ());
+    }
+
     [Fact]
     [Fact]
     public void Draw_NonVisibleView_DoesNotUpdateClip ()
     public void Draw_NonVisibleView_DoesNotUpdateClip ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
+        IDriver driver = CreateFakeDriver ();
         var originalClip = new Region (driver.Screen);
         var originalClip = new Region (driver.Screen);
         driver.Clip = originalClip.Clone ();
         driver.Clip = originalClip.Clone ();
 
 
@@ -522,8 +787,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void ExcludeFromClip_ExcludesRegion ()
     public void ExcludeFromClip_ExcludesRegion ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
@@ -542,13 +807,12 @@ public class ViewDrawingClippingTests () : FakeDriverBase
 
 
         Assert.NotNull (driver.Clip);
         Assert.NotNull (driver.Clip);
         Assert.False (driver.Clip.Contains (20, 20)); // Point inside excluded rect should not be in clip
         Assert.False (driver.Clip.Contains (20, 20)); // Point inside excluded rect should not be in clip
-
     }
     }
 
 
     [Fact]
     [Fact]
     public void ExcludeFromClip_WithNullClip_DoesNotThrow ()
     public void ExcludeFromClip_WithNullClip_DoesNotThrow ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
+        IDriver driver = CreateFakeDriver ();
         driver.Clip = null!;
         driver.Clip = null!;
 
 
         var view = new View
         var view = new View
@@ -560,10 +824,9 @@ public class ViewDrawingClippingTests () : FakeDriverBase
             Driver = driver
             Driver = driver
         };
         };
 
 
-        var exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (15, 15, 10, 10)));
+        Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (15, 15, 10, 10)));
 
 
         Assert.Null (exception);
         Assert.Null (exception);
-
     }
     }
 
 
     #endregion
     #endregion
@@ -573,7 +836,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void SetClip_SetsDriverClip ()
     public void SetClip_SetsDriverClip ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
+        IDriver driver = CreateFakeDriver ();
 
 
         var view = new View
         var view = new View
         {
         {
@@ -584,7 +847,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
             Driver = driver
             Driver = driver
         };
         };
 
 
-        var newClip = new Region (new Rectangle (5, 5, 30, 30));
+        var newClip = new Region (new (5, 5, 30, 30));
         view.SetClip (newClip);
         view.SetClip (newClip);
 
 
         Assert.Equal (newClip, driver.Clip);
         Assert.Equal (newClip, driver.Clip);
@@ -593,8 +856,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact (Skip = "See BUGBUG in SetClip")]
     [Fact (Skip = "See BUGBUG in SetClip")]
     public void SetClip_WithNullClip_ClearsClip ()
     public void SetClip_WithNullClip_ClearsClip ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (new Rectangle (10, 10, 20, 20));
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (new (10, 10, 20, 20));
 
 
         var view = new View
         var view = new View
         {
         {
@@ -613,7 +876,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void Draw_Excludes_View_From_Clip ()
     public void Draw_Excludes_View_From_Clip ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
+        IDriver driver = CreateFakeDriver ();
         var originalClip = new Region (driver.Screen);
         var originalClip = new Region (driver.Screen);
         driver.Clip = originalClip.Clone ();
         driver.Clip = originalClip.Clone ();
 
 
@@ -641,8 +904,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void Draw_EmptyViewport_DoesNotCrash ()
     public void Draw_EmptyViewport_DoesNotCrash ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
@@ -652,13 +915,13 @@ public class ViewDrawingClippingTests () : FakeDriverBase
             Height = 1,
             Height = 1,
             Driver = driver
             Driver = driver
         };
         };
-        view.Border!.Thickness = new Thickness (1);
+        view.Border!.Thickness = new (1);
         view.BeginInit ();
         view.BeginInit ();
         view.EndInit ();
         view.EndInit ();
         view.LayoutSubViews ();
         view.LayoutSubViews ();
 
 
         // With border of 1, viewport should be empty (0x0 or negative)
         // With border of 1, viewport should be empty (0x0 or negative)
-        var exception = Record.Exception (() => view.Draw ());
+        Exception? exception = Record.Exception (() => view.Draw ());
 
 
         Assert.Null (exception);
         Assert.Null (exception);
     }
     }
@@ -666,8 +929,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void Draw_VeryLargeView_HandlesClippingCorrectly ()
     public void Draw_VeryLargeView_HandlesClippingCorrectly ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
@@ -681,7 +944,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
         view.EndInit ();
         view.EndInit ();
         view.LayoutSubViews ();
         view.LayoutSubViews ();
 
 
-        var exception = Record.Exception (() => view.Draw ());
+        Exception? exception = Record.Exception (() => view.Draw ());
 
 
         Assert.Null (exception);
         Assert.Null (exception);
     }
     }
@@ -689,8 +952,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void Draw_NegativeCoordinates_HandlesClippingCorrectly ()
     public void Draw_NegativeCoordinates_HandlesClippingCorrectly ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
@@ -704,7 +967,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
         view.EndInit ();
         view.EndInit ();
         view.LayoutSubViews ();
         view.LayoutSubViews ();
 
 
-        var exception = Record.Exception (() => view.Draw ());
+        Exception? exception = Record.Exception (() => view.Draw ());
 
 
         Assert.Null (exception);
         Assert.Null (exception);
     }
     }
@@ -712,8 +975,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     [Fact]
     public void Draw_OutOfScreenBounds_HandlesClippingCorrectly ()
     public void Draw_OutOfScreenBounds_HandlesClippingCorrectly ()
     {
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
 
         var view = new View
         var view = new View
         {
         {
@@ -727,7 +990,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
         view.EndInit ();
         view.EndInit ();
         view.LayoutSubViews ();
         view.LayoutSubViews ();
 
 
-        var exception = Record.Exception (() => view.Draw ());
+        Exception? exception = Record.Exception (() => view.Draw ());
 
 
         Assert.Null (exception);
         Assert.Null (exception);
     }
     }

+ 43 - 51
Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs

@@ -1,7 +1,7 @@
 using Terminal.Gui.App;
 using Terminal.Gui.App;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace ApplicationTests;
+namespace ApplicationTests.Mouse;
 
 
 /// <summary>
 /// <summary>
 ///     Parallelizable tests for mouse event routing and coordinate transformation.
 ///     Parallelizable tests for mouse event routing and coordinate transformation.
@@ -32,9 +32,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
         };
         };
 
 
         Point? receivedPosition = null;
         Point? receivedPosition = null;
-        var eventReceived = false;
+        bool eventReceived = false;
 
 
-        view.MouseEvent += (sender, args) =>
+        view.MouseEvent += (_, args) =>
         {
         {
             eventReceived = true;
             eventReceived = true;
             receivedPosition = args.Position;
             receivedPosition = args.Position;
@@ -90,9 +90,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
         };
         };
 
 
         Point? receivedPosition = null;
         Point? receivedPosition = null;
-        var eventReceived = false;
+        bool eventReceived = false;
 
 
-        view.MouseEvent += (sender, args) =>
+        view.MouseEvent += (_, args) =>
         {
         {
             eventReceived = true;
             eventReceived = true;
             receivedPosition = args.Position;
             receivedPosition = args.Position;
@@ -100,7 +100,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
 
 
         MouseEventArgs mouseEvent = new ()
         MouseEventArgs mouseEvent = new ()
         {
         {
-            Position = new Point (viewRelativeX, viewRelativeY),
+            Position = new (viewRelativeX, viewRelativeY),
             Flags = MouseFlags.Button1Clicked
             Flags = MouseFlags.Button1Clicked
         };
         };
 
 
@@ -146,9 +146,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
         superView.Add (subView);
         superView.Add (subView);
 
 
         Point? subViewReceivedPosition = null;
         Point? subViewReceivedPosition = null;
-        var subViewEventReceived = false;
+        bool subViewEventReceived = false;
 
 
-        subView.MouseEvent += (sender, args) =>
+        subView.MouseEvent += (_, args) =>
         {
         {
             subViewEventReceived = true;
             subViewEventReceived = true;
             subViewReceivedPosition = args.Position;
             subViewReceivedPosition = args.Position;
@@ -175,7 +175,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
     }
     }
 
 
     [Fact]
     [Fact]
-    public void MouseClick_OnSubView_RaisesMouseClickEvent ()
+    public void MouseClick_OnSubView_RaisesSelectingEvent ()
     {
     {
         // Arrange
         // Arrange
         View superView = new ()
         View superView = new ()
@@ -194,8 +194,8 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
 
 
         superView.Add (subView);
         superView.Add (subView);
 
 
-        var clickCount = 0;
-        subView.MouseClick += (sender, args) => clickCount++;
+        int selectingCount = 0;
+        subView.Selecting += (_, _) => selectingCount++;
 
 
         MouseEventArgs mouseEvent = new ()
         MouseEventArgs mouseEvent = new ()
         {
         {
@@ -207,7 +207,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
         subView.NewMouseEvent (mouseEvent);
         subView.NewMouseEvent (mouseEvent);
 
 
         // Assert
         // Assert
-        Assert.Equal (1, clickCount);
+        Assert.Equal (1, selectingCount);
 
 
         subView.Dispose ();
         subView.Dispose ();
         superView.Dispose ();
         superView.Dispose ();
@@ -222,20 +222,20 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
     {
     {
         // Arrange
         // Arrange
         View view = new () { Width = 10, Height = 10 };
         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;
             handlerCalled = true;
             args.Handled = true; // Mark as handled
             args.Handled = true; // Mark as handled
         };
         };
 
 
-        view.MouseClick += (sender, args) => { clickHandlerCalled = true; };
+        view.MouseEvent += (_, e) => { clickHandlerCalled = !e.IsSingleDoubleOrTripleClicked; ; };
 
 
         MouseEventArgs mouseEvent = new ()
         MouseEventArgs mouseEvent = new ()
         {
         {
-            Position = new Point (5, 5),
+            Position = new (5, 5),
             Flags = MouseFlags.Button1Clicked
             Flags = MouseFlags.Button1Clicked
         };
         };
 
 
@@ -255,20 +255,17 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
     {
     {
         // Arrange
         // Arrange
         View view = new () { Width = 10, Height = 10 };
         View view = new () { Width = 10, Height = 10 };
-        var eventHandlerCalled = false;
-        var clickHandlerCalled = false;
+        bool eventHandlerCalled = false;
 
 
-        view.MouseEvent += (sender, args) =>
+        view.MouseEvent += (_, _) =>
         {
         {
             eventHandlerCalled = true;
             eventHandlerCalled = true;
             // Don't set Handled = true
             // Don't set Handled = true
         };
         };
 
 
-        view.MouseClick += (sender, args) => { clickHandlerCalled = true; };
-
         MouseEventArgs mouseEvent = new ()
         MouseEventArgs mouseEvent = new ()
         {
         {
-            Position = new Point (5, 5),
+            Position = new (5, 5),
             Flags = MouseFlags.Button1Clicked
             Flags = MouseFlags.Button1Clicked
         };
         };
 
 
@@ -277,7 +274,6 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
 
 
         // Assert
         // Assert
         Assert.True (eventHandlerCalled);
         Assert.True (eventHandlerCalled);
-        Assert.True (clickHandlerCalled); // Click handler should be called when event is not handled
 
 
         view.Dispose ();
         view.Dispose ();
     }
     }
@@ -287,18 +283,17 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
     #region Mouse Button Events
     #region Mouse Button Events
 
 
     [Theory]
     [Theory]
-    [InlineData (MouseFlags.Button1Pressed, 1, 0, 0)]
-    [InlineData (MouseFlags.Button1Released, 0, 1, 0)]
-    [InlineData (MouseFlags.Button1Clicked, 0, 0, 1)]
-    public void View_MouseButtonEvents_RaiseCorrectHandlers (MouseFlags flags, int expectedPressed, int expectedReleased, int expectedClicked)
+    [InlineData (MouseFlags.Button1Pressed, 1, 0)]
+    [InlineData (MouseFlags.Button1Released, 0, 1)]
+    [InlineData (MouseFlags.Button1Clicked, 0, 0)]
+    public void View_MouseButtonEvents_RaiseCorrectHandlers (MouseFlags flags, int expectedPressed, int expectedReleased)
     {
     {
         // Arrange
         // Arrange
         View view = new () { Width = 10, Height = 10 };
         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))
             if (args.Flags.HasFlag (MouseFlags.Button1Pressed))
             {
             {
@@ -311,11 +306,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
             }
             }
         };
         };
 
 
-        view.MouseClick += (sender, args) => { clickedCount++; };
-
         MouseEventArgs mouseEvent = new ()
         MouseEventArgs mouseEvent = new ()
         {
         {
-            Position = new Point (5, 5),
+            Position = new (5, 5),
             Flags = flags
             Flags = flags
         };
         };
 
 
@@ -325,7 +318,6 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
         // Assert
         // Assert
         Assert.Equal (expectedPressed, pressedCount);
         Assert.Equal (expectedPressed, pressedCount);
         Assert.Equal (expectedReleased, releasedCount);
         Assert.Equal (expectedReleased, releasedCount);
-        Assert.Equal (expectedClicked, clickedCount);
 
 
         view.Dispose ();
         view.Dispose ();
     }
     }
@@ -339,13 +331,13 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
     {
     {
         // Arrange
         // Arrange
         View view = new () { Width = 10, Height = 10 };
         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 ()
         MouseEventArgs mouseEvent = new ()
         {
         {
-            Position = new Point (5, 5),
+            Position = new (5, 5),
             Flags = clickFlag
             Flags = clickFlag
         };
         };
 
 
@@ -373,12 +365,12 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
             Enabled = false
             Enabled = false
         };
         };
 
 
-        var eventCalled = false;
-        view.MouseEvent += (sender, args) => { eventCalled = true; };
+        bool eventCalled = false;
+        view.MouseEvent += (_, _) => { eventCalled = true; };
 
 
         MouseEventArgs mouseEvent = new ()
         MouseEventArgs mouseEvent = new ()
         {
         {
-            Position = new Point (5, 5),
+            Position = new (5, 5),
             Flags = MouseFlags.Button1Clicked
             Flags = MouseFlags.Button1Clicked
         };
         };
 
 
@@ -392,7 +384,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
     }
     }
 
 
     [Fact]
     [Fact]
-    public void View_Disabled_DoesNotRaiseMouseClickEvent ()
+    public void View_Disabled_DoesNotRaiseSelectingEvent ()
     {
     {
         // Arrange
         // Arrange
         View view = new ()
         View view = new ()
@@ -402,12 +394,12 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
             Enabled = false
             Enabled = false
         };
         };
 
 
-        var clickCalled = false;
-        view.MouseClick += (sender, args) => { clickCalled = true; };
+        bool selectingCalled = false;
+        view.Selecting += (_, _) => { selectingCalled = true; };
 
 
         MouseEventArgs mouseEvent = new ()
         MouseEventArgs mouseEvent = new ()
         {
         {
-            Position = new Point (5, 5),
+            Position = new (5, 5),
             Flags = MouseFlags.Button1Clicked
             Flags = MouseFlags.Button1Clicked
         };
         };
 
 
@@ -415,7 +407,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
         view.NewMouseEvent (mouseEvent);
         view.NewMouseEvent (mouseEvent);
 
 
         // Assert
         // Assert
-        Assert.False (clickCalled);
+        Assert.False (selectingCalled);
 
 
         view.Dispose ();
         view.Dispose ();
     }
     }
@@ -445,7 +437,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
 
 
         MouseEventArgs mouseEvent = new ()
         MouseEventArgs mouseEvent = new ()
         {
         {
-            Position = new Point (2, 2),
+            Position = new (2, 2),
             Flags = MouseFlags.Button1Clicked
             Flags = MouseFlags.Button1Clicked
         };
         };
 
 
@@ -475,12 +467,12 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
 
 
         superView.Add (view);
         superView.Add (view);
 
 
-        var selectingCount = 0;
-        view.Selecting += (sender, args) => selectingCount++;
+        int selectingCount = 0;
+        view.Selecting += (_, _) => selectingCount++;
 
 
         MouseEventArgs mouseEvent = new ()
         MouseEventArgs mouseEvent = new ()
         {
         {
-            Position = new Point (5, 5),
+            Position = new (5, 5),
             Flags = MouseFlags.Button1Clicked
             Flags = MouseFlags.Button1Clicked
         };
         };
 
 

+ 1 - 2
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.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.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
 - [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
 ### 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
 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
 4. [View.MouseBindings](~/api/Terminal.Gui.ViewBase.View.yml) - Converts mouse actions to commands
 5. Command handlers
 5. Command handlers
-6. [View.MouseClick](~/api/Terminal.Gui.ViewBase.View.yml) event (high-level)
 
 
 ### Layout
 ### Layout
 
 

+ 110 - 6
docfx/docs/migratingfromv1.md

@@ -555,7 +555,7 @@ void HandleMouse(object? sender, MouseEventArgs args) { }
 
 
 ```csharp
 ```csharp
 // v2 - Viewport-relative coordinates
 // v2 - Viewport-relative coordinates
-view.MouseClick += (s, e) =>
+view.MouseEvent += (s, e) =>
 {
 {
     // e.Position is relative to view's Viewport
     // e.Position is relative to view's Viewport
     var x = e.Position.X; // 0 = left edge of 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<MouseBinding> { 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<MouseBinding> { 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
 ```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.
 See [Mouse Deep Dive](mouse.md) for complete details.

+ 154 - 22
docfx/docs/mouse.md

@@ -44,8 +44,12 @@ public class MyView : View
         AddCommand (Command.ScrollDown, () => ScrollVertical (1));
         AddCommand (Command.ScrollDown, () => ScrollVertical (1));
         MouseBindings.Add (MouseFlags.WheelDown, Command.ScrollDown);
         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:
 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)
 * **Double-Click Events**: `MouseFlags.Button1DoubleClicked` for default actions (like opening/accepting)
 * **Right-Click Events**: `MouseFlags.Button3Clicked` for context menus
 * **Right-Click Events**: `MouseFlags.Button3Clicked` for context menus
 * **Scroll Events**: `MouseFlags.WheelUp` and `MouseFlags.WheelDown` for scrolling content
 * **Scroll Events**: `MouseFlags.WheelUp` and `MouseFlags.WheelDown` for scrolling content
 * **Drag Events**: `MouseFlags.Button1Pressed` combined with mouse move tracking for drag operations
 * **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<MouseBinding> { 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<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
+        {
+            // Custom selection logic with mouse position
+            if (mouseArgs.Position.Y == 0)
+            {
+                HandleHeaderClick();
+                return true;
+            }
+        }
+        return base.OnSelecting(args);
+    }
+}
+```
+
 ## Mouse Events
 ## 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.
 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
 ### 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:
 3. **View Level**: The target view processes the event through:
    - `OnMouseEvent` (virtual method that can be overridden)
    - `OnMouseEvent` (virtual method that can be overridden)
    - `MouseEvent` event (for event subscribers)
    - `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
 ### 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<MouseBinding> { 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
 ## 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.
 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:
 Mouse states include:
 * **Normal** - Default state when mouse is not interacting with the view
 * **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
 * **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.
 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
 ```cs
 view.MouseStateChanged += (sender, e) => 
 view.MouseStateChanged += (sender, e) => 
 {
 {
-    switch (e.NewState)
+    switch (e.Value)
     {
     {
-        case MouseState.Over:
+        case MouseState.In:
             // Change appearance when mouse hovers
             // Change appearance when mouse hovers
             break;
             break;
         case MouseState.Pressed:
         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
 ## Mouse Button and Movement Concepts
 
 
 * **Down** - Indicates the user pushed a mouse button down.
 * **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.
 * **Released** - Indicates the user released a mouse button.
 * **Clicked** - Indicates the user pressed then released the mouse button while over a particular View. 
 * **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.
 * **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.
 * **Moved** - Indicates the mouse moved to a new location since the last mouse event.
 * **Wheel** - Indicates the mouse wheel was scrolled up or down.
 * **Wheel** - Indicates the mouse wheel was scrolled up or down.
 
 
@@ -179,7 +301,7 @@ public class MyView : View
         if (mouseEvent.Flags.HasFlag(MouseFlags.Button3Clicked))
         if (mouseEvent.Flags.HasFlag(MouseFlags.Button3Clicked))
         {
         {
             // Access application mouse functionality through View.App
             // Access application mouse functionality through View.App
-            App?.MouseEvent?.Invoke(this, mouseEvent);
+            App?.Mouse?.RaiseMouseEvent(mouseEvent);
             return true;
             return true;
         }
         }
         return base.OnMouseEvent(mouseEvent);
         return base.OnMouseEvent(mouseEvent);
@@ -209,16 +331,31 @@ view.MouseLeave += (sender, e) =>
 
 
 Mouse coordinates in Terminal.Gui are provided in multiple coordinate systems:
 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:
 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
 ## 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  
 * **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
 * **Respect platform conventions** - use right-click for context menus, double-click for default actions
 * **Provide keyboard alternatives** - ensure all mouse functionality has keyboard equivalents
 * **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
 * Mouse wheel support may vary between platforms and terminals
 * Some terminals may not support all mouse buttons or modifier keys
 * 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
 * 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

+ 1 - 1
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 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 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 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
 ## See Also