Browse Source

Merge branch 'v2_develop' into v2_release

Tig 1 month ago
parent
commit
d2b06f17b0
100 changed files with 4970 additions and 3903 deletions
  1. 5 0
      .cursorrules
  2. 3 3
      .editorconfig
  3. 123 24
      .github/ISSUE_TEMPLATE/bug_report.md
  4. 7 0
      .github/copilot-instructions.md
  5. 21 10
      .github/workflows/build-validation.yml
  6. 86 40
      .github/workflows/integration-tests.yml
  7. 43 0
      .github/workflows/quick-build.yml
  8. 120 32
      .github/workflows/unit-tests.yml
  9. 5 0
      .gitignore
  10. 14 0
      AGENTS.md
  11. 370 133
      CONTRIBUTING.md
  12. 4 4
      Directory.Packages.props
  13. 20 1
      Examples/Example/Example.cs
  14. 2 2
      Examples/NativeAot/Program.cs
  15. 1 1
      Examples/SelfContained/Program.cs
  16. 22 68
      Examples/UICatalog/Properties/launchSettings.json
  17. 9 16
      Examples/UICatalog/Scenario.cs
  18. 12 12
      Examples/UICatalog/Scenarios/Adornments.cs
  19. 6 10
      Examples/UICatalog/Scenarios/AllViewsTester.cs
  20. 4 1
      Examples/UICatalog/Scenarios/AnimationScenario/AnimationScenario.cs
  21. 1 1
      Examples/UICatalog/Scenarios/AnsiRequestsScenario.cs
  22. 0 1
      Examples/UICatalog/Scenarios/Arrangement.cs
  23. 3 3
      Examples/UICatalog/Scenarios/Bars.cs
  24. 22 49
      Examples/UICatalog/Scenarios/Buttons.cs
  25. 7 31
      Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs
  26. 1 2
      Examples/UICatalog/Scenarios/Clipping.cs
  27. 1 1
      Examples/UICatalog/Scenarios/CollectionNavigatorTester.cs
  28. 19 15
      Examples/UICatalog/Scenarios/ColorPicker.cs
  29. 1 1
      Examples/UICatalog/Scenarios/CombiningMarks.cs
  30. 7 10
      Examples/UICatalog/Scenarios/Dialogs.cs
  31. 5 5
      Examples/UICatalog/Scenarios/DimAutoDemo.cs
  32. 10 10
      Examples/UICatalog/Scenarios/DynamicMenuBar.cs
  33. 3 3
      Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentsEditor.cs
  34. 13 79
      Examples/UICatalog/Scenarios/EditorsAndHelpers/ArrangementEditor.cs
  35. 14 13
      Examples/UICatalog/Scenarios/EditorsAndHelpers/BorderEditor.cs
  36. 9 9
      Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs
  37. 14 22
      Examples/UICatalog/Scenarios/EditorsAndHelpers/MarginEditor.cs
  38. 9 9
      Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs
  39. 9 10
      Examples/UICatalog/Scenarios/EditorsAndHelpers/ViewPropertiesEditor.cs
  40. 138 138
      Examples/UICatalog/Scenarios/FileDialogExamples.cs
  41. 6 6
      Examples/UICatalog/Scenarios/GraphViewExample.cs
  42. 17 17
      Examples/UICatalog/Scenarios/Images.cs
  43. 1 4
      Examples/UICatalog/Scenarios/Keys.cs
  44. 3 3
      Examples/UICatalog/Scenarios/LineCanvasExperiment.cs
  45. 10 4
      Examples/UICatalog/Scenarios/LineDrawing.cs
  46. 219 0
      Examples/UICatalog/Scenarios/LineExample.cs
  47. 0 76
      Examples/UICatalog/Scenarios/LineViewExample.cs
  48. 3 3
      Examples/UICatalog/Scenarios/Mazing.cs
  49. 9 8
      Examples/UICatalog/Scenarios/MessageBoxes.cs
  50. 14 12
      Examples/UICatalog/Scenarios/Navigation.cs
  51. 42 105
      Examples/UICatalog/Scenarios/Notepad.cs
  52. 2 2
      Examples/UICatalog/Scenarios/NumericUpDownDemo.cs
  53. 145 139
      Examples/UICatalog/Scenarios/PosAlignDemo.cs
  54. 17 13
      Examples/UICatalog/Scenarios/ProgressBarStyles.cs
  55. 18 45
      Examples/UICatalog/Scenarios/RegionScenario.cs
  56. 5 9
      Examples/UICatalog/Scenarios/ScrollBarDemo.cs
  57. 19 15
      Examples/UICatalog/Scenarios/Scrolling.cs
  58. 287 0
      Examples/UICatalog/Scenarios/Selectors.cs
  59. 104 66
      Examples/UICatalog/Scenarios/Shortcuts.cs
  60. 246 107
      Examples/UICatalog/Scenarios/TableEditor.cs
  61. 10 10
      Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs
  62. 2 2
      Examples/UICatalog/Scenarios/TextInputControls.cs
  63. 7 7
      Examples/UICatalog/Scenarios/Themes.cs
  64. 0 228
      Examples/UICatalog/Scenarios/TileViewNesting.cs
  65. 12 15
      Examples/UICatalog/Scenarios/TimeAndDate.cs
  66. 1 1
      Examples/UICatalog/Scenarios/TreeViewFileSystem.cs
  67. 13 14
      Examples/UICatalog/Scenarios/Unicode.cs
  68. 2 2
      Examples/UICatalog/Scenarios/ViewportSettings.cs
  69. 1 1
      Examples/UICatalog/Scenarios/WindowsAndFrameViews.cs
  70. 6 6
      Examples/UICatalog/Scenarios/Wizards.cs
  71. 44 19
      Examples/UICatalog/UICatalog.cs
  72. 52 44
      Examples/UICatalog/UICatalogTop.cs
  73. 2 3
      README.md
  74. 109 0
      Scripts/Run-LocalCoverage.ps1
  75. 3 2
      Terminal.Gui.Analyzers.Tests/HandledEventArgsAnalyzerTests.cs
  76. 62 56
      Terminal.Gui.Analyzers.Tests/ProjectBuilder.cs
  77. 53 29
      Terminal.Gui/App/Application.Driver.cs
  78. 0 260
      Terminal.Gui/App/Application.Initialization.cs
  79. 19 305
      Terminal.Gui/App/Application.Keyboard.cs
  80. 95 0
      Terminal.Gui/App/Application.Lifecycle.cs
  81. 48 252
      Terminal.Gui/App/Application.Mouse.cs
  82. 22 47
      Terminal.Gui/App/Application.Navigation.cs
  83. 5 1
      Terminal.Gui/App/Application.Popover.cs
  84. 49 579
      Terminal.Gui/App/Application.Run.cs
  85. 14 74
      Terminal.Gui/App/Application.Screen.cs
  86. 7 17
      Terminal.Gui/App/Application.Toplevel.cs
  87. 4 4
      Terminal.Gui/App/Application.cd
  88. 7 114
      Terminal.Gui/App/Application.cs
  89. 176 0
      Terminal.Gui/App/ApplicationImpl.Driver.cs
  90. 251 0
      Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
  91. 347 0
      Terminal.Gui/App/ApplicationImpl.Run.cs
  92. 177 0
      Terminal.Gui/App/ApplicationImpl.Screen.cs
  93. 62 286
      Terminal.Gui/App/ApplicationImpl.cs
  94. 28 14
      Terminal.Gui/App/CWP/CWPPropertyHelper.cs
  95. 2 2
      Terminal.Gui/App/Clipboard/ClipboardBase.cs
  96. 1 1
      Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs
  97. 449 104
      Terminal.Gui/App/IApplication.cs
  98. 1 1
      Terminal.Gui/App/IterationEventArgs.cs
  99. 113 0
      Terminal.Gui/App/Keyboard/IKeyboard.cs
  100. 384 0
      Terminal.Gui/App/Keyboard/KeyboardImpl.cs

+ 5 - 0
.cursorrules

@@ -0,0 +1,5 @@
+# Terminal.Gui - Cursor AI Rules
+
+> **📘 Source of Truth: [CONTRIBUTING.md](CONTRIBUTING.md)**
+
+This project uses [CONTRIBUTING.md](CONTRIBUTING.md) as the single source of truth for contribution guidelines. AI agents, including CoPilot and Cursor **MUST** follow the guidelines in [CONTRIBUTING.md](CONTRIBUTING.md)/

+ 3 - 3
.editorconfig

@@ -82,8 +82,8 @@ dotnet_diagnostic.cs0464.severity = warning
 dotnet_diagnostic.cs0465.severity = warning
 dotnet_diagnostic.cs0469.severity = warning
 dotnet_diagnostic.cs0472.severity = warning
-dotnet_diagnostic.cs0612.severity = warning
-dotnet_diagnostic.cs0618.severity = warning
+dotnet_diagnostic.cs0612.severity = none
+dotnet_diagnostic.cs0618.severity = none
 dotnet_diagnostic.cs0628.severity = warning
 dotnet_diagnostic.cs0642.severity = warning
 dotnet_diagnostic.cs0649.severity = warning
@@ -94,7 +94,7 @@ dotnet_diagnostic.cs0659.severity = warning
 dotnet_diagnostic.cs0660.severity = warning
 dotnet_diagnostic.cs0661.severity = warning
 dotnet_diagnostic.cs0665.severity = warning
-dotnet_diagnostic.cs0672.severity = warning
+dotnet_diagnostic.cs0672.severity = none
 dotnet_diagnostic.cs0675.severity = warning
 dotnet_diagnostic.cs0693.severity = warning
 dotnet_diagnostic.cs0728.severity = warning

+ 123 - 24
.github/ISSUE_TEMPLATE/bug_report.md

@@ -1,41 +1,140 @@
 ---
 name: Bug report
-about: Create a report to help us improve
+about: Create a report to help us improve Terminal.Gui
 title: ''
 labels: bug
 assignees: ''
 
 ---
 
-**Describe the bug**
+## Describe the bug
+
 A clear and concise description of what the bug is.
 
-**To Reproduce**
+## To Reproduce
+
 Steps to reproduce the behavior:
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
 
-**Expected behavior**
-A clear and concise description of what you expected to happen.
+1. Run the following code:
+   ```csharp
+   // Paste your minimal reproduction code here
+   ```
+
+2. Expected behavior: (describe what should happen)
+
+3. Actual behavior: (describe what actually happens)
+
+## Environment
+
+Please run the following commands in your terminal and paste the output:
+
+### OS Information
+
+**Windows (PowerShell):**
+```powershell
+"OS: $(Get-CimInstance Win32_OperatingSystem | Select-Object -ExpandProperty Caption) $(Get-CimInstance Win32_OperatingSystem | Select-Object -ExpandProperty Version)"
+```
+
+**macOS/Linux:**
+```bash
+echo "OS: $(uname -s) $(uname -r)"
+```
+
+**Output:**
+```
+(paste output here)
+```
+
+### Terminal Information
+
+**Windows Terminal:**
+```powershell
+"Terminal: Windows Terminal $(Get-AppxPackage -Name Microsoft.WindowsTerminal | Select-Object -ExpandProperty Version)"
+```
+
+**Other terminals:**
+```bash
+echo $TERM
+```
+
+**Output:**
+```
+(paste output here)
+```
+
+### PowerShell Version
+
+```powershell
+$PSVersionTable.PSVersion
+```
+
+**Output:**
+```
+(paste output here)
+```
+
+### .NET Information
+
+```bash
+dotnet --version
+dotnet --info
+```
+
+**Output:**
+```
+(paste output here)
+```
+
+### Terminal.Gui Version
+
+**Option 1 - Run UICatalog (easiest):**
+
+UICatalog displays the Terminal.Gui version in its About box and status bar.
+
+```bash
+dotnet run --project Examples/UICatalog/UICatalog.csproj
+```
+
+**Option 2 - NuGet Package Version:**
+```
+(e.g., 2.0.0-alpha.1, 2.0.0-develop.123, etc.)
+```
+
+**Option 3 - Building from source:**
+```bash
+git rev-parse HEAD
+git describe --tags --always --dirty
+```
+
+**Version:**
+```
+(paste version here)
+```
+
+## Screenshots, GIFs, or Terminal Output
+
+If applicable, add screenshots, animated GIFs, or copy/paste terminal output to help explain your problem.
+
+**Animated GIFs are especially helpful for showing behavior!**
+
+- **Windows**: [ShareX](https://getsharex.com/) (free, captures screen to GIF)
+- **macOS**: [Kap](https://getkap.co/) (free, open source)
+- **Linux**: [Peek](https://github.com/phw/peek) (free)
+
+**For terminal output, use code blocks:**
 
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
+```
+(paste terminal output here)
+```
 
-**Desktop (please complete the following information):**
- - OS: [e.g. iOS]
- - Browser [e.g. chrome, safari]
- - Version [e.g. 22]
+## Additional context
 
-**Smartphone (please complete the following information):**
- - Device: [e.g. iPhone6]
- - OS: [e.g. iOS8.1]
- - Browser [e.g. stock browser, safari]
- - Version [e.g. 22]
+Add any other context about the problem here, such as:
+- Does this happen consistently or intermittently?
+- Did this work in a previous version?
+- Are there any error messages in the console?
+- Terminal configuration or settings that might be relevant?
 
-**Additional context**
-Add any other context about the problem here.
+## For Maintainers
 
-**Set Project & Milestone**
-If you have access, please don't forget to set the right Project and Milestone.
+**Set Project & Milestone:** If you have access, please don't forget to set the right Project and Milestone.

+ 7 - 0
.github/copilot-instructions.md

@@ -0,0 +1,7 @@
+# Terminal.Gui - CoPilot AI Rules
+
+> **📘 Source of Truth: [../CONTRIBUTING.md](../CONTRIBUTING.md)**
+
+This project uses [../CONTRIBUTING.md](../CONTRIBUTING.md) as the single source of truth for contribution guidelines. AI agents, including CoPilot and Cursor **MUST** follow the guidelines in [../CONTRIBUTING.md](CONTRIBUTING.md)/
+
+**CRITICAL** - CoPilot you MUST internalize [../CONTRIBUTING.md](../CONTRIBUTING.md) and follow the guidance for any work you do on Terminal.Gui. This includes Github Copilot in Agent mode. 

+ 21 - 10
.github/workflows/build-release.yml → .github/workflows/build-validation.yml

@@ -1,4 +1,4 @@
-name: Ensure that Release Build of Solution Builds Correctly
+name: Build Validation
 
 on:
   push:
@@ -11,11 +11,14 @@ on:
       - '**.md'
       
 jobs:
-  build_release:
-    # Ensure that RELEASE builds are not broken
+  build-validation:
+    name: Build All Configurations
     runs-on: ubuntu-latest
+    
+    timeout-minutes: 10
     steps:
-    - name: Checkout ${{ github.ref_name }}
+
+    - name: Checkout code
       uses: actions/checkout@v4
 
     - name: Setup .NET Core
@@ -24,11 +27,19 @@ jobs:
         dotnet-version: 8.x
         dotnet-quality: 'ga'
 
+    - name: Restore dependencies
+      run: dotnet restore
+
+    # Suppress CS0618 (member is obsolete) and CS0612 (member is obsolete without message)
+    # Using -property: syntax with URL-encoded semicolon (%3B) to avoid shell interpretation issues
+    - name: Build Debug
+      run: dotnet build --configuration Debug --no-restore -property:NoWarn=0618%3B0612
+
     - name: Build Release Terminal.Gui
-      run: dotnet build Terminal.Gui/Terminal.Gui.csproj --no-incremental --nologo --force --configuration Release
+      run: dotnet build Terminal.Gui/Terminal.Gui.csproj --configuration Release --no-incremental --force -property:NoWarn=0618%3B0612
 
     - name: Pack Release Terminal.Gui
-      run: dotnet pack Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ./local_packages
+      run: dotnet pack Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ./local_packages -property:NoWarn=0618%3B0612
 
     - name: Restore AOT and Self-Contained projects
       run: |
@@ -40,8 +51,8 @@ jobs:
 
     - name: Build Release AOT and Self-Contained
       run: |
-        dotnet build ./Examples/NativeAot/NativeAot.csproj --configuration Release
-        dotnet build ./Examples/SelfContained/SelfContained.csproj --configuration Release
+        dotnet build ./Examples/NativeAot/NativeAot.csproj --configuration Release -property:NoWarn=0618%3B0612
+        dotnet build ./Examples/SelfContained/SelfContained.csproj --configuration Release -property:NoWarn=0618%3B0612
 
-    - name: Build Release Solution without restore
-      run: dotnet build --configuration Release --no-restore
+    - name: Build Release Solution
+      run: dotnet build --configuration Release --no-restore -property:NoWarn=0618%3B0612

+ 86 - 40
.github/workflows/integration-tests.yml

@@ -1,5 +1,4 @@
 name: Build & Run Integration Tests
-
 on:
   push:
     branches: [ v2_release, v2_develop ]
@@ -9,52 +8,99 @@ on:
     branches: [ v2_release, v2_develop ]
     paths-ignore:
       - '**.md'
-      
+
 jobs:
-  build_and_test_debug:
+  build:
+    uses: ./.github/workflows/quick-build.yml
 
+  integration_tests:
+    name: Integration Tests
     runs-on: ${{ matrix.os }}
+    needs: build
     strategy:
-      # Turn off fail-fast to let all runners run even if there are errors
-      fail-fast: true
+      fail-fast: false  # Let all OSes finish even if one fails
       matrix:
         os: [ ubuntu-latest, windows-latest, macos-latest ]
+    timeout-minutes: 15
 
-    timeout-minutes: 10
     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: Download build artifacts
+        uses: actions/download-artifact@v4
+        with:
+          name: test-build-artifacts
+          path: .
+
+      - name: Restore NuGet packages
+        run: dotnet 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/IntegrationTests/${{ runner.os }}/" >> $GITHUB_ENV
+
+      - name: Run IntegrationTests
+        shell: bash
+        run: |
+          if [ "${{ runner.os }}" == "Linux" ]; then
+            # Run with coverage on Linux only
+            dotnet test Tests/IntegrationTests \
+              --no-build \
+              --verbosity minimal \
+              --collect:"XPlat Code Coverage" \
+              --settings Tests/IntegrationTests/runsettings.coverage.xml \
+              --diag:logs/IntegrationTests/${{ runner.os }}/logs.txt \
+              --blame \
+              --blame-crash \
+              --blame-hang \
+              --blame-hang-timeout 60s \
+              --blame-crash-collect-always
+          else
+            # Run without coverage on Windows/macOS for speed
+            dotnet test Tests/IntegrationTests \
+              --no-build \
+              --verbosity minimal \
+              --settings Tests/IntegrationTests/runsettings.xml \
+              --diag:logs/IntegrationTests/${{ runner.os }}/logs.txt \
+              --blame \
+              --blame-crash \
+              --blame-hang \
+              --blame-hang-timeout 60s \
+              --blame-crash-collect-always
+          fi
+
+      - name: Upload Integration Test Logs
+        if: always()
+        uses: actions/upload-artifact@v4
+        with:
+          name: integration_tests-logs-${{ runner.os }}
+          path: |
+            logs/IntegrationTests/
+            TestResults/
 
-    - 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 IntegrationTests
-      run: dotnet build Tests/IntegrationTests --configuration Debug --no-restore
-
-    - name: Set VSTEST_DUMP_PATH
-      shell: bash
-      run: echo "{VSTEST_DUMP_PATH}={logs/${{ runner.os }}/}" >> $GITHUB_ENV
-
-    - name: Run IntegrationTests
-      run: |
-       dotnet test Tests/IntegrationTests --no-build --verbosity normal --diag:logs/${{ runner.os }}/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always -- xunit.stopOnFail=true
-     
-       # mv -v Tests/IntegrationTests/TestResults/*/*.* TestResults/IntegrationTests/
-
-    - name: Upload Test Logs
-      if: always()
-      uses: actions/upload-artifact@v4
-      with:
-        name: integration-test-logs-${{ runner.os }}
-        path: |
-          logs/    
-          TestResults/IntegrationTests/
+      - name: Upload Integration Tests Coverage to Codecov
+        if: matrix.os == 'ubuntu-latest' && always()
+        uses: codecov/codecov-action@v4
+        with:
+          files: TestResults/**/coverage.cobertura.xml
+          flags: integrationtests
+          name: IntegrationTests-${{ runner.os }}
+          token: ${{ secrets.CODECOV_TOKEN }}
+          fail_ci_if_error: false
 

+ 43 - 0
.github/workflows/quick-build.yml

@@ -0,0 +1,43 @@
+name: Quick Build for Tests
+
+on:
+  workflow_call:
+    outputs:
+      artifact-name:
+        description: "Name of the build artifacts"
+        value: ${{ jobs.quick-build.outputs.artifact-name }}
+      
+jobs:
+  quick-build:
+    name: Build Debug Only
+    runs-on: ubuntu-latest
+    outputs:
+      artifact-name: test-build-artifacts
+    
+    timeout-minutes: 5
+    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: Restore dependencies
+      run: dotnet restore
+
+    # Suppress CS0618 (member is obsolete) and CS0612 (member is obsolete without message)
+    - name: Build Debug
+      run: dotnet build --configuration Debug --no-restore -property:NoWarn=0618%3B0612
+
+    - name: Upload build artifacts
+      uses: actions/upload-artifact@v4
+      with:
+        name: test-build-artifacts
+        path: |
+          **/bin/Debug/**
+          **/obj/Debug/**
+        retention-days: 1

+ 120 - 32
.github/workflows/unit-tests.yml

@@ -11,16 +11,21 @@ on:
       - '**.md'
       
 jobs:
+  # Call the quick-build workflow to build Debug configuration only
+  build:
+    uses: ./.github/workflows/quick-build.yml
+
   non_parallel_unittests:
     name: Non-Parallel Unit Tests  
     runs-on: ${{ matrix.os }}
+    needs: build
     strategy:
       # Turn off fail-fast to let all runners run even if there are errors
-      fail-fast: true
+      fail-fast: false
       matrix:
         os: [ ubuntu-latest, windows-latest, macos-latest ]
 
-    timeout-minutes: 10
+    timeout-minutes: 15  # Increased from 10 for Windows
     steps:
 
     - name: Checkout code
@@ -32,27 +37,59 @@ jobs:
         dotnet-version: 8.x
         dotnet-quality: 'ga'
 
-    - name: Install dependencies
-      run: |
-        dotnet restore
+    - name: Download build artifacts
+      uses: actions/download-artifact@v4
+      with:
+        name: test-build-artifacts
+        path: .
 
-    - name: Build Solution Debug
-      run: dotnet build --configuration Debug --no-restore
+    # KEEP THIS - It's needed for --no-build to work
+    - name: Restore NuGet packages
+      run: dotnet restore
 
-# Test
-    # Note: The --blame and VSTEST_DUMP_PATH stuff is needed to diagnose the test runner crashing on ubuntu/mac
-    # See https://github.com/microsoft/vstest/issues/2952 for why the --blame stuff below is needed.
-    # Without it, the test runner crashes on ubuntu (but not Windows or mac)
+    # Optimize Windows performance
+    - 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/UnitTests/${{ runner.os }}/}" >> $GITHUB_ENV
+      run: echo "VSTEST_DUMP_PATH=logs/UnitTests/${{ runner.os }}/" >> $GITHUB_ENV
 
     - name: Run UnitTests
+      shell: bash
       run: |
-       dotnet test Tests/UnitTests --no-build --verbosity normal --collect:"XPlat Code Coverage" --settings Tests/UnitTests/coverlet.runsettings --diag:logs/UnitTests/${{ runner.os }}/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always -- xunit.stopOnFail=true
-     
-       # mv -v Tests/UnitTests/TestResults/*/*.* TestResults/UnitTests/
+        if [ "${{ runner.os }}" == "Linux" ]; then
+          # Run with coverage on Linux only
+          dotnet test Tests/UnitTests \
+            --no-build \
+            --verbosity normal \
+            --collect:"XPlat Code Coverage" \
+            --settings Tests/UnitTests/runsettings.xml \
+            --diag:logs/UnitTests/${{ runner.os }}/logs.txt \
+            --blame \
+            --blame-crash \
+            --blame-hang \
+            --blame-hang-timeout 60s \
+            --blame-crash-collect-always
+        else
+          # Run without coverage on Windows/macOS for speed
+          dotnet test Tests/UnitTests \
+            --no-build \
+            --verbosity normal \
+            --settings Tests/UnitTests/runsettings.xml \
+            --diag:logs/UnitTests/${{ runner.os }}/logs.txt \
+            --blame \
+            --blame-crash \
+            --blame-hang \
+            --blame-hang-timeout 120s \
+            --blame-crash-collect-always
+        fi
 
     - name: Upload Test Logs
       if: always()
@@ -61,18 +98,29 @@ jobs:
         name: non_parallel_unittests-logs-${{ runner.os }}
         path: |
           logs/UnitTests    
-          TestResults/UnitTests/
+          TestResults/
   
+    - name: Upload Non-Parallel UnitTests Coverage to Codecov
+      if: matrix.os == 'ubuntu-latest' && always()
+      uses: codecov/codecov-action@v4
+      with:
+        files: TestResults/**/coverage.cobertura.xml
+        flags: unittests-nonparallel
+        name: UnitTests-${{ runner.os }}
+        token: ${{ secrets.CODECOV_TOKEN }}
+        fail_ci_if_error: false
+
   parallel_unittests:
     name: Parallel Unit Tests  
     runs-on: ${{ matrix.os }}
+    needs: build
     strategy:
       # Turn off fail-fast to let all runners run even if there are errors
-      fail-fast: true
+      fail-fast: false
       matrix:
         os: [ ubuntu-latest, windows-latest, macos-latest ]
 
-    timeout-minutes: 10
+    timeout-minutes: 15
     steps:
 
     - name: Checkout code
@@ -84,27 +132,57 @@ jobs:
         dotnet-version: 8.x
         dotnet-quality: 'ga'
 
-    - name: Install dependencies
-      run: |
-        dotnet restore
+    - name: Download build artifacts
+      uses: actions/download-artifact@v4
+      with:
+        name: test-build-artifacts
+        path: .
 
-    - name: Build Solution Debug
-      run: dotnet build --configuration Debug --no-restore
+    - name: Restore NuGet packages
+      run: dotnet restore
 
-# Test
-    # Note: The --blame and VSTEST_DUMP_PATH stuff is needed to diagnose the test runner crashing on ubuntu/mac
-    # See https://github.com/microsoft/vstest/issues/2952 for why the --blame stuff below is needed.
-    # Without it, the test runner crashes on ubuntu (but not Windows or mac)
+    - 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
+      run: echo "VSTEST_DUMP_PATH=logs/UnitTestsParallelizable/${{ runner.os }}/" >> $GITHUB_ENV
 
     - name: Run UnitTestsParallelizable
+      shell: bash
       run: |
-       dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal --collect:"XPlat Code Coverage" --settings Tests/UnitTestsParallelizable/coverlet.runsettings --diag:logs/UnitTestsParallelizable/${{ runner.os }}/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always -- xunit.stopOnFail=true
-     
-       # mv -v Tests/UnitTestsParallelizable/TestResults/*/*.* TestResults/UnitTestsParallelizable/
+        if [ "${{ runner.os }}" == "Linux" ]; then
+          # Run with coverage on Linux only
+          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
+        else
+          # Run without coverage on Windows/macOS for speed
+          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
+        fi
 
     - name: Upload UnitTestsParallelizable Logs
       if: always()
@@ -113,4 +191,14 @@ jobs:
         name: parallel_unittests-logs-${{ runner.os }}
         path: |
           logs/UnitTestsParallelizable/
-          TestResults/UnitTestsParallelizable/
+          TestResults/
+  
+    - name: Upload Parallelizable UnitTests Coverage to Codecov
+      if: matrix.os == 'ubuntu-latest' && always()
+      uses: codecov/codecov-action@v4
+      with:
+        files: TestResults/**/coverage.cobertura.xml
+        flags: unittests-parallel
+        name: UnitTestsParallelizable-${{ runner.os }}
+        token: ${{ secrets.CODECOV_TOKEN }}
+        fail_ci_if_error: false

+ 5 - 0
.gitignore

@@ -68,3 +68,8 @@ BenchmarkDotNet.Artifacts/
 *.log.*
 
 log.*
+
+/Tests/coverage/
+!/Tests/coverage/.gitkeep   # keep folder in repo
+/Tests/report/
+*.cobertura.xml

+ 14 - 0
AGENTS.md

@@ -0,0 +1,14 @@
+# Terminal.Gui - AI Agent Instructions
+
+> **📘 For complete contributor guidelines (humans and AI agents), see [CONTRIBUTING.md](CONTRIBUTING.md).**
+
+This repository uses [CONTRIBUTING.md](CONTRIBUTING.md) as the single source of truth for code style, testing, CI/CD, and contribution workflow. GitHub Copilot and other AI coding agents should also refer to [.github/copilot-instructions.md](.github/copilot-instructions.md) for a curated summary of non-negotiable rules.
+
+**Key highlights for AI agents:**
+- Always use explicit types (no `var` except for built-in simple types)
+- Always use target-typed `new()` syntax
+- Add new tests to `Tests/UnitTestsParallelizable/` when possible
+- Never decrease code coverage
+- Follow `.editorconfig` and `Terminal.sln.DotSettings` for formatting
+
+See [CONTRIBUTING.md](CONTRIBUTING.md) for complete details.

+ 370 - 133
CONTRIBUTING.md

@@ -1,191 +1,428 @@
 # Contributing to Terminal.Gui
 
-We welcome contributions from the community. See [Issues](https://github.com/gui-cs/Terminal.Gui/issues) for a list of open [bugs](https://github.com/gui-cs/Terminal.Gui/issues?q=is%3Aopen+is%3Aissue+label%3Abug) and [enhancements](https://github.com/gui-cs/Terminal.Gui/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement). Contributors looking for something fun to work on should look at issues tagged as:
+> **📘 This document is the single source of truth for all contributors (humans and AI agents) to Terminal.Gui.**
 
-- [good first issue](https://github.com/gui-cs/Terminal.Gui/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
-- [up for grabs](https://github.com/gui-cs/Terminal.Gui/issues?q=is%3Aopen+is%3Aissue+label%3Aup-for-grabs)
-- [help wanted](https://github.com/gui-cs/Terminal.Gui/issues?q=is%3Aopen+is%3Aissue+label%3Aup-for-grabs)
+Welcome! This guide provides everything you need to know to contribute effectively to Terminal.Gui, including project structure, build instructions, coding conventions, testing requirements, and CI/CD workflows.
 
-## Forking and Submitting Changes
+## Table of Contents
 
-Terminal.Gui uses the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. 
+- [Project Overview](#project-overview)
+- [Building and Testing](#building-and-testing)
+- [Coding Conventions](#coding-conventions)
+- [Testing Requirements](#testing-requirements)
+- [API Documentation Requirements](#api-documentation-requirements)
+- [Pull Request Guidelines](#pull-request-guidelines)
+- [CI/CD Workflows](#cicd-workflows)
+- [Repository Structure](#repository-structure)
+- [Branching Model](#branching-model)
+- [Key Architecture Concepts](#key-architecture-concepts)
+- [What NOT to Do](#what-not-to-do)
+- [Additional Resources](#additional-resources)
 
-* The `v1_release` and `v2_release` branches are always stable, and always match the most recently released Nuget package.
-* The `v1_develop` and `v2_develop` branches are where new development and bug-fixes happen. `v2_develop` is the default Github branch.
+---
 
-### Forking Terminal.Gui
+## Project Overview
 
-1. Use GitHub to fork the `Terminal.Gui` repo to your account (https://github.com/gui-cs/Terminal.Gui/fork).
+**Terminal.Gui** is a cross-platform UI toolkit for creating console-based graphical user interfaces in .NET. It's a large codebase (~1,050 C# files) providing a comprehensive framework for building interactive console applications with support for keyboard and mouse input, customizable views, and a robust event system.
 
-2. Clone your fork to your local machine
+**Key characteristics:**
+- **Language**: C# (net8.0)
+- **Platform**: Cross-platform (Windows, macOS, Linux)
+- **Architecture**: Console UI toolkit with driver-based architecture
+- **Version**: v2 (Alpha), v1 (maintenance mode)
+- **Branching**: GitFlow model (v2_develop is default/active development)
 
-```
-git clone https://github.com/<yourID>/Terminal.Gui
-```
-
-Now, your local repo will have an `origin` remote pointing to `https://github.com/<yourID>/Terminal.Gui`.
-
-3. Add a remote for `upstream`: 
-```
-git remote add upstream https://github.com/gui-cs/Terminal.Gui
-```
-You now have your own fork and a local repo that references it as `origin`. Your local repo also now references the orignal Terminal.Gui repo as `upstream`. 
-
-### Starting to Make a Change
-
-Ensure your local `v1_develop` (for v1) or `v2_develop` (for v2) branch is up-to-date with `upstream` (`github.com/gui-cs/Terminal.Gui`):
-```powershell
-cd ./Terminal.Gui
-git checkout v2_develop
-git pull upstream v2_develop
-```
-
-Create a new local branch:
-```powershell
-git checkout -b my_new_branch
-```
+---
 
-### Making Changes
-Follow all the guidelines below.
+## Building and Testing
 
-* [Coding Style](#TerminalGui-Coding-Style)
-* [Unit Tests](#Unit-Tests)
-* [Sample Code](#Sample-Code)
-* API Documentation
-* etc...
+### Required Tools
 
-When you're ready, commit your changes:
+- **.NET SDK**: 8.0.0 (see `global.json`)
+- **Runtime**: .NET 8.x (latest GA)
+- **Optional**: ReSharper/Rider for code formatting (honor `.editorconfig` and `Terminal.sln.DotSettings`)
 
-```powershell
-git add .
-git commit -m "Fixes #1234. Some bug"
-```
+### Build Commands (In Order)
 
-### Submitting a Pull Request
+**ALWAYS run these commands from the repository root:**
 
-1. Push your local branch to your fork (`origin`):
+1. **Restore packages** (required first, ~15-20 seconds):
+   ```bash
+   dotnet restore
+   ```
 
-```powershell
-git push --set-upstream origin my_new_branch
-```
+2. **Build solution** (Debug, ~50 seconds):
+   ```bash
+   dotnet build --configuration Debug --no-restore
+   ```
+   - Expect ~326 warnings (nullable reference warnings, unused variables, etc.) - these are normal
+   - 0 errors expected
 
-2. Create the Pull Request:
+3. **Build Release** (for packaging):
+   ```bash
+   dotnet build --configuration Release --no-restore
+   ```
 
-In the output of the `git push` command you'll see instructions with a link to the Pull Request:
-
-```powershell
- $ git push --set-upstream origin my_new_branch
-Enumerating objects: 8, done.
-...
-remote:
-remote: Create a pull request for 'my_new_branch' on GitHub by visiting:
-remote:      https://github.com/<yourID>/Terminal.Gui/pull/new/more_doc_fixes
-remote:
-...
-```
+### Test Commands
 
-3. Go to that URL and create the Pull Request:
+**Two test projects exist:**
 
-(in Windows Terminal, just CTRL-Click on the URL)
+1. **Non-parallel tests** (depend on static state, ~10 min timeout):
+   ```bash
+   dotnet test Tests/UnitTests --no-build --verbosity normal
+   ```
+   - Uses `Application.Init` and static state
+   - Cannot run in parallel
+   - Includes `--blame` flags for crash diagnostics
 
-Follow the template instructions found on Github.
+2. **Parallel tests** (can run concurrently, ~10 min timeout):
+   ```bash
+   dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal
+   ```
+   - No dependencies on static state
+   - **Preferred for new tests**
 
-## Tenets for [gui-cs](www.github.com/gui-cs) Code Style (Unless you have better ones)
+3. **Integration tests**:
+   ```bash
+   dotnet test Tests/IntegrationTests --no-build --verbosity normal
+   ```
 
-* **Six-Year-Old Reading Level** - Our code style is biased towards code readability and away from terseness. This is *Systems Software* and needs to stand the test of time. Code should be structured and use variable names that make it readable by a 6-year-old, and comments in code are encouraged. 
-* **Consistency, Consistency, Consistency** - We adopt and document our standards for code style and then enforce them ruthlessly. For example, we require code reviews to pay attention to code style, not just functionality. 
-* **Don't be Weird** - Like all developers we have opinions, but our opinions on code style are tempered by existing standards. We are biased towards code style that used by Microsoft and other leading dotnet developers. For example, we choose 4 spaces for indentation instead of 8.
-* **Set and Forget** - We embrace and encourage the use of technology that makes it easy for contributors to apply best-practice code-style, such as ReSharper. As we do so we are mindful that tools can cause hidden issues and merge hell.
-* **Documentation is the Spec** - We care deeply about providing delightful developer documentation and are sticklers for grammar and clarity. If the code and the docs conflict, we are biased to believe what we wrote in the API documentation. This drives a virtuous cycle of clear thinking.
+### Common Build Issues
 
-**Terminal.Gui** uses a derivative of the [Microsoft C# Coding Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions), with any deviations from those (somewhat older) conventions codified in the .editorconfig for the solution, as well as even more specific definitions in team-shared dotsettings files, used by ReSharper and Rider.\
-Before you commit code, please run the formatting rules on **only the code file(s) you have modified**, in one of the following ways, in order of most preferred to least preferred:
+#### Issue: Build Warnings
+- **Expected**: None warnings (~100 currently).
+- **Action**: Don't add new warnings; fix warnings in code you modify
 
- 1. `Ctrl-E-C` if using ReSharper or Rider
- 2. Running the free [CleanupCode](https://www.jetbrains.com/help/resharper/CleanupCode.html) tool from JetBrains (this applies the same formatting rules as if you had used ReSharper or Rider, but is free for all users, if you don't have a license for those products)
-     - Run at the command line, from the solution root directory, as: `cleanupcode.exe relative/path/to/your/file.cs`
- 3. If you are unable to use either of those options, the last resort is to use `Ctrl-K-D` in Visual Studio (with default C# developer key bindings), to apply the subset of the formatting rules that Visual Studio can apply.
+#### Issue: NativeAot/SelfContained Build
+- **Solution**: Restore these projects explicitly:
+  ```bash
+  dotnet restore ./Examples/NativeAot/NativeAot.csproj -f
+  dotnet restore ./Examples/SelfContained/SelfContained.csproj -f
+  ```
 
-## User Experience Tenets
+### Running Examples
 
-**Terminal.Gui**, as a UI framework, heavily influences how console graphical user interfaces (GUIs) work. We use the following [tenets](https://ceklog.kindel.com/2020/02/10/tenets/) to guide us:
+**UICatalog** (comprehensive demo app):
+```bash
+dotnet run --project Examples/UICatalog/UICatalog.csproj
+```
 
-*NOTE: Like all tenets, these are up for debate. If you disagree, have questions, or suggestions about these tenets and guidelines submit an Issue using the [design](https://github.com/gui-cs/Terminal.Gui/issues?q=is%3Aopen+is%3Aissue+label%3Adesign) tag.*
+---
 
-1. **Honor What's Come Before**. The Mac and Windows OS's have well-established GUI idioms that are mostly consistent. We adhere to these versus inventing new ways for users to do things. For example, **Terminal.Gui** adopts the `ctrl/command-c`, `ctrl/command-v`, and `ctrl/command-x` keyboard shortcuts for cut, copy, and paste versus defining new shortcuts.
-2. **Consistency Matters**. Common UI idioms should be consistent across the GUI framework. For example, `ctrl/command-q` quits/exits all modal views. See [Issue #456](https://github.com/gui-cs/Terminal.Gui/issues/456) as a counter-example that should be fixed.
-3. **Honor the OS, but Work Everywhere**. **Terminal.Gui** is cross-platform, but we support taking advantage of a platform's unique advantages. For example, the Windows Console API is richer than the Unix API in terms of keyboard handling. Thus, in Windows pressing the `alt` key in a **Terminal.Gui** app will activate the `MenuBar`, but in Unix, the user has to press the full hotkey (e.g. `alt-f`) or `F9`. 
-4. **Keyboard first, Mouse also**. Users use consoles primarily with the keyboard; **Terminal.Gui** is optimized for getting stuff done without using the Mouse. However, as a GUI framework, the Mouse is essential thus we strive to ensure that everything also works via the Mouse.
+## Coding Conventions
+
+### Code Style Tenets
+
+1. **Six-Year-Old Reading Level** - Readability over terseness
+2. **Consistency, Consistency, Consistency** - Follow existing patterns ruthlessly
+3. **Don't be Weird** - Follow Microsoft/.NET conventions
+4. **Set and Forget** - Rely on automated tooling
+5. **Documentation is the Spec** - API docs are source of truth
+
+### Code Formatting
+
+**⚠️ CRITICAL - These rules MUST be followed in ALL new or modified code:**
+
+- **Do NOT add formatting tools** - Use existing `.editorconfig` and `Terminal.sln.DotSettings`
+- Format code with:
+  1. ReSharper/Rider (`Ctrl-E-C`)
+  2. JetBrains CleanupCode CLI tool (free)
+  3. Visual Studio (`Ctrl-K-D`) as fallback
+- **Only format files you modify**
+- Follow `.editorconfig` settings (e.g., braces on new lines, spaces after keywords)
+- 4-space indentation
+- No trailing whitespace
+- File-scoped namespaces
+- **ALWAYS use explicit types** - Never use `var` except for built-in simple types (`int`, `string`, `bool`, `double`, `float`, `decimal`, `char`, `byte`)
+  ```csharp
+  // ✅ CORRECT - Explicit types
+  View view = new () { Width = 10 };
+  MouseEventArgs args = new () { Position = new Point(5, 5) };
+  List<View?> views = new ();
+  var count = 0;  // OK - int is a built-in type
+  var name = "test";  // OK - string is a built-in type
+  
+  // ❌ WRONG - Using var for non-built-in types
+  var view = new View { Width = 10 };
+  var args = new MouseEventArgs { Position = new Point(5, 5) };
+  var views = new List<View?>();
+  ```
+
+- **ALWAYS use target-typed `new ()`** - Use `new ()` instead of `new TypeName()` when the type is already declared
+  ```csharp
+  // ✅ CORRECT - Target-typed new
+  View view = new () { Width = 10 };
+  MouseEventArgs args = new ();
+  
+  // ❌ WRONG - Redundant type name
+  View view = new View() { Width = 10 };
+  MouseEventArgs args = new MouseEventArgs();
+  ```
+
+**⚠️ CRITICAL - These conventions apply to ALL code - production code, test code, examples, and samples.**
+
+---
+
+## Testing Requirements
+
+### Code Coverage
+
+- **Never decrease code coverage** - PRs must maintain or increase coverage
+- Target: 70%+ coverage for new code
+- **Coverage collection**:
+- Centralized in `TestResults/` directory at repository root
+- Collected only on Linux (ubuntu-latest) runners in CI for performance
+- Windows and macOS runners skip coverage collection to reduce execution time
+- Coverage reports uploaded to Codecov automatically from Linux runner
+- CI monitors coverage on each PR
+
+### Test Patterns
+
+- **Parallelizable tests preferred** - Add new tests to `UnitTestsParallelizable` when possible
+- **Avoid static dependencies** - Don't use `Application.Init`, `ConfigurationManager` in tests
+- **Don't use `[AutoInitShutdown]`** - Legacy pattern, being phased out
+- **Make tests granular** - Each test should cover smallest area possible
+- Follow existing test patterns in respective test projects
+
+### Test Configuration
+
+- `xunit.runner.json` - xUnit configuration
+- `coverlet.runsettings` - Coverage settings (OpenCover format)
+
+---
+
+## API Documentation Requirements
+
+**All public APIs MUST have XML documentation:**
+
+- Clear, concise `<summary>` tags
+- Use `<see cref=""/>` for cross-references
+- Add `<remarks>` for context
+- Include `<example>` for non-obvious usage
+- Complex topics → `docfx/docs/*.md` files
+- Proper English and grammar - Clear, concise, complete. Use imperative mood.
+
+---
+
+## Pull Request Guidelines
+
+### PR Requirements
+
+- **Title**: "Fixes #issue. Terse description". If multiple issues, list all, separated by commas (e.g. "Fixes #123, #456. Terse description")
+- **Description**: 
+  - Include "- Fixes #issue" for each issue near the top
+  - **ALWAYS** include instructions for pulling down locally at end of Description
+  - Suggest user setup a remote named `copilot` pointing to your fork
+  - Example:
+    ```markdown
+    # To pull down this PR locally:
+    git remote add copilot <your-fork-url>
+    git fetch copilot <branch-name>
+    git checkout copilot/<branch-name>
+    ```
+- **Coding Style**: Follow all coding conventions in this document for new and modified code
+- **Tests**: Add tests for new functionality (see [Testing Requirements](#testing-requirements))
+- **Coverage**: Maintain or increase code coverage
+- **Scenarios**: Update UICatalog scenarios when adding features
+- **Warnings**: **CRITICAL - PRs must not introduce any new warnings**
+  - Any file modified in a PR that currently generates warnings **MUST** be fixed to remove those warnings
+  - Exception: Warnings caused by `[Obsolete]` attributes can remain
+  - Expected baseline: ~326 warnings (mostly nullable reference warnings, unused variables, xUnit suggestions)
+  - Action: Before submitting a PR, verify your changes don't add new warnings and fix any warnings in files you modify
+
+---
+
+## CI/CD Workflows
+
+The repository uses multiple GitHub Actions workflows. What runs and when:
+
+### 1) Build Solution (`.github/workflows/build.yml`)
+
+- **Triggers**: push and pull_request to `v2_release`, `v2_develop` (ignores `**.md`); supports `workflow_call`
+- **Runner/timeout**: `ubuntu-latest`, 10 minutes
+- **Steps**:
+- Checkout and setup .NET 8.x GA
+- `dotnet restore`
+- Build Debug: `dotnet build --configuration Debug --no-restore -property:NoWarn=0618%3B0612`
+- Build Release (library): `dotnet build Terminal.Gui/Terminal.Gui.csproj --configuration Release --no-incremental --force -property:NoWarn=0618%3B0612`
+- Pack Release: `dotnet pack Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ./local_packages -property:NoWarn=0618%3B0612`
+- Restore NativeAot/SelfContained examples, then restore solution again
+- Build Release for `Examples/NativeAot` and `Examples/SelfContained`
+- Build Release solution
+- Upload artifacts named `build-artifacts`, retention 1 day
+
+### 2) Build & Run Unit Tests (`.github/workflows/unit-tests.yml`)
+
+- **Triggers**: push and pull_request to `v2_release`, `v2_develop` (ignores `**.md`)
+- **Matrix**: Ubuntu/Windows/macOS
+- **Timeout**: 15 minutes per job
+- **Process**:
+1. Calls build workflow to build solution once
+2. Downloads build artifacts
+3. Runs `dotnet restore` (required for `--no-build` to work)
+4. **Performance optimizations**:
+   - Disables Windows Defender on Windows runners (significant speedup)
+   - Collects code coverage **only on Linux** (ubuntu-latest) for performance
+   - Windows and macOS skip coverage collection to reduce test time
+   - Increased blame-hang-timeout to 120s for Windows/macOS (60s for Linux)
+5. Runs two test jobs:
+   - **Non-parallel UnitTests**: `Tests/UnitTests` with blame/diag flags; `xunit.stopOnFail=false`
+   - **Parallel UnitTestsParallelizable**: `Tests/UnitTestsParallelizable` with blame/diag flags; `xunit.stopOnFail=false`
+6. Uploads test logs and diagnostic data from all runners
+7. **Uploads code coverage to Codecov only from Linux runner**
+
+**Test results**: All tests output to unified `TestResults/` directory at repository root
+
+### 3) Build & Run Integration Tests (`.github/workflows/integration-tests.yml`)
+
+- **Triggers**: push and pull_request to `v2_release`, `v2_develop` (ignores `**.md`)
+- **Matrix**: Ubuntu/Windows/macOS
+- **Timeout**: 15 minutes
+- **Process**:
+1. Calls build workflow
+2. Downloads build artifacts
+3. Runs `dotnet restore`
+4. **Performance optimizations** (same as unit tests):
+   - Disables Windows Defender on Windows runners
+   - Collects code coverage **only on Linux**
+   - Increased blame-hang-timeout to 120s for Windows/macOS
+5. Runs IntegrationTests with blame/diag flags; `xunit.stopOnFail=true`
+6. Uploads logs per-OS
+7. **Uploads coverage to Codecov only from Linux runner**
+
+### 4) Publish to NuGet (`.github/workflows/publish.yml`)
+
+- **Triggers**: push to `v2_release`, `v2_develop`, and tags `v*`(ignores `**.md`)
+- Uses GitVersion to compute SemVer, builds Release, packs with symbols, and pushes to NuGet.org using `NUGET_API_KEY`
+
+### 5) Build and publish API docs (`.github/workflows/api-docs.yml`)
+
+- **Triggers**: push to `v1_release` and `v2_develop`
+- Builds DocFX site on Windows and deploys to GitHub Pages when `ref_name` is `v2_release` or `v2_develop`
+
+
+### Replicating CI Locally
+
+```bash
+# Full CI sequence:
+dotnet restore
+dotnet build --configuration Debug --no-restore
+dotnet test Tests/UnitTests --no-build --verbosity normal
+dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal
+dotnet build --configuration Release --no-restore
+```
 
-## Public API Tenets & Guidelines
+---
 
-**Terminal.Gui** provides an API that is used by many. As the project evolves, contributors should follow these [tenets](https://ceklog.kindel.com/2020/02/10/tenets/) to ensure Consistency and backward compatibility.
+## Repository Structure
 
-*NOTE: Like all tenets, these are up for debate. If you disagree, have questions, or suggestions about these tenets and guidelines submit an Issue using the [design](https://github.com/gui-cs/Terminal.Gui/issues?q=is%3Aopen+is%3Aissue+label%3Adesign) tag.*
+### Root Directory Files
 
-1. **Stand on the shoulders of giants.** Follow the [Microsoft .NET Framework Design Guidelines](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/) where appropriate. 
-2. **Don't Break Existing Stuff.** Avoid breaking changes to user behavior or the public API; instead, figure out how to implement new functionality in a similar way. If a breaking change can't be avoided, follow the guidelines below.
-3. **Fail-fast.** Fail-fast makes bugs and failures appear sooner, leading to a higher-quality framework and API.
-4. **Standards Reduce Complexity**. We strive to adopt standard API idoms because doing so reduces complexity for users of the API. For example, see Tenet #1 above. A counterexample is [Issue #447](https://github.com/gui-cs/Terminal.Gui/issues/447).
+- `Terminal.sln` - Main solution file
+- `Terminal.sln.DotSettings` - ReSharper code style settings
+- `.editorconfig` - Code formatting rules (111KB, extensive)
+- `global.json` - .NET SDK version pinning
+- `Directory.Build.props` - Common MSBuild properties
+- `Directory.Packages.props` - Central package version management
+- `GitVersion.yml` - Version numbering configuration
+- `CONTRIBUTING.md` - This file - contribution guidelines (source of truth)
+- `AGENTS.md` - Pointer to this file for AI agents
+- `README.md` - Project documentation
 
-### Include API Documentation
+### Main Directories
 
-Great care has been provided thus far in ensuring **Terminal.Gui** has great [API Documentation](https://gui-cs.github.io/Terminal.Gui). Contributors have the responsibility of continuously improving the API Documentation.
+**`/Terminal.Gui/`** - Core library (496 C# files):
+- `App/` - Application lifecycle (`Application.cs` static class, `SessionToken`, `MainLoop`)
+- `Configuration/` - `ConfigurationManager` for settings
+- `Drivers/` - Console driver implementations (`Dotnet`, `Windows`, `Unix`, `Fake`)
+- `Drawing/` - Rendering system (attributes, colors, glyphs)
+- `Input/` - Keyboard and mouse input handling
+- `ViewBase/` - Core `View` class hierarchy and layout
+- `Views/` - Specific View subclasses (Window, Dialog, Button, ListView, etc.)
+- `Text/` - Text manipulation and formatting
+- `FileServices/` - File operations and services
 
-- All public APIs must have clear, concise, and complete documentation in the form of [XML Documentation](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/).
-- Keep the `<summary></summary>` terse.
-- Use `<see cref=""/>` liberally to cross-link topics.
-- Use `<remarks></remarks>` to add more context and explanation.
-- For complex topics, provide conceptual documentation in the `docfx/articles` folder as a `.md` file. It will automatically get picked up and be added to [Conceptual Documentation](https://gui-cs.github.io/Terminal.Gui/docs/index.html).
-- Use proper English and good grammar.
+**`/Tests/`**:
+- `UnitTests/` - Non-parallel tests (use `Application.Init`, static state)
+- `UnitTestsParallelizable/` - Parallel tests (no static dependencies) - **Preferred**
+- `IntegrationTests/` - Integration tests
+- `StressTests/` - Long-running stress tests (scheduled daily)
+- `coverlet.runsettings` - Code coverage configuration
 
-### Defining Events
+**`/Examples/`**:
+- `UICatalog/` - Comprehensive demo app for manual testing
+- `Example/` - Basic example
+- `NativeAot/`, `SelfContained/` - Deployment examples
+- `ReactiveExample/`, `CommunityToolkitExample/` - Integration examples
 
-See https://gui-cs.github.io/Terminal.Gui/docs/events.html
+**`/docfx/`** - Documentation source:
+- `docs/` - Conceptual documentation (deep dives)
+- `api/` - Generated API docs (gitignored)
+- `docfx.json` - DocFX configuration
 
+**`/Scripts/`** - PowerShell build utilities (requires PowerShell 7.4+)
 
-### Defining new `View` classes
+**`/.github/workflows/`** - CI/CD pipelines (see [CI/CD Workflows](#cicd-workflows))
 
-- Support parameterless constructors (see [Issue 102](Parameterless constructors #102)). Do not require callers to use a parameterized constructor except when forcing `Absolute Layout`).
-- Avoid doing initialization via constructors. Instead use a property so consumers can use object initialization (e.g. `var foo = new Foo() { a = b };`).
-- Ensure the `UICatalog` demo for the new class illustrates both `Absolutle Layout` and `Computed Layout`.
+---
 
-## Breaking Changes to User Behavior or the Public API
+## Branching Model
 
-- Tag all pull requests that cause breaking changes to user behavior or the public API with the [breaking-change](https://github.com/gui-cs/Terminal.Gui/issues?q=is%3Aopen+is%3Aissue+label%3Abreaking-change) tag. This will help project maintainers track and document these.
-- Add a `<remark></remark>` to the XML Documentation to the code describing the breaking change. These will get picked up in the [API Documentation](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.html).
+### GitFlow Model
 
-## Unit Tests
+- `v2_develop` - Default branch, active development
+- `v2_release` - Stable releases, matches NuGet
+- `v1_develop`, `v1_release` - Legacy v1 (maintenance only)
 
-PRs should never cause code coverage to go down. Ideally, every PR will get the project closer to 100%. PRs that include new functionality (e.g. a new control) should have at least 70% code coverage for the new functionality. 
+---
 
-**Terminal.Gui** has an automated unit or regression test suite. See the [Testing wiki](https://github.com/gui-cs/Terminal.Gui/wiki/Testing).
+## Key Architecture Concepts
 
-We analyze unit tests and code coverage on each PR push. 
+**⚠️ CRITICAL - Contributors should understand these concepts before starting work.**
 
-The code coverage of the latest released build (on NuGet) is shown as a badge at the top of `README.md`. Here as well:
+See `/docfx/docs/` for deep dives on:
 
-![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27/raw/code-coverage.json)
+- **Application Lifecycle** - How `Application.Init`, `Application.Run`, and `Application.Shutdown` work
+- **View Hierarchy** - Understanding `View`, `Toplevel`, `Window`, and view containment
+- **Layout System** - Pos, Dim, and automatic layout
+- **Event System** - How keyboard, mouse, and application events flow
+- **Driver Architecture** - How console drivers abstract platform differences
+- **Drawing Model** - How rendering works with Attributes, Colors, and Glyphs
 
-The project uses Fine Code Coverage to allow easy access to code coverage info on a per-component basis.
+Key documentation:
+- [View Documentation](https://gui-cs.github.io/Terminal.Gui/docs/View.html)
+- [Events Deep Dive](https://gui-cs.github.io/Terminal.Gui/docs/events.html)
+- [Layout System](https://gui-cs.github.io/Terminal.Gui/docs/layout.html)
+- [Keyboard Handling](https://gui-cs.github.io/Terminal.Gui/docs/keyboard.html)
+- [Mouse Support](https://gui-cs.github.io/Terminal.Gui/docs/mouse.html)
+- [Drivers](https://gui-cs.github.io/Terminal.Gui/docs/drivers.html)
 
-Use the following command to generate the same CC info that the Publish Github Action uses to publish the results to the badge:
+---
 
-```
-dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage"  --settings UnitTests/coverlet.runsettings
-```
+## What NOT to Do
 
-Then open up the resulting `coverage.opencover.xml` file and you'll see the `sequenceCoverage` value:
+- ❌ Don't add new linters/formatters (use existing)
+- ❌ Don't modify unrelated code
+- ❌ Don't remove/edit unrelated tests
+- ❌ Don't break existing functionality
+- ❌ Don't add tests to `UnitTests` if they can be parallelizable
+- ❌ Don't use `Application.Init` in new tests
+- ❌ Don't decrease code coverage
+- ❌ **Don't use `var` for anything but built-in simple types** (use explicit types)
+- ❌ **Don't use redundant type names with `new`** (**ALWAYS PREFER** target-typed `new ()`)
+- ❌ **Don't introduce new warnings** (fix warnings in files you modify; exception: `[Obsolete]` warnings)
 
-```xml
-<?xml version="1.0" encoding="utf-8"?>
-<CoverageSession>
-  <Summary numSequencePoints="15817" visitedSequencePoints="7249" numBranchPoints="9379" visitedBranchPoints="3640" sequenceCoverage="45.83" branchCoverage="38.81" maxCyclomaticComplexity="10276" minCyclomaticComplexity="10276" visitedClasses="105" numClasses="141" visitedMethods="965" numMethods="1751" />
- 
-```
+---
 
-## Sample Code
+## Additional Resources
 
-[UI Catalog](https://github.com/gui-cs/Terminal.Gui/tree/master/UICatalog) is a great sample app for manual testing.
+- **Full Documentation**: https://gui-cs.github.io/Terminal.Gui
+- **API Reference**: https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.App.html
+- **Deep Dives**: `/docfx/docs/` directory
+- **Getting Started**: https://gui-cs.github.io/Terminal.Gui/docs/getting-started.html
+- **Migrating from v1 to v2**: https://gui-cs.github.io/Terminal.Gui/docs/migratingfromv1.html
+- **Showcase**: https://gui-cs.github.io/Terminal.Gui/docs/showcase.html
+
+---
 
-When adding new functionality, fixing bugs, or changing things, please either add a new `Scenario` to **UICatalog** or update an existing `Scenario` to fully illustrate your work and provide a test-case.
+**Thank you for contributing to Terminal.Gui!** 🎉

+ 4 - 4
Directory.Packages.props

@@ -11,14 +11,14 @@
     <PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0" />
     <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[8,9)" />
     <PackageVersion Include="ColorHelper" Version="[1.8.1,2)" />
-    <PackageVersion Include="JetBrains.Annotations" Version="[2024.3.0,)" />
+    <PackageVersion Include="JetBrains.Annotations" Version="[2025.2.2,)" />
     <PackageVersion Include="Microsoft.CodeAnalysis" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
-    <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[9.0.2,10)" />
+    <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[9.0.0,10)" />
     <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" />
-    <PackageVersion Include="System.IO.Abstractions" Version="[22.0.11,23)" />
-    <PackageVersion Include="Wcwidth" Version="[2,3)" />
+    <PackageVersion Include="System.IO.Abstractions" Version="[22.0.16,23)" />
+    <PackageVersion Include="Wcwidth" Version="[3.0.0,)" />
     <PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21.2,2)" />
     <PackageVersion Include="Serilog" Version="4.2.0" />
     <PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />

+ 20 - 1
Examples/Example/Example.cs

@@ -11,9 +11,11 @@ using Terminal.Gui.Views;
 using Attribute = Terminal.Gui.Drawing.Attribute;
 
 // Override the default configuration for the application to use the Light theme
-ConfigurationManager.RuntimeConfig = """{ "Theme": "Light" }""";
+//ConfigurationManager.RuntimeConfig = """{ "Theme": "Light" }""";
 ConfigurationManager.Enable(ConfigLocations.All);
 
+
+
 Application.Run<ExampleWindow> ().Dispose ();
 
 // Before the application exits, reset Terminal.Gui for clean shutdown
@@ -89,5 +91,22 @@ public class ExampleWindow : Window
 
         // Add the views to the Window
         Add (usernameLabel, userNameText, passwordLabel, passwordText, btnLogin);
+
+        ListView lv = new ListView ()
+        {
+            Y = Pos.AnchorEnd(),
+            Height= Dim.Auto(),
+            Width = Dim.Auto()
+        };
+        lv.SetSource (["One", "Two", "Three", "Four"]);
+        Add (lv);
+    }
+
+    public override void EndInit ()
+    {
+        base.EndInit ();
+        // Set the theme to "Anders" if it exists, otherwise use "Default"
+        ThemeManager.Theme = ThemeManager.GetThemeNames ().FirstOrDefault (x => x == "Anders") ?? "Default";
     }
 }
+ 

+ 2 - 2
Examples/NativeAot/Program.cs

@@ -12,8 +12,8 @@ namespace NativeAot;
 
 public static class Program
 {
-    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(IConsoleDriver, String)")]
-    [RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(IConsoleDriver, String)")]
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(IDriver, String)")]
+    [RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(IDriver, String)")]
     private static void Main (string [] args)
     {
         ConfigurationManager.Enable(ConfigLocations.All);

+ 1 - 1
Examples/SelfContained/Program.cs

@@ -12,7 +12,7 @@ namespace SelfContained;
 
 public static class Program
 {
-    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Run<T>(Func<Exception, Boolean>, IConsoleDriver)")]
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Run<T>(Func<Exception, Boolean>, IDriver)")]
     private static void Main (string [] args)
     {
         ConfigurationManager.Enable (ConfigLocations.All);

+ 22 - 68
Examples/UICatalog/Properties/launchSettings.json

@@ -4,25 +4,13 @@
       "commandName": "Project",
       "commandLineArgs": "--debug-log-level Debug"
     },
-    "UICatalog --driver NetDriver": {
+    "UICatalog --driver windows": {
       "commandName": "Project",
-      "commandLineArgs": "--driver NetDriver"
+      "commandLineArgs": "--driver windows -dl Trace"
     },
-    "UICatalog --driver WindowsDriver": {
+    "UICatalog --driver dotnet": {
       "commandName": "Project",
-      "commandLineArgs": "--driver WindowsDriver"
-    },
-    "UICatalog --driver v2": {
-      "commandName": "Project",
-      "commandLineArgs": "--driver v2 -dl Trace"
-    },
-    "UICatalog --driver v2win": {
-      "commandName": "Project",
-      "commandLineArgs": "--driver v2win -dl Trace"
-    },
-    "UICatalog --driver v2net": {
-      "commandName": "Project",
-      "commandLineArgs": "--driver v2net -dl Trace"
+      "commandLineArgs": "--driver dotnet -dl Trace"
     },
     "WSL: UICatalog": {
       "commandName": "Executable",
@@ -30,28 +18,16 @@
       "commandLineArgs": "dotnet UICatalog.dll",
       "distributionName": ""
     },
-    "WSL: UICatalog --driver NetDriver": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver NetDriver",
-      "distributionName": ""
-    },
-    "WSL: UICatalog --driver v2": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2",
-      "distributionName": ""
-    },
-    "WSL: UICatalog --driver v2unix": {
+    "WSL: UICatalog --driver dotnet": {
       "commandName": "Executable",
       "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2unix",
+      "commandLineArgs": "dotnet UICatalog.dll --driver dotnet",
       "distributionName": ""
     },
-    "WSL: UICatalog --driver v2net": {
+    "WSL: UICatalog --driver unix": {
       "commandName": "Executable",
       "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2net",
+      "commandLineArgs": "dotnet UICatalog.dll --driver unix",
       "distributionName": ""
     },
     "WSL-Gnome: UICatalog": {
@@ -60,45 +36,29 @@
       "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll; exec bash\"'",
       "distributionName": ""
     },
-    "WSL-Gnome: UICatalog --driver NetDriver": {
+    "WSL-Gnome: UICatalog --driver dotnet": {
       "commandName": "Executable",
       "executablePath": "wsl",
-      "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver NetDriver; exec bash\"'",
+      "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver dotnet; exec bash\"'",
       "distributionName": ""
     },
-    "WSL-Gnome: UICatalog --driver v2": {
+    "WSL-Gnome: UICatalog --driver unix": {
       "commandName": "Executable",
       "executablePath": "wsl",
-      "commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2; exec bash\"'",
-      "distributionName": ""
-    },
-    "WSL-Gnome: UICatalog --driver v2unix": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2unix; exec bash\"'",
-      "distributionName": ""
-    },
-    "WSL-Gnome: UICatalog --driver v2net": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2net; exec bash\"'",
+      "commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver unix; exec bash\"'",
       "distributionName": ""
     },
     "Benchmark All": {
       "commandName": "Project",
       "commandLineArgs": "--benchmark"
     },
-    "Benchmark All --driver NetDriver": {
-      "commandName": "Project",
-      "commandLineArgs": "--driver NetDriver --benchmark"
-    },
-    "Benchmark All --driver v2win": {
+    "Benchmark All --driver dotnet": {
       "commandName": "Project",
-      "commandLineArgs": "--driver v2win --benchmark"
+      "commandLineArgs": "--driver dotnet --benchmark"
     },
-    "Benchmark All --driver v2net": {
+    "Benchmark All --driver windows": {
       "commandName": "Project",
-      "commandLineArgs": "--driver v2net --benchmark"
+      "commandLineArgs": "--driver windows --benchmark"
     },
     "WSL: Benchmark All": {
       "commandName": "Executable",
@@ -106,22 +66,16 @@
       "commandLineArgs": "dotnet UICatalog.dll --benchmark",
       "distributionName": ""
     },
-    "WSL: Benchmark All --driver v2": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2 --benchmark",
-      "distributionName": ""
-    },
-    "WSL: Benchmark All --driver v2unix": {
+    "WSL: Benchmark All --driver unix": {
       "commandName": "Executable",
       "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2unix --benchmark",
+      "commandLineArgs": "dotnet UICatalog.dll --driver unix --benchmark",
       "distributionName": ""
     },
-    "WSL: Benchmark All --driver v2net": {
+    "WSL: Benchmark All --driver dotnet": {
       "commandName": "Executable",
       "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2net --benchmark",
+      "commandLineArgs": "dotnet UICatalog.dll --driver dotnet --benchmark",
       "distributionName": ""
     },
     "Docker": {
@@ -135,9 +89,9 @@
       "commandName": "Project",
       "commandLineArgs": "--disable-cm\r\n"
     },
-    "UICatalog --disable-cm --driver v2win": {
+    "UICatalog --disable-cm --driver windows": {
       "commandName": "Project",
-      "commandLineArgs": "--disable-cm --driver v2win"
+      "commandLineArgs": "--disable-cm --driver windows"
     },
     "Themes": {
       "commandName": "Project",

+ 9 - 16
Examples/UICatalog/Scenario.cs

@@ -189,32 +189,25 @@ public class Scenario : IDisposable
             }
 
             Application.Iteration += OnApplicationOnIteration;
-            Application.Driver!.ClearedContents += (sender, args) => BenchmarkResults.ClearedContentCount++;
 
-            if (Application.Driver is ConsoleDriver cd)
-            {
-                cd.Refreshed += (sender, args) =>
-                                                 {
-                                                     BenchmarkResults.RefreshedCount++;
-                                                     if (args.Value)
-                                                     {
-                                                         BenchmarkResults.UpdatedCount++;
-                                                     }
-                                                 };
-
-            }
-            Application.NotifyNewRunState += OnApplicationNotifyNewRunState;
+            Application.Driver!.ClearedContents += OnClearedContents;
+            Application.SessionBegun += OnApplicationSessionBegun;
 
 
             _stopwatch = Stopwatch.StartNew ();
         }
         else
         {
-            Application.NotifyNewRunState -= OnApplicationNotifyNewRunState;
+            Application.Driver!.ClearedContents -= OnClearedContents;
+            Application.SessionBegun -= OnApplicationSessionBegun;
             Application.Iteration -= OnApplicationOnIteration;
             BenchmarkResults.Duration = _stopwatch!.Elapsed;
             _stopwatch?.Stop ();
         }
+
+        return;
+
+        void OnClearedContents (object? sender, EventArgs args) => BenchmarkResults.ClearedContentCount++;
     }
 
     private void OnApplicationOnIteration (object? s, IterationEventArgs a)
@@ -226,7 +219,7 @@ public class Scenario : IDisposable
         }
     }
 
-    private void OnApplicationNotifyNewRunState (object? sender, RunStateEventArgs e)
+    private void OnApplicationSessionBegun (object? sender, SessionTokenEventArgs e)
     {
         SubscribeAllSubViews (Application.Top!);
 

+ 12 - 12
Examples/UICatalog/Scenarios/Adornments.cs

@@ -26,7 +26,7 @@ public class Adornments : Scenario
             X = Pos.AnchorEnd ()
         };
 
-        editor.Border.Thickness = new (1, 2, 1, 1);
+        editor.Border!.Thickness = new (1, 2, 1, 1);
 
         app.Add (editor);
 
@@ -71,7 +71,7 @@ public class Adornments : Scenario
             Width = 40,
             Height = 6 // TODO: Use Dim.Auto
         };
-        label.Border.Thickness = new (1, 3, 1, 1);
+        label.Border!.Thickness = new (1, 3, 1, 1);
 
         var btnButtonInWindow = new Button { X = Pos.AnchorEnd (), Y = Pos.AnchorEnd (), Text = "Button" };
 
@@ -84,13 +84,13 @@ public class Adornments : Scenario
             SchemeName = "Dialog"
         };
 
-        window.Margin.Data = "Margin";
-        window.Margin.Text = "Margin Text";
-        window.Margin.Thickness = new (0);
+        window.Margin!.Data = "Margin";
+        window.Margin!.Text = "Margin Text";
+        window.Margin!.Thickness = new (0);
 
-        window.Border.Data = "Border";
-        window.Border.Text = "Border Text";
-        window.Border.Thickness = new (0);
+        window.Border!.Data = "Border";
+        window.Border!.Text = "Border Text";
+        window.Border!.Thickness = new (0);
 
         window.Padding.Data = "Padding";
         window.Padding.Text = "Padding Text line 1\nPadding Text line 3\nPadding Text line 3\nPadding Text line 4\nPadding Text line 5";
@@ -134,14 +134,14 @@ public class Adornments : Scenario
                                   };
                                   btnButtonInPadding.Accepting += (s, e) => MessageBox.Query (20, 7, "Hi", "Button in Padding Pressed!", "Ok");
                                   btnButtonInPadding.BorderStyle = LineStyle.Dashed;
-                                  btnButtonInPadding.Border.Thickness = new (1, 1, 1, 1);
+                                  btnButtonInPadding.Border!.Thickness = new (1, 1, 1, 1);
                                   window.Padding.Add (btnButtonInPadding);
 
 #if SUBVIEW_BASED_BORDER
-                                btnButtonInPadding.Border.CloseButton.Visible = true;
+                                btnButtonInPadding.Border!.CloseButton.Visible = true;
 
-                                view.Border.CloseButton.Visible = true;
-                                view.Border.CloseButton.Accept += (s, e) =>
+                                view.Border!.CloseButton.Visible = true;
+                                view.Border!.CloseButton.Accept += (s, e) =>
                                                                   {
                                                                       MessageBox.Query (20, 7, "Hi", "Window Close Button Pressed!", "Ok");
                                                                       e.Handled = true;

+ 6 - 10
Examples/UICatalog/Scenarios/AllViewsTester.cs

@@ -38,7 +38,7 @@ public class AllViewsTester : Scenario
 
         // Set the BorderStyle we use for all subviews, but disable the app border thickness
         app.Border!.LineStyle = LineStyle.Heavy;
-        app.Border.Thickness = new (0);
+        app.Border!.Thickness = new (0);
 
 
         _viewClasses = GetAllViewClassesCollection ()
@@ -158,16 +158,13 @@ public class AllViewsTester : Scenario
 
         _eventLog = new ()
         {
-            // X = Pos.Right(_layoutEditor),
+            X = Pos.AnchorEnd () - 1,
+            Y = 0,
+            Width = 30,
+            Height = Dim.Fill (),
             SuperViewRendersLineCanvas = true
         };
         _eventLog.Border!.Thickness = new (1);
-        _eventLog.X = Pos.AnchorEnd () - 1;
-        _eventLog.Y = 0;
-
-        _eventLog.Height = Dim.Height (_classListView);
-
-        //_eventLog.Width = 30;
 
         _layoutEditor.Width = Dim.Fill (
                                         Dim.Func (
@@ -194,7 +191,6 @@ public class AllViewsTester : Scenario
             Height = Dim.Fill (),
             CanFocus = true,
             TabStop = TabBehavior.TabStop,
-            //SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base),
             Arrangement = ViewArrangement.LeftResizable | ViewArrangement.BottomResizable | ViewArrangement.RightResizable,
             BorderStyle = LineStyle.Double,
             SuperViewRendersLineCanvas = true
@@ -228,7 +224,7 @@ public class AllViewsTester : Scenario
         if (type.IsGenericType)
         {
             // For each of the <T> arguments
-            List<Type> typeArguments = new ();
+            List<Type> typeArguments = [];
 
             // use <object> or the original type if applicable
             foreach (Type arg in type.GetGenericArguments ())

+ 4 - 1
Examples/UICatalog/Scenarios/AnimationScenario/AnimationScenario.cs

@@ -40,7 +40,10 @@ public class AnimationScenario : Scenario
 
         var lbl2 = new Label
         {
-            X = Pos.AnchorEnd (), Y = Pos.AnchorEnd (), Text = "https://commons.wikimedia.org/wiki/File:Spinning_globe.gif"
+            // This ensures the URL that has an underscore is drawn correctly
+            HotKeySpecifier = new Rune ('\xFFFF'),
+            X = Pos.AnchorEnd (), Y = Pos.AnchorEnd (), 
+            Text = "https://commons.wikimedia.org/wiki/File:Spinning_globe.gif"
         };
         win.Add (lbl2);
 

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

@@ -112,7 +112,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
 
                                                       break;
                                                   case "CSI_ReportTerminalSizeInChars":
-                                                      selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_ReportTerminalSizeInChars;
+                                                      selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_ReportWindowSizeInChars;
 
                                                       break;
                                                   case "CSI_RequestCursorPositionReport":

+ 0 - 1
Examples/UICatalog/Scenarios/Arrangement.cs

@@ -99,7 +99,6 @@ public class Arrangement : Scenario
 
                              progressBar.Fraction += 0.01f;
 
-                             Application.Wakeup ();
 
                              progressBar.SetNeedsDraw ();
                          };

+ 3 - 3
Examples/UICatalog/Scenarios/Bars.cs

@@ -40,7 +40,7 @@ public class Bars : Scenario
             SchemeName = "Toplevel",
             Source = new ListWrapper<string> (eventSource)
         };
-        eventLog.Border.Thickness = new (0, 1, 0, 0);
+        eventLog.Border!.Thickness = new (0, 1, 0, 0);
         Application.Top.Add (eventLog);
 
         FrameView menuBarLikeExamples = new ()
@@ -185,9 +185,9 @@ public class Bars : Scenario
 
         menuLikeExamples.Add (popOverMenu);
 
-        menuLikeExamples.MouseClick += MenuLikeExamplesMouseClick;
+        menuLikeExamples.MouseEvent += MenuLikeExamplesMouseEvent;
 
-        void MenuLikeExamplesMouseClick (object sender, MouseEventArgs e)
+        void MenuLikeExamplesMouseEvent (object _, MouseEventArgs e)
         {
             if (e.Flags.HasFlag (MouseFlags.Button3Clicked))
             {

+ 22 - 49
Examples/UICatalog/Scenarios/Buttons.cs

@@ -243,17 +243,17 @@ public class Buttons : Scenario
         };
         main.Add (label);
 
-        var radioGroup = new RadioGroup
+        OptionSelector<Alignment> osAlignment = new ()
         {
             X = 4,
             Y = Pos.Bottom (label) + 1,
-            SelectedItem = 2,
-            RadioLabels = new [] { "_Start", "_End", "_Center", "_Fill" },
-            Title = "_9 RadioGroup",
+            Value = Alignment.Center,
+            AssignHotKeys = true,
+            Title = "_9 OptionSelector",
             BorderStyle = LineStyle.Dotted,
             // CanFocus = false
         };
-        main.Add (radioGroup);
+        main.Add (osAlignment);
 
         // Demo changing hotkey
         string MoveHotkey (string txt)
@@ -292,7 +292,7 @@ public class Buttons : Scenario
         var moveHotKeyBtn = new Button
         {
             X = 2,
-            Y = Pos.Bottom (radioGroup) + 1,
+            Y = Pos.Bottom (osAlignment) + 1,
             Width = Dim.Width (computedFrame) - 2,
             SchemeName = "TopLevel",
             Text = mhkb
@@ -309,7 +309,7 @@ public class Buttons : Scenario
         var moveUnicodeHotKeyBtn = new Button
         {
             X = Pos.Left (absoluteFrame) + 1,
-            Y = Pos.Bottom (radioGroup) + 1,
+            Y = Pos.Bottom (osAlignment) + 1,
             Width = Dim.Width (absoluteFrame) - 2,
             SchemeName = "TopLevel",
             Text = muhkb
@@ -321,48 +321,21 @@ public class Buttons : Scenario
                                        };
         main.Add (moveUnicodeHotKeyBtn);
 
-        radioGroup.SelectedItemChanged += (s, args) =>
-                                          {
-                                              switch (args.SelectedItem)
-                                              {
-                                                  case 0:
-                                                      moveBtn.TextAlignment = Alignment.Start;
-                                                      sizeBtn.TextAlignment = Alignment.Start;
-                                                      moveBtnA.TextAlignment = Alignment.Start;
-                                                      sizeBtnA.TextAlignment = Alignment.Start;
-                                                      moveHotKeyBtn.TextAlignment = Alignment.Start;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = Alignment.Start;
-
-                                                      break;
-                                                  case 1:
-                                                      moveBtn.TextAlignment = Alignment.End;
-                                                      sizeBtn.TextAlignment = Alignment.End;
-                                                      moveBtnA.TextAlignment = Alignment.End;
-                                                      sizeBtnA.TextAlignment = Alignment.End;
-                                                      moveHotKeyBtn.TextAlignment = Alignment.End;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = Alignment.End;
-
-                                                      break;
-                                                  case 2:
-                                                      moveBtn.TextAlignment = Alignment.Center;
-                                                      sizeBtn.TextAlignment = Alignment.Center;
-                                                      moveBtnA.TextAlignment = Alignment.Center;
-                                                      sizeBtnA.TextAlignment = Alignment.Center;
-                                                      moveHotKeyBtn.TextAlignment = Alignment.Center;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = Alignment.Center;
-
-                                                      break;
-                                                  case 3:
-                                                      moveBtn.TextAlignment = Alignment.Fill;
-                                                      sizeBtn.TextAlignment = Alignment.Fill;
-                                                      moveBtnA.TextAlignment = Alignment.Fill;
-                                                      sizeBtnA.TextAlignment = Alignment.Fill;
-                                                      moveHotKeyBtn.TextAlignment = Alignment.Fill;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = Alignment.Fill;
-
-                                                      break;
-                                              }
-                                          };
+        osAlignment.ValueChanged += (s, args) =>
+                                    {
+                                        if (args.Value is null)
+                                        {
+                                            return;
+                                        }
+
+                                        Alignment newValue = args.Value.Value;
+                                        moveBtn.TextAlignment = newValue;
+                                        sizeBtn.TextAlignment = newValue;
+                                        moveBtnA.TextAlignment = newValue;
+                                        sizeBtnA.TextAlignment = newValue;
+                                        moveHotKeyBtn.TextAlignment = newValue;
+                                        moveUnicodeHotKeyBtn.TextAlignment = newValue;
+                                    };
 
         label = new ()
         {

+ 7 - 31
Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs

@@ -85,7 +85,7 @@ public class CharacterMap : Scenario
             X = Pos.Right (jumpLabel) + 1,
             Y = Pos.Y (_charMap),
             Width = 17,
-            Caption = "e.g. 01BE3 or ✈"
+            Title = "e.g. 01BE3 or ✈"
 
             //SchemeName = "Dialog"
         };
@@ -365,40 +365,16 @@ public class CharacterMap : Scenario
         options [0] = "All";
         Array.Copy (allCategoryNames, 0, options, 1, allCategoryNames.Length);
 
-        // TODO: When #4126 is merged update this to use OptionSelector<UnicodeCategory?>
-        var selector = new OptionSelector
-        {
-            AssignHotKeysToCheckBoxes = true,
-            Options = options
-        };
+        // TODO: Add a "None" option
+        OptionSelector<UnicodeCategory> selector = new ();
 
         _unicodeCategorySelector = selector;
 
-        // Default to "All"
-        selector.SelectedItem = 0;
+        selector.Value = null;
         _charMap!.ShowUnicodeCategory = null;
 
-        selector.SelectedItemChanged += (s, e) =>
-                                        {
-                                            int? idx = selector.SelectedItem;
-
-                                            if (idx is null)
-                                            {
-                                                return;
-                                            }
-
-                                            if (idx.Value == 0)
-                                            {
-                                                _charMap.ShowUnicodeCategory = null;
-                                            }
-                                            else
-                                            {
-                                                // Map index to UnicodeCategory (offset by 1 because 0 is "All")
-                                                UnicodeCategory cat = Enum.GetValues<UnicodeCategory> () [idx.Value - 1];
-                                                _charMap.ShowUnicodeCategory = cat;
-                                            }
-                                        };
-
-        return new() { CommandView = selector };
+        selector.ValueChanged += (_, e) => _charMap.ShowUnicodeCategory = e.Value;
+
+        return new () { CommandView = selector };
     }
 }

+ 1 - 2
Examples/UICatalog/Scenarios/Clipping.cs

@@ -100,7 +100,6 @@ public class Clipping : Scenario
                                  {
                                      tiledProgressBar1.Pulse ();
                                      tiledProgressBar2.Pulse ();
-                                     Application.Wakeup ();
                                  };
 
         progressTimer.Start ();
@@ -152,7 +151,7 @@ public class Clipping : Scenario
         //tiled.Padding.Thickness = new (1);
         //tiled.Padding.Diagnostics =  ViewDiagnosticFlags.Thickness;
 
-        //tiled.Margin.Thickness = new (1);
+        //tiled.Margin!.Thickness = new (1);
 
         FrameView fv = new ()
         {

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

@@ -129,7 +129,7 @@ public class CollectionNavigatorTester : Scenario
         _items = new (_items.OrderBy (i => i, StringComparer.OrdinalIgnoreCase));
 
         CreateListView ();
-        var vsep = new LineView (Orientation.Vertical) { X = Pos.Right (_listView), Y = 1, Height = Dim.Fill () };
+        var vsep = new Line { Orientation = Orientation.Vertical, X = Pos.Right (_listView), Y = 1, Height = Dim.Fill () };
         top.Add (vsep);
         CreateTreeView ();
 

+ 19 - 15
Examples/UICatalog/Scenarios/ColorPicker.cs

@@ -125,26 +125,25 @@ public class ColorPickers : Scenario
         app.Add (_demoView);
 
 
-        // Radio for switching color models
-        var rgColorModel = new RadioGroup ()
+        var osColorModel = new OptionSelector ()
         {
             Y = Pos.Bottom (_demoView),
             Width = Dim.Auto (),
             Height = Dim.Auto (),
-            RadioLabels = new []
-            {
+            Labels =
+            [
                 "_RGB",
                 "_HSV",
                 "H_SL",
                 "_16 Colors"
-            },
-            SelectedItem = (int)foregroundColorPicker.Style.ColorModel,
+            ],
+            Value = (int)foregroundColorPicker.Style.ColorModel,
         };
 
-        rgColorModel.SelectedItemChanged += (_, e) =>
+        osColorModel.ValueChanged += (_, e) =>
                                             {
                                                 // 16 colors
-                                                if (e.SelectedItem == 3)
+                                                if (e.Value == 3)
                                                 {
 
                                                     foregroundColorPicker16.Visible = true;
@@ -161,12 +160,17 @@ public class ColorPickers : Scenario
                                                 {
                                                     foregroundColorPicker16.Visible = false;
                                                     foregroundColorPicker.Visible = true;
-                                                    foregroundColorPicker.Style.ColorModel = (ColorModel)e.SelectedItem;
-                                                    foregroundColorPicker.ApplyStyleChanges ();
 
-                                                    backgroundColorPicker16.Visible = false;
-                                                    backgroundColorPicker.Visible = true;
-                                                    backgroundColorPicker.Style.ColorModel = (ColorModel)e.SelectedItem;
+                                                    if (e.Value is { })
+                                                    {
+                                                        foregroundColorPicker.Style.ColorModel = (ColorModel)e.Value;
+                                                        foregroundColorPicker.ApplyStyleChanges ();
+
+                                                        backgroundColorPicker16.Visible = false;
+                                                        backgroundColorPicker.Visible = true;
+                                                        backgroundColorPicker.Style.ColorModel = (ColorModel)e.Value;
+                                                    }
+
                                                     backgroundColorPicker.ApplyStyleChanges ();
 
 
@@ -176,13 +180,13 @@ public class ColorPickers : Scenario
                                                 }
                                             };
 
-        app.Add (rgColorModel);
+        app.Add (osColorModel);
 
         // Checkbox for switching show text fields on and off
         var cbShowTextFields = new CheckBox ()
         {
             Text = "Show _Text Fields",
-            Y = Pos.Bottom (rgColorModel) + 1,
+            Y = Pos.Bottom (osColorModel) + 1,
             Width = Dim.Auto (),
             Height = Dim.Auto (),
             CheckedState = foregroundColorPicker.Style.ShowTextFields ? CheckState.Checked : CheckState.UnChecked,

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

@@ -13,7 +13,7 @@ public class CombiningMarks : Scenario
         top.DrawComplete += (s, e) =>
         {
             // Forces reset _lineColsOffset because we're dealing with direct draw
-            Application.ClearScreenNextIteration = true;
+            Application.Top!.SetNeedsDraw ();
 
             var i = -1;
             top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");

+ 7 - 10
Examples/UICatalog/Scenarios/Dialogs.cs

@@ -151,18 +151,15 @@ public class Dialogs : Scenario
         };
         frame.Add (label);
 
-        // Add hotkeys
-        var labels = Enum.GetNames<Alignment> ().Select (n => n = "_" + n);
-        var alignmentGroup = new RadioGroup
+        OptionSelector<Alignment> alignmentOptionSelector = new ()
         {
             X = Pos.Right (label) + 1,
             Y = Pos.Top (label),
-            RadioLabels = labels.ToArray (),
             Title = "Ali_gn",
-            BorderStyle = LineStyle.Dashed
+            AssignHotKeys = true
         };
-        frame.Add (alignmentGroup);
-        alignmentGroup.SelectedItem = labels.ToList ().IndexOf ("_" + Dialog.DefaultButtonAlignment.ToString ());
+        frame.Add (alignmentOptionSelector);
+        alignmentOptionSelector.Value = Dialog.DefaultButtonAlignment;
 
         frame.ValidatePosDim = true;
 
@@ -192,7 +189,7 @@ public class Dialogs : Scenario
                                                                       titleEdit,
                                                                       numButtonsEdit,
                                                                       glyphsNotWords,
-                                                                      alignmentGroup,
+                                                                      alignmentOptionSelector,
                                                                       buttonPressedLabel
                                                                      );
                                        Application.Run (dlg);
@@ -216,7 +213,7 @@ public class Dialogs : Scenario
         TextField titleEdit,
         TextField numButtonsEdit,
         CheckBox glyphsNotWords,
-        RadioGroup alignmentRadioGroup,
+        OptionSelector alignmentGroup,
         Label buttonPressedLabel
     )
     {
@@ -269,7 +266,7 @@ public class Dialogs : Scenario
             {
                 Title = titleEdit.Text,
                 Text = "Dialog Text",
-                ButtonAlignment = (Alignment)Enum.Parse (typeof (Alignment), alignmentRadioGroup.RadioLabels [alignmentRadioGroup.SelectedItem].Substring (1)),
+                ButtonAlignment = (Alignment)Enum.Parse (typeof (Alignment), alignmentGroup.Labels! [(int)alignmentGroup.Value!.Value] [1..]),
 
                 Buttons = buttons.ToArray ()
             };

+ 5 - 5
Examples/UICatalog/Scenarios/DimAutoDemo.cs

@@ -56,7 +56,7 @@ public class DimAutoDemo : Scenario
             Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: Dim.Percent (25)),
             Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 10)
         };
-        dimAutoFrameView.Margin.Thickness = new Thickness (1);
+        dimAutoFrameView.Margin!.Thickness = new Thickness (1);
         dimAutoFrameView.ValidatePosDim = true;
 
         var textEdit = new TextView
@@ -163,15 +163,15 @@ public class DimAutoDemo : Scenario
         dimAutoFrameView.Add (resetButton);
 
 
-        var radioGroup = new RadioGroup ()
+        var optionSelector = new OptionSelector ()
         {
-            RadioLabels = ["One", "Two", "Three"],
+            Labels = ["One", "Two", "Three"],
             X = 0,
             Y = Pos.AnchorEnd (),
-            Title = "Radios",
+            Title = "Options",
             BorderStyle = LineStyle.Dotted
         };
-        dimAutoFrameView.Add (radioGroup);
+        dimAutoFrameView.Add (optionSelector);
         return dimAutoFrameView;
     }
 

+ 10 - 10
Examples/UICatalog/Scenarios/DynamicMenuBar.cs

@@ -174,11 +174,11 @@ public class DynamicMenuBar : Scenario
 
             var rChkLabels = new [] { "NoCheck", "Checked", "Radio" };
 
-            RbChkStyle = new ()
+            OsChkStyle = new ()
             {
-                X = Pos.Left (lblTitle), Y = Pos.Bottom (CkbSubMenu) + 1, RadioLabels = rChkLabels
+                X = Pos.Left (lblTitle), Y = Pos.Bottom (CkbSubMenu) + 1, Labels = rChkLabels
             };
-            Add (RbChkStyle);
+            Add (OsChkStyle);
 
             var lblShortcut = new Label
             {
@@ -294,7 +294,7 @@ public class DynamicMenuBar : Scenario
         public CheckBox CkbIsTopLevel { get; }
         public CheckBox CkbNullCheck { get; }
         public CheckBox CkbSubMenu { get; }
-        public RadioGroup RbChkStyle { get; }
+        public OptionSelector OsChkStyle { get; }
         public TextView TextAction { get; }
         public TextField TextHelp { get; }
         public TextField TextHotKey { get; }
@@ -361,7 +361,7 @@ public class DynamicMenuBar : Scenario
             CkbNullCheck.CheckedState = menuItem.AllowNullChecked ? CheckState.Checked : CheckState.UnChecked;
             TextHelp.Enabled = CkbSubMenu.CheckedState == CheckState.UnChecked;
             TextAction.Enabled = CkbSubMenu.CheckedState == CheckState.UnChecked;
-            RbChkStyle.SelectedItem = (int)(menuItem?.CheckType ?? MenuItemCheckStyle.NoCheck);
+            OsChkStyle.Value = (int)(menuItem?.CheckType ?? MenuItemCheckStyle.NoCheck);
             TextShortcutKey.Text = menuItem?.ShortcutTag ?? "";
 
             TextShortcutKey.Enabled = CkbIsTopLevel.CheckedState == CheckState.Checked && CkbSubMenu.CheckedState == CheckState.UnChecked
@@ -434,8 +434,8 @@ public class DynamicMenuBar : Scenario
                     HotKey = TextHotKey.Text,
                     IsTopLevel = CkbIsTopLevel?.CheckedState == CheckState.Checked,
                     HasSubMenu = CkbSubMenu?.CheckedState == CheckState.Checked,
-                    CheckStyle = RbChkStyle.SelectedItem == 0 ? MenuItemCheckStyle.NoCheck :
-                                 RbChkStyle.SelectedItem == 1 ? MenuItemCheckStyle.Checked :
+                    CheckStyle = OsChkStyle.Value == 0 ? MenuItemCheckStyle.NoCheck :
+                                 OsChkStyle.Value == 1 ? MenuItemCheckStyle.Checked :
                                  MenuItemCheckStyle.Radio,
                     ShortcutKey = TextShortcutKey.Text,
                     AllowNullChecked = CkbNullCheck?.CheckedState == CheckState.Checked,
@@ -484,7 +484,7 @@ public class DynamicMenuBar : Scenario
             TextHotKey.Text = "";
             CkbIsTopLevel.CheckedState = CheckState.UnChecked;
             CkbSubMenu.CheckedState = CheckState.UnChecked;
-            RbChkStyle.SelectedItem = (int)MenuItemCheckStyle.NoCheck;
+            OsChkStyle.Value = (int)MenuItemCheckStyle.NoCheck;
             TextShortcutKey.Text = "";
         }
 
@@ -829,9 +829,9 @@ public class DynamicMenuBar : Scenario
                                          HotKey = frmMenuDetails.TextHotKey.Text,
                                          IsTopLevel = frmMenuDetails.CkbIsTopLevel?.CheckedState == CheckState.Checked,
                                          HasSubMenu = frmMenuDetails.CkbSubMenu?.CheckedState == CheckState.Checked,
-                                         CheckStyle = frmMenuDetails.RbChkStyle.SelectedItem == 0
+                                         CheckStyle = frmMenuDetails.OsChkStyle.Value == 0
                                                           ? MenuItemCheckStyle.NoCheck
-                                                          : frmMenuDetails.RbChkStyle.SelectedItem == 1
+                                                          : frmMenuDetails.OsChkStyle.Value == 1
                                                               ? MenuItemCheckStyle.Checked
                                                               : MenuItemCheckStyle.Radio,
                                          ShortcutKey = frmMenuDetails.TextShortcutKey.Text

+ 3 - 3
Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentsEditor.cs

@@ -99,7 +99,7 @@ public class AdornmentsEditor : EditorBase
             SuperViewRendersLineCanvas = true,
             BorderStyle = LineStyle.Single
         };
-        MarginEditor.Border!.Thickness = MarginEditor.Border.Thickness with { Bottom = 0 };
+        MarginEditor.Border!.Thickness = MarginEditor.Border!.Thickness with { Bottom = 0 };
         Add (MarginEditor);
 
         BorderEditor = new ()
@@ -109,7 +109,7 @@ public class AdornmentsEditor : EditorBase
             SuperViewRendersLineCanvas = true,
             BorderStyle = LineStyle.Single
         };
-        BorderEditor.Border!.Thickness = BorderEditor.Border.Thickness with { Bottom = 0 };
+        BorderEditor.Border!.Thickness = BorderEditor.Border!.Thickness with { Bottom = 0 };
         Add (BorderEditor);
 
         PaddingEditor = new ()
@@ -119,7 +119,7 @@ public class AdornmentsEditor : EditorBase
             SuperViewRendersLineCanvas = true,
             BorderStyle = LineStyle.Single
         };
-        PaddingEditor.Border!.Thickness = PaddingEditor.Border.Thickness with { Bottom = 0 };
+        PaddingEditor.Border!.Thickness = PaddingEditor.Border!.Thickness with { Bottom = 0 };
         Add (PaddingEditor);
 
         Width = Dim.Auto (maximumContentDim: Dim.Func (_ => MarginEditor.Frame.Width - 2));

+ 13 - 79
Examples/UICatalog/Scenarios/EditorsAndHelpers/ArrangementEditor.cs

@@ -1,7 +1,4 @@
 #nullable enable
-using System;
-using System.Collections.Generic;
-
 namespace UICatalog.Scenarios;
 
 /// <summary>
@@ -16,92 +13,34 @@ public sealed class ArrangementEditor : EditorBase
 
         Initialized += ArrangementEditor_Initialized;
 
-        _arrangementSlider.Options =
-        [
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.Movable.ToString (),
-                Data = ViewArrangement.Movable
-            },
-
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.LeftResizable.ToString (),
-                Data = ViewArrangement.LeftResizable
-            },
-
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.RightResizable.ToString (),
-                Data = ViewArrangement.RightResizable
-            },
-
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.TopResizable.ToString (),
-                Data = ViewArrangement.TopResizable
-            },
-
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.BottomResizable.ToString (),
-                Data = ViewArrangement.BottomResizable
-            },
-
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.Overlapped.ToString (),
-                Data = ViewArrangement.Overlapped
-            }
-        ];
-
-        Add (_arrangementSlider);
+        Add (_arrangementSelector);
     }
 
-    private readonly Slider<ViewArrangement> _arrangementSlider = new()
+    private readonly FlagSelector<ViewArrangement> _arrangementSelector = new ()
     {
-        Orientation = Orientation.Vertical,
-        UseMinimumSize = true,
-        Type = SliderType.Multiple,
-        AllowEmpty = true,
+        Orientation = Orientation.Vertical
     };
 
     protected override void OnViewToEditChanged ()
     {
-        _arrangementSlider.Enabled = ViewToEdit is not Adornment;
+        _arrangementSelector.Enabled = ViewToEdit is not Adornment;
 
-        _arrangementSlider.OptionsChanged -= ArrangementSliderOnOptionsChanged;
+        _arrangementSelector.ValueChanged -= ArrangementFlagsOnValueChanged;
 
         // Set the appropriate options in the slider based on _viewToEdit.Arrangement
         if (ViewToEdit is { })
         {
-            _arrangementSlider.Options.ForEach (
-                                                option =>
-                                                {
-                                                    _arrangementSlider.ChangeOption (
-                                                                                     _arrangementSlider.Options.IndexOf (option),
-                                                                                     (ViewToEdit.Arrangement & option.Data) == option.Data);
-                                                });
+            _arrangementSelector.Value = ViewToEdit.Arrangement;
         }
 
-        _arrangementSlider.OptionsChanged += ArrangementSliderOnOptionsChanged;
+        _arrangementSelector.ValueChanged += ArrangementFlagsOnValueChanged;
     }
 
-    private void ArrangementEditor_Initialized (object? sender, EventArgs e) { _arrangementSlider.OptionsChanged += ArrangementSliderOnOptionsChanged; }
-
-    private void ArrangementSliderOnOptionsChanged (object? sender, SliderEventArgs<ViewArrangement> e)
+    private void ArrangementFlagsOnValueChanged (object? sender, EventArgs<ViewArrangement?> e)
     {
-        if (ViewToEdit is { })
+        if (ViewToEdit is { } && e.Value is { })
         {
-            // Set the arrangement based on the selected options
-            var arrangement = ViewArrangement.Fixed;
-
-            foreach (KeyValuePair<int, SliderOption<ViewArrangement>> option in e.Options)
-            {
-                arrangement |= option.Value.Data;
-            }
-
-            ViewToEdit.Arrangement = arrangement;
+            ViewToEdit.Arrangement = (ViewArrangement)e.Value;
 
             if (ViewToEdit.Arrangement.HasFlag (ViewArrangement.Overlapped))
             {
@@ -114,14 +53,9 @@ public sealed class ArrangementEditor : EditorBase
                 ViewToEdit.SchemeName = ViewToEdit!.SuperView!.SchemeName;
             }
 
-            if (ViewToEdit.Arrangement.HasFlag (ViewArrangement.Movable))
-            {
-                ViewToEdit.BorderStyle = LineStyle.Double;
-            }
-            else
-            {
-                ViewToEdit.BorderStyle = LineStyle.Single;
-            }
+            ViewToEdit.BorderStyle = ViewToEdit.Arrangement.HasFlag (ViewArrangement.Movable) ? LineStyle.Double : LineStyle.Single;
         }
     }
+
+    private void ArrangementEditor_Initialized (object? sender, EventArgs e) { _arrangementSelector.ValueChanged += ArrangementFlagsOnValueChanged; }
 }

+ 14 - 13
Examples/UICatalog/Scenarios/EditorsAndHelpers/BorderEditor.cs

@@ -8,7 +8,7 @@ namespace UICatalog.Scenarios;
 public class BorderEditor : AdornmentEditor
 {
     private CheckBox? _ckbTitle;
-    private RadioGroup? _rbBorderStyle;
+    private OptionSelector<LineStyle>? _osBorderStyle;
     private CheckBox? _ckbGradient;
 
     public BorderEditor ()
@@ -21,34 +21,31 @@ public class BorderEditor : AdornmentEditor
     private void BorderEditor_AdornmentChanged (object? sender, EventArgs e)
     {
         _ckbTitle!.CheckedState = ((Border)AdornmentToEdit!).Settings.FastHasFlags (BorderSettings.Title) ? CheckState.Checked : CheckState.UnChecked;
-        _rbBorderStyle!.SelectedItem = (int)((Border)AdornmentToEdit).LineStyle;
+        _osBorderStyle!.Value = ((Border)AdornmentToEdit).LineStyle;
         _ckbGradient!.CheckedState = ((Border)AdornmentToEdit).Settings.FastHasFlags (BorderSettings.Gradient) ? CheckState.Checked : CheckState.UnChecked;
     }
 
     private void BorderEditor_Initialized (object? sender, EventArgs e)
     {
-        List<LineStyle> borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast<LineStyle> ().ToList ();
-
-        _rbBorderStyle = new ()
+        _osBorderStyle = new ()
         {
             X = 0,
 
-            Y = Pos.Bottom (SubViews.ToArray() [^1]),
+            Y = Pos.Bottom (SubViews.ToArray () [^1]),
             Width = Dim.Fill (),
-            SelectedItem = (int)(((Border)AdornmentToEdit!)?.LineStyle ?? LineStyle.None),
+            Value = ((Border)AdornmentToEdit!)?.LineStyle ?? LineStyle.None,
             BorderStyle = LineStyle.Single,
             Title = "Border St_yle",
             SuperViewRendersLineCanvas = true,
-            RadioLabels = borderStyleEnum.Select (style => style.ToString ()).ToArray ()
         };
-        Add (_rbBorderStyle);
+        Add (_osBorderStyle);
 
-        _rbBorderStyle.SelectedItemChanged += OnRbBorderStyleOnSelectedItemChanged;
+        _osBorderStyle.ValueChanged += OnRbBorderStyleOnValueChanged;
 
         _ckbTitle = new ()
         {
             X = 0,
-            Y = Pos.Bottom (_rbBorderStyle),
+            Y = Pos.Bottom (_osBorderStyle),
 
             CheckedState = CheckState.Checked,
             SuperViewRendersLineCanvas = true,
@@ -73,10 +70,14 @@ public class BorderEditor : AdornmentEditor
 
         return;
 
-        void OnRbBorderStyleOnSelectedItemChanged (object? s, SelectedItemChangedArgs args)
+        void OnRbBorderStyleOnValueChanged (object? s, EventArgs<LineStyle?> args)
         {
             LineStyle prevBorderStyle = AdornmentToEdit!.BorderStyle;
-            ((Border)AdornmentToEdit).LineStyle = (LineStyle)args.SelectedItem!;
+
+            if (args.Value is { })
+            {
+                ((Border)AdornmentToEdit).LineStyle = (LineStyle)args.Value;
+            }
 
             if (((Border)AdornmentToEdit).LineStyle == LineStyle.None)
             {

+ 9 - 9
Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs

@@ -18,7 +18,7 @@ public class DimEditor : EditorBase
     }
 
     private int _value;
-    private RadioGroup? _dimRadioGroup;
+    private OptionSelector? _dimOptionSelector;
     private TextField? _valueEdit;
 
     /// <inheritdoc />
@@ -44,7 +44,7 @@ public class DimEditor : EditorBase
 
         try
         {
-            _dimRadioGroup!.SelectedItem = _dimNames.IndexOf (_dimNames.First (s => dim!.ToString ().StartsWith (s)));
+            _dimOptionSelector!.Value = _dimNames.IndexOf (_dimNames.First (s => dim!.ToString ().StartsWith (s)));
         }
         catch (InvalidOperationException e)
         {
@@ -92,13 +92,13 @@ public class DimEditor : EditorBase
             Text = $"{Title}:"
         };
         Add (label);
-        _dimRadioGroup = new () { X = 0, Y = Pos.Bottom (label), RadioLabels = _radioItems };
-        _dimRadioGroup.SelectedItemChanged += OnRadioGroupOnSelectedItemChanged;
+        _dimOptionSelector = new () { X = 0, Y = Pos.Bottom (label), Labels = _optionLabels };
+        _dimOptionSelector.ValueChanged += OnOptionSelectorOnValueChanged;
         _valueEdit = new ()
         {
             X = Pos.Right (label) + 1,
             Y = 0,
-            Width = Dim.Func (_ => _radioItems.Max (i => i.GetColumns ()) - label.Frame.Width + 1),
+            Width = Dim.Func (_ => _optionLabels.Max (i => i.GetColumns ()) - label.Frame.Width + 1),
             Text = $"{_value}"
         };
 
@@ -117,15 +117,15 @@ public class DimEditor : EditorBase
         };
         Add (_valueEdit);
 
-        Add (_dimRadioGroup);
+        Add (_dimOptionSelector);
 
     }
 
-    private void OnRadioGroupOnSelectedItemChanged (object? s, SelectedItemChangedArgs selected) { DimChanged (); }
+    private void OnOptionSelectorOnValueChanged (object? s, EventArgs<int?> selected) { DimChanged (); }
 
     // These need to have same order 
     private readonly List<string> _dimNames = ["Absolute", "Auto", "Fill", "Func", "Percent",];
-    private readonly string [] _radioItems = ["Absolute(n)", "Auto", "Fill(n)", "Func(()=>n)", "Percent(n)",];
+    private readonly string [] _optionLabels = ["Absolute(n)", "Auto", "Fill(n)", "Func(()=>n)", "Percent(n)",];
 
     private void DimChanged ()
     {
@@ -136,7 +136,7 @@ public class DimEditor : EditorBase
 
         try
         {
-            Dim? dim = _dimRadioGroup!.SelectedItem switch
+            Dim? dim = _dimOptionSelector!.Value switch
             {
                 0 => Dim.Absolute (_value),
                 1 => Dim.Auto (),

+ 14 - 22
Examples/UICatalog/Scenarios/EditorsAndHelpers/MarginEditor.cs

@@ -12,7 +12,7 @@ public class MarginEditor : AdornmentEditor
         AdornmentChanged += MarginEditor_AdornmentChanged;
     }
 
-    private RadioGroup? _rgShadow;
+    private OptionSelector<ShadowStyle>? _optionsShadow;
 
     private FlagSelector? _flagSelectorTransparent;
 
@@ -20,18 +20,18 @@ public class MarginEditor : AdornmentEditor
     {
         if (AdornmentToEdit is { })
         {
-            _rgShadow!.SelectedItem = (int)((Margin)AdornmentToEdit).ShadowStyle;
+            _optionsShadow!.Value = ((Margin)AdornmentToEdit).ShadowStyle;
         }
 
         if (AdornmentToEdit is { })
         {
-            _flagSelectorTransparent!.Value = (uint)((Margin)AdornmentToEdit).ViewportSettings;
+            _flagSelectorTransparent!.Value = (int)((Margin)AdornmentToEdit).ViewportSettings;
         }
     }
 
     private void MarginEditor_Initialized (object? sender, EventArgs e)
     {
-        _rgShadow = new RadioGroup
+        _optionsShadow = new ()
         {
             X = 0,
             Y = Pos.Bottom (SubViews.ElementAt(SubViews.Count-1)),
@@ -39,44 +39,36 @@ public class MarginEditor : AdornmentEditor
             SuperViewRendersLineCanvas = true,
             Title = "_Shadow",
             BorderStyle = LineStyle.Single,
-            RadioLabels = Enum.GetNames (typeof (ShadowStyle)),
+            AssignHotKeys = true
         };
 
         if (AdornmentToEdit is { })
         {
-            _rgShadow.SelectedItem = (int)((Margin)AdornmentToEdit).ShadowStyle;
+            _optionsShadow.Value = ((Margin)AdornmentToEdit).ShadowStyle;
         }
 
-        _rgShadow.SelectedItemChanged += (_, args) =>
-                                        {
-                                            ((Margin)AdornmentToEdit!).ShadowStyle = (ShadowStyle)args.SelectedItem!;
-                                        };
+        _optionsShadow.ValueChanged += (_, args) => ((Margin)AdornmentToEdit!).ShadowStyle = args.Value!.Value;
 
-        Add (_rgShadow);
+        Add (_optionsShadow);
 
-        var flags = new Dictionary<uint, string> ()
-        {
-            { (uint)Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent, "Transparent" },
-            { (uint)Terminal.Gui.ViewBase.ViewportSettingsFlags.TransparentMouse, "TransparentMouse" }
-        };
-
-        _flagSelectorTransparent = new FlagSelector ()
+        _flagSelectorTransparent = new FlagSelector<ViewportSettingsFlags> ()
         {
             X = 0,
-            Y = Pos.Bottom (_rgShadow),
+            Y = Pos.Bottom (_optionsShadow),
 
             SuperViewRendersLineCanvas = true,
             Title = "_ViewportSettings",
             BorderStyle = LineStyle.Single,
         };
-        _flagSelectorTransparent.SetFlags(flags.AsReadOnly ());
-
+        _flagSelectorTransparent.Values = [(int)ViewportSettingsFlags.Transparent, (int)ViewportSettingsFlags.TransparentMouse];
+        _flagSelectorTransparent.Labels = ["Transparent", "TransparentMouse"];
+        _flagSelectorTransparent.AssignHotKeys = true;
 
         Add (_flagSelectorTransparent);
 
         if (AdornmentToEdit is { })
         {
-            _flagSelectorTransparent.Value = (uint)((Margin)AdornmentToEdit).ViewportSettings;
+            _flagSelectorTransparent.Value = (int)((Margin)AdornmentToEdit).ViewportSettings;
         }
 
         _flagSelectorTransparent.ValueChanged += (_, args) =>

+ 9 - 9
Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs

@@ -19,7 +19,7 @@ public class PosEditor : EditorBase
     }
 
     private int _value;
-    private RadioGroup? _posRadioGroup;
+    private OptionSelector? _posOptionSelector;
     private TextField? _valueEdit;
 
     protected override void OnUpdateLayoutSettings ()
@@ -44,7 +44,7 @@ public class PosEditor : EditorBase
 
         try
         {
-            _posRadioGroup!.SelectedItem = _posNames.IndexOf (_posNames.First (s => pos.ToString ().Contains (s)));
+            _posOptionSelector!.Value = _posNames.IndexOf (_posNames.First (s => pos.ToString ().Contains (s)));
         }
         catch (InvalidOperationException e)
         {
@@ -91,14 +91,14 @@ public class PosEditor : EditorBase
             Text = $"{Title}:"
         };
         Add (label);
-        _posRadioGroup = new () { X = 0, Y = Pos.Bottom (label), RadioLabels = _radioItems };
-        _posRadioGroup.SelectedItemChanged += OnRadioGroupOnSelectedItemChanged;
+        _posOptionSelector = new () { X = 0, Y = Pos.Bottom (label), Labels = _optionLabels };
+        _posOptionSelector.ValueChanged += OnOptionSelectorOnValueChanged;
 
         _valueEdit = new ()
         {
             X = Pos.Right (label) + 1,
             Y = 0,
-            Width = Dim.Func (_ => _radioItems.Max (i => i.GetColumns ()) - label.Frame.Width + 1),
+            Width = Dim.Func (_ => _optionLabels.Max (i => i.GetColumns ()) - label.Frame.Width + 1),
             Text = $"{_value}"
         };
 
@@ -118,14 +118,14 @@ public class PosEditor : EditorBase
                                 };
         Add (_valueEdit);
 
-        Add (_posRadioGroup);
+        Add (_posOptionSelector);
     }
 
-    private void OnRadioGroupOnSelectedItemChanged (object? s, SelectedItemChangedArgs selected) { PosChanged (); }
+    private void OnOptionSelectorOnValueChanged (object? s, EventArgs<int?> selected) { PosChanged (); }
 
     // These need to have same order 
     private readonly List<string> _posNames = ["Absolute", "Align", "AnchorEnd", "Center", "Func", "Percent"];
-    private readonly string [] _radioItems = ["Absolute(n)", "Align", "AnchorEnd", "Center", "Func(()=>n)", "Percent(n)"];
+    private readonly string [] _optionLabels = ["Absolute(n)", "Align", "AnchorEnd", "Center", "Func(()=>n)", "Percent(n)"];
 
     private void PosChanged ()
     {
@@ -136,7 +136,7 @@ public class PosEditor : EditorBase
 
         try
         {
-            Pos? pos = _posRadioGroup!.SelectedItem switch
+            Pos? pos = _posOptionSelector!.Value switch
                        {
                            0 => Pos.Absolute (_value),
                            1 => Pos.Align (Alignment.Start),

+ 9 - 10
Examples/UICatalog/Scenarios/EditorsAndHelpers/ViewPropertiesEditor.cs

@@ -6,7 +6,7 @@ public class ViewPropertiesEditor : EditorBase
 {
     private CheckBox? _canFocusCheckBox;
     private CheckBox? _enabledCheckBox;
-    private RadioGroup? _orientation;
+    private OptionSelector<Orientation>? _orientationOptionSelector;
     private TextView? _text;
 
     /// <inheritdoc/>
@@ -48,24 +48,23 @@ public class ViewPropertiesEditor : EditorBase
 
         Label label = new () { X = Pos.Right (_enabledCheckBox) + 1, Y = Pos.Top (_enabledCheckBox), Text = "Orientation:" };
 
-        _orientation = new ()
+        _orientationOptionSelector = new ()
         {
             X = Pos.Right (label) + 1,
             Y = Pos.Top (label),
-            RadioLabels = new [] { "Horizontal", "Vertical" },
             Orientation = Orientation.Horizontal
         };
 
-        _orientation.SelectedItemChanged += (s, selected) =>
+        _orientationOptionSelector.ValueChanged += (s, selected) =>
                                             {
                                                 if (ViewToEdit is IOrientation orientatedView)
                                                 {
-                                                    orientatedView.Orientation = (Orientation)_orientation.SelectedItem!;
+                                                    orientatedView.Orientation = _orientationOptionSelector.Value!.Value;
                                                 }
                                             };
-        Add (label, _orientation);
+        Add (label, _orientationOptionSelector);
 
-        label = new () { X = 0, Y = Pos.Bottom (_orientation), Text = "Text:" };
+        label = new () { X = 0, Y = Pos.Bottom (_orientationOptionSelector), Text = "Text:" };
 
         _text = new ()
         {
@@ -114,12 +113,12 @@ public class ViewPropertiesEditor : EditorBase
 
             if (ViewToEdit is IOrientation orientatedView)
             {
-                _orientation!.SelectedItem = (int)orientatedView.Orientation;
-                _orientation.Enabled = true;
+                _orientationOptionSelector!.Value = orientatedView.Orientation;
+                _orientationOptionSelector.Enabled = true;
             }
             else
             {
-                _orientation!.Enabled = false;
+                _orientationOptionSelector!.Enabled = false;
             }
         }
     }

+ 138 - 138
Examples/UICatalog/Scenarios/FileDialogExamples.cs

@@ -1,8 +1,4 @@
-using System;
-using System.IO;
-using System.IO.Abstractions;
-using System.Linq;
-using System.Text;
+using System.IO.Abstractions;
 
 namespace UICatalog.Scenarios;
 
@@ -20,10 +16,10 @@ public class FileDialogExamples : Scenario
     private CheckBox _cbMustExist;
     private CheckBox _cbShowTreeBranchLines;
     private CheckBox _cbUseColors;
-    private RadioGroup _rgAllowedTypes;
-    private RadioGroup _rgCaption;
-    private RadioGroup _rgIcons;
-    private RadioGroup _rgOpenMode;
+    private OptionSelector _osAllowedTypes;
+    private OptionSelector _osCaption;
+    private OptionSelector _osIcons;
+    private OptionSelector _osOpenMode;
     private TextField _tbCancelButton;
     private TextField _tbOkButton;
 
@@ -34,65 +30,66 @@ public class FileDialogExamples : Scenario
         var x = 1;
         var win = new Window { Title = GetQuitKeyAndName () };
 
-        _cbMustExist = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Must E_xist" };
+        _cbMustExist = new () { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Must E_xist" };
         win.Add (_cbMustExist);
 
-        _cbUseColors = new CheckBox { CheckedState = FileDialogStyle.DefaultUseColors ? CheckState.Checked : CheckState.UnChecked, Y = y++, X = x, Text = "_Use Colors" };
+        _cbUseColors = new ()
+            { CheckedState = FileDialogStyle.DefaultUseColors ? CheckState.Checked : CheckState.UnChecked, Y = y++, X = x, Text = "_Use Colors" };
         win.Add (_cbUseColors);
 
-        _cbCaseSensitive = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Case Sensitive Search" };
+        _cbCaseSensitive = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Case Sensitive Search" };
         win.Add (_cbCaseSensitive);
 
-        _cbAllowMultipleSelection = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Multiple" };
+        _cbAllowMultipleSelection = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Multiple" };
         win.Add (_cbAllowMultipleSelection);
 
-        _cbShowTreeBranchLines = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Tree Branch _Lines" };
+        _cbShowTreeBranchLines = new () { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Tree Branch _Lines" };
         win.Add (_cbShowTreeBranchLines);
 
-        _cbAlwaysTableShowHeaders = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Always Show _Headers" };
+        _cbAlwaysTableShowHeaders = new () { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Always Show _Headers" };
         win.Add (_cbAlwaysTableShowHeaders);
 
-        _cbDrivesOnlyInTree = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Only Show _Drives" };
+        _cbDrivesOnlyInTree = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Only Show _Drives" };
         win.Add (_cbDrivesOnlyInTree);
 
-        _cbPreserveFilenameOnDirectoryChanges = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Preserve Filename" };
+        _cbPreserveFilenameOnDirectoryChanges = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Preserve Filename" };
         win.Add (_cbPreserveFilenameOnDirectoryChanges);
 
         y = 0;
         x = 24;
 
         win.Add (
-                 new LineView (Orientation.Vertical) { X = x++, Y = 1, Height = 4 }
+                 new Line { Orientation = Orientation.Vertical, X = x++, Y = 1, Height = 4 }
                 );
         win.Add (new Label { X = x++, Y = y++, Text = "Caption" });
 
-        _rgCaption = new RadioGroup { X = x, Y = y };
-        _rgCaption.RadioLabels = new [] { "_Ok", "O_pen", "_Save" };
-        win.Add (_rgCaption);
+        _osCaption = new () { X = x, Y = y };
+        _osCaption.Labels = ["_Ok", "O_pen", "_Save"];
+        win.Add (_osCaption);
 
         y = 0;
         x = 34;
 
         win.Add (
-                 new LineView (Orientation.Vertical) { X = x++, Y = 1, Height = 4 }
+                 new Line { Orientation = Orientation.Vertical, X = x++, Y = 1, Height = 4 }
                 );
         win.Add (new Label { X = x++, Y = y++, Text = "OpenMode" });
 
-        _rgOpenMode = new RadioGroup { X = x, Y = y };
-        _rgOpenMode.RadioLabels = new [] { "_File", "D_irectory", "_Mixed" };
-        win.Add (_rgOpenMode);
+        _osOpenMode = new () { X = x, Y = y };
+        _osOpenMode.Labels = ["_File", "D_irectory", "_Mixed"];
+        win.Add (_osOpenMode);
 
         y = 0;
         x = 48;
 
         win.Add (
-                 new LineView (Orientation.Vertical) { X = x++, Y = 1, Height = 4 }
+                 new Line { Orientation = Orientation.Vertical, X = x++, Y = 1, Height = 4 }
                 );
         win.Add (new Label { X = x++, Y = y++, Text = "Icons" });
 
-        _rgIcons = new RadioGroup { X = x, Y = y };
-        _rgIcons.RadioLabels = new [] { "_None", "_Unicode", "Nerd_*" };
-        win.Add (_rgIcons);
+        _osIcons = new () { X = x, Y = y };
+        _osIcons.Labels = ["_None", "_Unicode", "Nerd_*"];
+        win.Add (_osIcons);
 
         win.Add (new Label { Y = Pos.AnchorEnd (2), Text = "* Requires installing Nerd fonts" });
         win.Add (new Label { Y = Pos.AnchorEnd (1), Text = "  (see: https://github.com/devblackops/Terminal-Icons)" });
@@ -101,48 +98,48 @@ public class FileDialogExamples : Scenario
         x = 24;
 
         win.Add (
-                 new LineView (Orientation.Vertical) { X = x++, Y = y + 1, Height = 4 }
+                 new Line { Orientation = Orientation.Vertical, X = x++, Y = y + 1, Height = 4 }
                 );
         win.Add (new Label { X = x++, Y = y++, Text = "Allowed" });
 
-        _rgAllowedTypes = new RadioGroup { X = x, Y = y };
-        _rgAllowedTypes.RadioLabels = new [] { "An_y", "Cs_v (Recommended)", "Csv (S_trict)" };
-        win.Add (_rgAllowedTypes);
+        _osAllowedTypes = new () { X = x, Y = y };
+        _osAllowedTypes.Labels = ["An_y", "Cs_v (Recommended)", "Csv (S_trict)"];
+        win.Add (_osAllowedTypes);
 
         y = 5;
         x = 45;
 
         win.Add (
-                 new LineView (Orientation.Vertical) { X = x++, Y = y + 1, Height = 4 }
+                 new Line { Orientation = Orientation.Vertical, X = x++, Y = y + 1, Height = 4 }
                 );
         win.Add (new Label { X = x++, Y = y++, Text = "Buttons" });
 
         win.Add (new Label { X = x, Y = y++, Text = "O_k Text:" });
-        _tbOkButton = new TextField { X = x, Y = y++, Width = 12 };
+        _tbOkButton = new () { X = x, Y = y++, Width = 12 };
         win.Add (_tbOkButton);
         win.Add (new Label { X = x, Y = y++, Text = "_Cancel Text:" });
-        _tbCancelButton = new TextField { X = x, Y = y++, Width = 12 };
+        _tbCancelButton = new () { X = x, Y = y++, Width = 12 };
         win.Add (_tbCancelButton);
-        _cbFlipButtonOrder = new CheckBox { X = x, Y = y++, Text = "Flip Ord_er" };
+        _cbFlipButtonOrder = new () { X = x, Y = y++, Text = "Flip Ord_er" };
         win.Add (_cbFlipButtonOrder);
 
         var btn = new Button { X = 1, Y = 9, IsDefault = true, Text = "Run Dialog" };
 
         win.Accepting += (s, e) =>
-                        {
-                            try
-                            {
-                                CreateDialog ();
-                            }
-                            catch (Exception ex)
-                            {
-                                MessageBox.ErrorQuery ("Error", ex.ToString (), "_Ok");
-                            }
-                            finally
-                            {
-                                e.Handled = true;
-                            }
-                        };
+                         {
+                             try
+                             {
+                                 CreateDialog ();
+                             }
+                             catch (Exception ex)
+                             {
+                                 MessageBox.ErrorQuery ("Error", ex.ToString (), "_Ok");
+                             }
+                             finally
+                             {
+                                 e.Handled = true;
+                             }
+                         };
         win.Add (btn);
 
         Application.Run (win);
@@ -164,112 +161,115 @@ public class FileDialogExamples : Scenario
 
     private void CreateDialog ()
     {
-        var fd = new FileDialog
+        if (_osOpenMode.Value is { })
         {
-            OpenMode = Enum.Parse<OpenMode> (
-                                             _rgOpenMode.RadioLabels
-                                                        .Select (l => TextFormatter.FindHotKey (l, _rgOpenMode.HotKeySpecifier, out int hotPos, out Key _)
-
-                                                                          // Remove the hotkey specifier at the found position
-                                                                          ? TextFormatter.RemoveHotKeySpecifier (l, hotPos, _rgOpenMode.HotKeySpecifier)
-
-                                                                          // No hotkey found, return the label as is
-                                                                          : l)
-                                                        .ToArray () [_rgOpenMode.SelectedItem]
-                                            ),
-            MustExist = _cbMustExist.CheckedState == CheckState.Checked,
-            AllowsMultipleSelection = _cbAllowMultipleSelection.CheckedState == CheckState.Checked
-        };
+            var fd = new FileDialog
+            {
+                OpenMode = Enum.Parse<OpenMode> (
+                                                 _osOpenMode.Labels
+                                                            .Select (l => TextFormatter.FindHotKey (l, _osOpenMode.HotKeySpecifier, out int hotPos, out Key _)
+
+                                                                              // Remove the hotkey specifier at the found position
+                                                                              ? TextFormatter.RemoveHotKeySpecifier (l, hotPos, _osOpenMode.HotKeySpecifier)
+
+                                                                              // No hotkey found, return the label as is
+                                                                              : l)
+                                                            .ToArray () [_osOpenMode.Value.Value]
+                                                ),
+                MustExist = _cbMustExist.CheckedState == CheckState.Checked,
+                AllowsMultipleSelection = _cbAllowMultipleSelection.CheckedState == CheckState.Checked
+            };
+
+            fd.Style.OkButtonText =
+                _osCaption.Labels.Select (l => TextFormatter.RemoveHotKeySpecifier (l, 0, _osCaption.HotKeySpecifier)).ToArray ()
+                    [_osCaption.Value!.Value];
+
+            // If Save style dialog then give them an overwrite prompt
+            if (_osCaption.Value == 2)
+            {
+                fd.FilesSelected += ConfirmOverwrite;
+            }
 
-        fd.Style.OkButtonText = _rgCaption.RadioLabels.Select (l => TextFormatter.RemoveHotKeySpecifier(l, 0, _rgCaption.HotKeySpecifier)).ToArray() [_rgCaption.SelectedItem];
+            fd.Style.IconProvider.UseUnicodeCharacters = _osIcons.Value == 1;
+            fd.Style.IconProvider.UseNerdIcons = _osIcons.Value == 2;
 
-        // If Save style dialog then give them an overwrite prompt
-        if (_rgCaption.SelectedItem == 2)
-        {
-            fd.FilesSelected += ConfirmOverwrite;
-        }
-
-        fd.Style.IconProvider.UseUnicodeCharacters = _rgIcons.SelectedItem == 1;
-        fd.Style.IconProvider.UseNerdIcons = _rgIcons.SelectedItem == 2;
+            if (_cbCaseSensitive.CheckedState == CheckState.Checked)
+            {
+                fd.SearchMatcher = new CaseSensitiveSearchMatcher ();
+            }
 
-        if (_cbCaseSensitive.CheckedState == CheckState.Checked)
-        {
-            fd.SearchMatcher = new CaseSensitiveSearchMatcher ();
-        }
+            fd.Style.UseColors = _cbUseColors.CheckedState == CheckState.Checked;
 
-        fd.Style.UseColors = _cbUseColors.CheckedState == CheckState.Checked;
+            fd.Style.TreeStyle.ShowBranchLines = _cbShowTreeBranchLines.CheckedState == CheckState.Checked;
+            fd.Style.TableStyle.AlwaysShowHeaders = _cbAlwaysTableShowHeaders.CheckedState == CheckState.Checked;
 
-        fd.Style.TreeStyle.ShowBranchLines = _cbShowTreeBranchLines.CheckedState == CheckState.Checked;
-        fd.Style.TableStyle.AlwaysShowHeaders = _cbAlwaysTableShowHeaders.CheckedState == CheckState.Checked;
+            IDirectoryInfoFactory dirInfoFactory = new FileSystem ().DirectoryInfo;
 
-        IDirectoryInfoFactory dirInfoFactory = new FileSystem ().DirectoryInfo;
+            if (_cbDrivesOnlyInTree.CheckedState == CheckState.Checked)
+            {
+                fd.Style.TreeRootGetter = () => { return Environment.GetLogicalDrives ().ToDictionary (dirInfoFactory.New, k => k); };
+            }
 
-        if (_cbDrivesOnlyInTree.CheckedState == CheckState.Checked)
-        {
-            fd.Style.TreeRootGetter = () => { return Environment.GetLogicalDrives ().ToDictionary (dirInfoFactory.New, k => k); };
-        }
+            fd.Style.PreserveFilenameOnDirectoryChanges = _cbPreserveFilenameOnDirectoryChanges.CheckedState == CheckState.Checked;
 
-        fd.Style.PreserveFilenameOnDirectoryChanges = _cbPreserveFilenameOnDirectoryChanges.CheckedState == CheckState.Checked;
-        
+            if (_osAllowedTypes.Value > 0)
+            {
+                fd.AllowedTypes.Add (new AllowedType ("Data File", ".csv", ".tsv"));
 
-        if (_rgAllowedTypes.SelectedItem > 0)
-        {
-            fd.AllowedTypes.Add (new AllowedType ("Data File", ".csv", ".tsv"));
+                if (_osAllowedTypes.Value == 1)
+                {
+                    fd.AllowedTypes.Insert (1, new AllowedTypeAny ());
+                }
+            }
 
-            if (_rgAllowedTypes.SelectedItem == 1)
+            if (!string.IsNullOrWhiteSpace (_tbOkButton.Text))
             {
-                fd.AllowedTypes.Insert (1, new AllowedTypeAny ());
+                fd.Style.OkButtonText = _tbOkButton.Text;
             }
-        }
 
-        if (!string.IsNullOrWhiteSpace (_tbOkButton.Text))
-        {
-            fd.Style.OkButtonText = _tbOkButton.Text;
-        }
-
-        if (!string.IsNullOrWhiteSpace (_tbCancelButton.Text))
-        {
-            fd.Style.CancelButtonText = _tbCancelButton.Text;
-        }
-
-        if (_cbFlipButtonOrder.CheckedState == CheckState.Checked)
-        {
-            fd.Style.FlipOkCancelButtonLayoutOrder = true;
-        }
+            if (!string.IsNullOrWhiteSpace (_tbCancelButton.Text))
+            {
+                fd.Style.CancelButtonText = _tbCancelButton.Text;
+            }
 
-        Application.Run (fd);
+            if (_cbFlipButtonOrder.CheckedState == CheckState.Checked)
+            {
+                fd.Style.FlipOkCancelButtonLayoutOrder = true;
+            }
 
-        var canceled = fd.Canceled;
-        var multiSelected = fd.MultiSelected;
-        var path = fd.Path;
+            Application.Run (fd);
 
-        // This needs to be disposed before opening other toplevel
-        fd.Dispose ();
+            bool canceled = fd.Canceled;
+            IReadOnlyList<string> multiSelected = fd.MultiSelected;
+            string path = fd.Path;
 
-        if (canceled)
-        {
-            MessageBox.Query (
-                              "Canceled",
-                              "You canceled navigation and did not pick anything",
-                              "Ok"
-                             );
+            // This needs to be disposed before opening other toplevel
+            fd.Dispose ();
 
-        }
-        else if (_cbAllowMultipleSelection.CheckedState == CheckState.Checked)
-        {
-            MessageBox.Query (
-                              "Chosen!",
-                              "You chose:" + Environment.NewLine + string.Join (Environment.NewLine, multiSelected.Select (m => m)),
-                              "Ok"
-                             );
-        }
-        else
-        {
-            MessageBox.Query (
-                              "Chosen!",
-                              "You chose:" + Environment.NewLine + path,
-                              "Ok"
-                             );
+            if (canceled)
+            {
+                MessageBox.Query (
+                                  "Canceled",
+                                  "You canceled navigation and did not pick anything",
+                                  "Ok"
+                                 );
+            }
+            else if (_cbAllowMultipleSelection.CheckedState == CheckState.Checked)
+            {
+                MessageBox.Query (
+                                  "Chosen!",
+                                  "You chose:" + Environment.NewLine + string.Join (Environment.NewLine, multiSelected.Select (m => m)),
+                                  "Ok"
+                                 );
+            }
+            else
+            {
+                MessageBox.Query (
+                                  "Chosen!",
+                                  "You chose:" + Environment.NewLine + path,
+                                  "Ok"
+                                 );
+            }
         }
     }
 

+ 6 - 6
Examples/UICatalog/Scenarios/GraphViewExample.cs

@@ -144,8 +144,8 @@ public class GraphViewExample : Scenario
             Height = Dim.Fill (1),
             BorderStyle = LineStyle.Single
         };
-        _graphView.Border.Thickness = _thickness;
-        _graphView.Margin.Thickness = _thickness;
+        _graphView.Border!.Thickness = _thickness;
+        _graphView.Margin!.Thickness = _thickness;
         _graphView.Padding.Thickness = _thickness;
 
         app.Add (_graphView);
@@ -955,14 +955,14 @@ public class GraphViewExample : Scenario
         if (_miShowBorder.Checked == true)
         {
             _graphView.BorderStyle = LineStyle.Single;
-            _graphView.Border.Thickness = _thickness;
-            _graphView.Margin.Thickness = _thickness;
+            _graphView.Border!.Thickness = _thickness;
+            _graphView.Margin!.Thickness = _thickness;
             _graphView.Padding.Thickness = _thickness;
         }
         else
         {
             _graphView.BorderStyle = LineStyle.None;
-            _graphView.Margin.Thickness = Thickness.Empty;
+            _graphView.Margin!.Thickness = Thickness.Empty;
             _graphView.Padding.Thickness = Thickness.Empty;
         }
     }
@@ -999,7 +999,7 @@ public class GraphViewExample : Scenario
 
         protected override void DrawBarLine (GraphView graph, Point start, Point end, BarSeriesBar beingDrawn)
         {
-            IConsoleDriver driver = Application.Driver;
+            IDriver driver = Application.Driver;
 
             int x = start.X;
 

+ 17 - 17
Examples/UICatalog/Scenarios/Images.cs

@@ -50,8 +50,8 @@ public class Images : Scenario
     private SixelToRender _fireSixel;
     private int _fireFrameCounter;
     private bool _isDisposed;
-    private RadioGroup _rgPaletteBuilder;
-    private RadioGroup _rgDistanceAlgorithm;
+    private OptionSelector _osPaletteBuilder;
+    private OptionSelector _osDistanceAlgorithm;
     private NumericUpDown _popularityThreshold;
     private SixelToRender _sixelImage;
 
@@ -408,22 +408,22 @@ public class Images : Scenario
             Y = Pos.Bottom (_pxY) + 1
         };
 
-        _rgPaletteBuilder = new ()
+        _osPaletteBuilder = new ()
         {
-            RadioLabels = new []
-            {
+            Labels =
+            [
                 "Popularity",
                 "Median Cut"
-            },
+            ],
             X = Pos.Right (_sixelView) + 2,
             Y = Pos.Bottom (l1),
-            SelectedItem = 1
+            Value = 1
         };
 
         _popularityThreshold = new ()
         {
-            X = Pos.Right (_rgPaletteBuilder) + 1,
-            Y = Pos.Top (_rgPaletteBuilder),
+            X = Pos.Right (_osPaletteBuilder) + 1,
+            Y = Pos.Top (_osPaletteBuilder),
             Value = 8
         };
 
@@ -439,12 +439,12 @@ public class Images : Scenario
             Text = "Color Distance Algorithm",
             Width = Dim.Auto (),
             X = Pos.Right (_sixelView),
-            Y = Pos.Bottom (_rgPaletteBuilder) + 1
+            Y = Pos.Bottom (_osPaletteBuilder) + 1
         };
 
-        _rgDistanceAlgorithm = new ()
+        _osDistanceAlgorithm = new ()
         {
-            RadioLabels = new []
+            Labels = new []
             {
                 "Euclidian",
                 "CIE76"
@@ -458,10 +458,10 @@ public class Images : Scenario
         _sixelSupported.Add (lblPxY);
         _sixelSupported.Add (_pxY);
         _sixelSupported.Add (l1);
-        _sixelSupported.Add (_rgPaletteBuilder);
+        _sixelSupported.Add (_osPaletteBuilder);
 
         _sixelSupported.Add (l2);
-        _sixelSupported.Add (_rgDistanceAlgorithm);
+        _sixelSupported.Add (_osDistanceAlgorithm);
         _sixelSupported.Add (_popularityThreshold);
         _sixelSupported.Add (lblPopThreshold);
 
@@ -470,7 +470,7 @@ public class Images : Scenario
 
     private IPaletteBuilder GetPaletteBuilder ()
     {
-        switch (_rgPaletteBuilder.SelectedItem)
+        switch (_osPaletteBuilder.Value)
         {
             case 0: return new PopularityPaletteWithThreshold (GetDistanceAlgorithm (), _popularityThreshold.Value);
             case 1: return new MedianCutPaletteBuilder (GetDistanceAlgorithm ());
@@ -480,7 +480,7 @@ public class Images : Scenario
 
     private IColorDistance GetDistanceAlgorithm ()
     {
-        switch (_rgDistanceAlgorithm.SelectedItem)
+        switch (_osDistanceAlgorithm.Value)
         {
             case 0: return new EuclideanColorDistance ();
             case 1: return new CIE76ColorDistance ();
@@ -532,7 +532,7 @@ public class Images : Scenario
             // Application.Driver?.Move (_screenLocationForSixel.X, _screenLocationForSixel.Y);
             // Application.Driver?.AddStr (_encodedSixelData);
 
-            // Works in NetDriver but results in screen flicker when moving mouse but vanish instantly
+            // Works in DotNetDriver but results in screen flicker when moving mouse but vanish instantly
             // Console.SetCursorPosition (_screenLocationForSixel.X, _screenLocationForSixel.Y);
             // Console.Write (_encodedSixelData);
         }

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

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

+ 3 - 3
Examples/UICatalog/Scenarios/LineCanvasExperiment.cs

@@ -130,9 +130,9 @@ public class LineCanvasExperiment : Scenario
         //    //Scheme = Colors.Schemes ["Error"],
         //    SuperViewRendersLineCanvas = true
         //};
-        //marginWindow.Margin.Scheme = Colors.Schemes ["Error"];
-        //marginWindow.Margin.Thickness = new (1);
-        //marginWindow.Border.Thickness = new (1, 2, 1, 1);
+        //marginWindow.Margin!.Scheme = Colors.Schemes ["Error"];
+        //marginWindow.Margin!.Thickness = new (1);
+        //marginWindow.Border!.Thickness = new (1, 2, 1, 1);
 
         //frame1.Add (marginWindow);
 

+ 10 - 4
Examples/UICatalog/Scenarios/LineDrawing.cs

@@ -210,7 +210,7 @@ public class ToolsView : Window
 {
     private Button _addLayerBtn;
     private readonly AttributeView _colors;
-    private RadioGroup _stylePicker;
+    private OptionSelector<LineStyle> _stylePicker;
 
     public Attribute CurrentColor
     {
@@ -236,10 +236,16 @@ public class ToolsView : Window
 
         _stylePicker = new ()
         {
-            X = 0, Y = Pos.Bottom (_colors), RadioLabels = Enum.GetNames (typeof (LineStyle)).ToArray ()
+            X = 0, Y = Pos.Bottom (_colors), AssignHotKeys = true
         };
-        _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem); };
-        _stylePicker.SelectedItem = 1;
+        _stylePicker.ValueChanged += (s, a) =>
+                                     {
+                                         if (a.Value is { })
+                                         {
+                                             SetStyle?.Invoke ((LineStyle)a.Value);
+                                         }
+                                     };
+        _stylePicker.Value = LineStyle.Single;
 
         _addLayerBtn = new () { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
 

+ 219 - 0
Examples/UICatalog/Scenarios/LineExample.cs

@@ -0,0 +1,219 @@
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Line", "Demonstrates the Line view with LineCanvas integration.")]
+[ScenarioCategory ("Controls")]
+[ScenarioCategory ("Drawing")]
+[ScenarioCategory ("Adornments")]
+public class LineExample : Scenario
+{
+    public override void Main ()
+    {
+        Application.Init ();
+
+        var app = new Window
+        {
+            Title = GetQuitKeyAndName ()
+        };
+
+        // Section 1: Basic Lines
+        var basicLabel = new Label
+        {
+            X = 0,
+            Y = 0,
+            Text = "Basic Lines:"
+        };
+        app.Add (basicLabel);
+
+        // Horizontal line
+        var hLine = new Line
+        {
+            X = 0,
+            Y = 1,
+            Width = 30
+        };
+        app.Add (hLine);
+
+        // Vertical line
+        var vLine = new Line
+        {
+            X = 32,
+            Y = 0,
+            Height = 10,
+            Orientation = Orientation.Vertical
+        };
+        app.Add (vLine);
+
+        // Section 2: Different Line Styles
+        var stylesLabel = new Label
+        {
+            X = 0,
+            Y = 3,
+            Text = "Line Styles:"
+        };
+        app.Add (stylesLabel);
+
+        (LineStyle, string) [] styles = new []
+        {
+            (LineStyle.Single, "Single"),
+            (LineStyle.Double, "Double"),
+            (LineStyle.Heavy, "Heavy"),
+            (LineStyle.Rounded, "Rounded"),
+            (LineStyle.Dashed, "Dashed"),
+            (LineStyle.Dotted, "Dotted")
+        };
+
+        var yPos = 4;
+
+        foreach ((LineStyle style, string name) in styles)
+        {
+            app.Add (new Label { X = 0, Y = yPos, Width = 15, Text = name + ":" });
+            app.Add (new Line { X = 16, Y = yPos, Width = 14, Style = style });
+            yPos++;
+        }
+
+        // Section 3: Line Intersections
+        var intersectionLabel = new Label
+        {
+            X = 35,
+            Y = 3,
+            Text = "Line Intersections:"
+        };
+        app.Add (intersectionLabel);
+
+        // Create a grid of intersecting lines
+        var gridX = 35;
+        var gridY = 5;
+
+        // Horizontal lines in the grid
+        for (var i = 0; i < 5; i++)
+        {
+            app.Add (
+                     new Line
+                     {
+                         X = gridX,
+                         Y = gridY + i * 2,
+                         Width = 21,
+                         Style = LineStyle.Single
+                     });
+        }
+
+        // Vertical lines in the grid
+        for (var i = 0; i < 5; i++)
+        {
+            app.Add (
+                     new Line
+                     {
+                         X = gridX + i * 5,
+                         Y = gridY,
+                         Height = 9,
+                         Orientation = Orientation.Vertical,
+                         Style = LineStyle.Single
+                     });
+        }
+
+        // Section 4: Mixed Styles (shows how LineCanvas handles different line styles)
+        var mixedLabel = new Label
+        {
+            X = 60,
+            Y = 3,
+            Text = "Mixed Style Intersections:"
+        };
+        app.Add (mixedLabel);
+
+        // Double horizontal
+        app.Add (
+                 new Line
+                 {
+                     X = 60,
+                     Y = 5,
+                     Width = 20,
+                     Style = LineStyle.Double
+                 });
+
+        // Single vertical through double horizontal
+        app.Add (
+                 new Line
+                 {
+                     X = 70,
+                     Y = 4,
+                     Height = 3,
+                     Orientation = Orientation.Vertical,
+                     Style = LineStyle.Single
+                 });
+
+        // Heavy horizontal
+        app.Add (
+                 new Line
+                 {
+                     X = 60,
+                     Y = 8,
+                     Width = 20,
+                     Style = LineStyle.Heavy
+                 });
+
+        // Single vertical through heavy horizontal
+        app.Add (
+                 new Line
+                 {
+                     X = 70,
+                     Y = 7,
+                     Height = 3,
+                     Orientation = Orientation.Vertical,
+                     Style = LineStyle.Single
+                 });
+
+        // Section 5: Box Example (showing borders and lines working together)
+        var boxLabel = new Label
+        {
+            X = 0,
+            Y = 12,
+            Text = "Lines with Borders:"
+        };
+        app.Add (boxLabel);
+
+        var framedView = new FrameView
+        {
+            Title = "Frame",
+            X = 0,
+            Y = 13,
+            Width = 30,
+            Height = 8,
+            BorderStyle = LineStyle.Single
+        };
+
+        // Add a cross inside the frame
+        framedView.Add (
+                        new Line
+                        {
+                            X = 0,
+                            Y = 3,
+                            Width = Dim.Fill (),
+                            Style = LineStyle.Single
+                        });
+
+        framedView.Add (
+                        new Line
+                        {
+                            X = 14,
+                            Y = 0,
+                            Height = Dim.Fill (),
+                            Orientation = Orientation.Vertical,
+                            Style = LineStyle.Single
+                        });
+
+        app.Add (framedView);
+
+        // Add help text
+        var helpLabel = new Label
+        {
+            X = Pos.Center (),
+            Y = Pos.AnchorEnd (1),
+            Text = "Line integrates with LineCanvas for automatic intersection handling"
+        };
+        app.Add (helpLabel);
+
+        Application.Run (app);
+        app.Dispose ();
+        Application.Shutdown ();
+    }
+}

+ 0 - 76
Examples/UICatalog/Scenarios/LineViewExample.cs

@@ -1,76 +0,0 @@
-using System.Globalization;
-using System.Text;
-
-namespace UICatalog.Scenarios;
-
-[ScenarioMetadata ("Line View", "Demonstrates drawing lines using the LineView control.")]
-[ScenarioCategory ("Controls")]
-[ScenarioCategory ("LineView")]
-[ScenarioCategory ("Adornments")]
-public class LineViewExample : Scenario
-{
-    public override void Main ()
-    {
-        Application.Init ();
-
-        var appWindow = new Window()
-        {
-            Title = GetQuitKeyAndName (),
-        };
-
-        appWindow.Add (new Label { Y = 1, Text = "Regular Line" });
-
-        // creates a horizontal line
-        var line = new LineView { Y = 2 };
-
-        appWindow.Add (line);
-
-        appWindow.Add (new Label { Y = 3, Text = "Double Width Line" });
-
-        // creates a horizontal line
-        var doubleLine = new LineView { Y = 4, LineRune = (Rune)'\u2550' };
-
-        appWindow.Add (doubleLine);
-
-        appWindow.Add (new Label { Y = 5, Text = "Short Line" });
-
-        // creates a horizontal line
-        var shortLine = new LineView { Y = 5, Width = 10 };
-
-        appWindow.Add (shortLine);
-
-        appWindow.Add (new Label { Y = 7, Text = "Arrow Line" });
-
-        // creates a horizontal line
-        var arrowLine = new LineView
-        {
-            Y = 8, Width = 10, StartingAnchor = Glyphs.LeftTee, EndingAnchor = (Rune)'>'
-        };
-
-        appWindow.Add (arrowLine);
-
-        appWindow.Add (new Label { Y = 10, X = 11, Text = "Vertical Line" });
-
-        // creates a horizontal line
-        var verticalLine = new LineView (Orientation.Vertical) { X = 25 };
-
-        appWindow.Add (verticalLine);
-
-        appWindow.Add (new Label { Y = 12, X = 28, Text = "Vertical Arrow" });
-
-        // creates a horizontal line
-        var verticalArrow = new LineView (Orientation.Vertical)
-        {
-            X = 27, StartingAnchor = Glyphs.TopTee, EndingAnchor = (Rune)'V'
-        };
-
-        appWindow.Add (verticalArrow);
-
-        // Run - Start the application.
-        Application.Run (appWindow);
-        appWindow.Dispose ();
-
-        // Shutdown - Calling Application.Shutdown is required.
-        Application.Shutdown ();
-    }
-}

+ 3 - 3
Examples/UICatalog/Scenarios/Mazing.cs

@@ -5,7 +5,7 @@ namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("A Mazing", "Illustrates how to make a basic maze game.")]
 [ScenarioCategory ("Drawing")]
-[ScenarioCategory ("Mouse and KeyBoard")]
+[ScenarioCategory ("Mouse and Keyboard")]
 [ScenarioCategory ("Games")]
 public class Mazing : Scenario
 {
@@ -33,7 +33,7 @@ public class Mazing : Scenario
         _top.KeyBindings.Add (Key.CursorDown, Command.Down);
 
         // Changing the key-bindings of a View is not allowed, however,
-        // by default, Toplevel does't bind any of our movement keys, so
+        // by default, Toplevel doesn't bind any of our movement keys, so
         // we can take advantage of the CommandNotBound event to handle them
         // 
         // An alternative implementation would be to create a TopLevel subclass that
@@ -129,7 +129,7 @@ public class Mazing : Scenario
             return;
         }
 
-        Point newPos = _m.Player;
+        Point newPos = _m!.Player;
 
         Command? command = e.Context?.Command;
 

+ 9 - 8
Examples/UICatalog/Scenarios/MessageBoxes.cs

@@ -182,18 +182,19 @@ public class MessageBoxes : Scenario
         };
         frame.Add (label);
 
-        var styleRadioGroup = new RadioGroup
+        var styleOptionSelector = new OptionSelector ()
         {
-            X = Pos.Right (label) + 1, 
-            Y = Pos.Top (label), 
-            RadioLabels = ["_Query", "_Error"],
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Labels = ["_Query", "_Error"],
+            Title = "Sty_le"
         };
-        frame.Add (styleRadioGroup);
+        frame.Add (styleOptionSelector);
 
         label = new ()
         {
             X = 0,
-            Y = Pos.Bottom (styleRadioGroup),
+            Y = Pos.Bottom (styleOptionSelector),
 
             Width = Dim.Width (label),
             Height = 1,
@@ -202,7 +203,7 @@ public class MessageBoxes : Scenario
         };
         var ckbWrapMessage = new CheckBox
         {
-            X = Pos.Right (label) + 1, Y = Pos.Bottom (styleRadioGroup),
+            X = Pos.Right (label) + 1, Y = Pos.Bottom (styleOptionSelector),
             CheckedState = CheckState.Checked,
             Text = "_Wrap Message",
         };
@@ -246,7 +247,7 @@ public class MessageBoxes : Scenario
                                                    btns.Add ($"_{NumberToWords.Convert (i)}");
                                                }
 
-                                               if (styleRadioGroup.SelectedItem == 0)
+                                               if (styleOptionSelector.Value == 0)
                                                {
                                                    buttonPressedLabel.Text =
                                                        $"{MessageBox.Query (

+ 14 - 12
Examples/UICatalog/Scenarios/Navigation.cs

@@ -107,18 +107,7 @@ public class Navigation : Scenario
         //                 };
         //timer.Start ();
 
-        Application.Iteration += (sender, args) =>
-                                 {
-                                     if (progressBar.Fraction == 1.0)
-                                     {
-                                         progressBar.Fraction = 0;
-                                     }
-
-                                     progressBar.Fraction += 0.01f;
-
-                                     Application.Invoke (() => { });
-
-                                 };
+        Application.Iteration += OnApplicationIteration;
 
         View overlappedView2 = CreateOverlappedView (3, 8, 10);
 
@@ -214,12 +203,25 @@ public class Navigation : Scenario
 
         testFrame.SetFocus ();
         Application.Run (app);
+        Application.Iteration -= OnApplicationIteration;
         // timer.Close ();
         app.Dispose ();
         Application.Shutdown ();
 
         return;
 
+        void OnApplicationIteration (object sender, IterationEventArgs args)
+        {
+            if (progressBar.Fraction == 1.0)
+            {
+                progressBar.Fraction = 0;
+            }
+
+            progressBar.Fraction += 0.01f;
+
+            Application.Invoke (() => { });
+        }
+
         void ColorPicker_ColorChanged (object sender, ResultEventArgs<Color> e)
         {
             testFrame.SetScheme (testFrame.GetScheme () with { Normal = new (testFrame.GetAttributeForRole (VisualRole.Normal).Foreground, e.Result) });

+ 42 - 105
Examples/UICatalog/Scenarios/Notepad.cs

@@ -1,6 +1,4 @@
-using System.IO;
-using System.Linq;
-
+#nullable enable
 namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("Notepad", "Multi-tab text editor using the TabView control.")]
@@ -9,10 +7,10 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("TextView")]
 public class Notepad : Scenario
 {
-    private TabView _focusedTabView;
-    public Shortcut LenShortcut { get; private set; }
+    private TabView? _focusedTabView;
     private int _numNewTabs = 1;
-    private TabView _tabView;
+    private TabView? _tabView;
+    public Shortcut? LenShortcut { get; private set; }
 
     public override void Main ()
     {
@@ -59,22 +57,23 @@ public class Notepad : Scenario
         _tabView.Style.ShowBorder = true;
         _tabView.ApplyStyleChanges ();
 
-        // Start with only a single view but support splitting to show side by side
-        var split = new TileView (1) { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) };
-        split.Tiles.ElementAt (0).ContentView.Add (_tabView);
-        split.LineStyle = LineStyle.None;
+        _tabView.X = 0;
+        _tabView.Y = 1;
+        _tabView.Width = Dim.Fill ();
+        _tabView.Height = Dim.Fill (1);
 
-        top.Add (split);
+        top.Add (_tabView);
         LenShortcut = new (Key.Empty, "Len: ", null);
 
-        var statusBar = new StatusBar (new [] {
-                                           new (Application.QuitKey, $"Quit", Quit),
-                                           new Shortcut(Key.F2, "Open", Open),
-                                           new Shortcut(Key.F1, "New", New),
+        var statusBar = new StatusBar (
+                                       [
+                                           new (Application.QuitKey, "Quit", Quit),
+                                           new (Key.F2, "Open", Open),
+                                           new (Key.F1, "New", New),
                                            new (Key.F3, "Save", Save),
                                            new (Key.F6, "Close", Close),
                                            LenShortcut
-                                       }
+                                       ]
                                       )
         {
             AlignmentModes = AlignmentModes.IgnoreFirstOrLast
@@ -97,7 +96,7 @@ public class Notepad : Scenario
         Application.Shutdown ();
     }
 
-    public void Save () { Save (_focusedTabView, _focusedTabView.SelectedTab); }
+    public void Save () { Save (_focusedTabView!, _focusedTabView!.SelectedTab!); }
 
     public void Save (TabView tabViewToSave, Tab tabToSave)
     {
@@ -119,7 +118,7 @@ public class Notepad : Scenario
 
     public bool SaveAs ()
     {
-        var tab = _focusedTabView.SelectedTab as OpenedFile;
+        var tab = _focusedTabView!.SelectedTab as OpenedFile;
 
         if (tab == null)
         {
@@ -152,7 +151,7 @@ public class Notepad : Scenario
         return true;
     }
 
-    private void Close () { Close (_focusedTabView, _focusedTabView.SelectedTab); }
+    private void Close () { Close (_focusedTabView!, _focusedTabView!.SelectedTab!); }
 
     private void Close (TabView tv, Tab tabToClose)
     {
@@ -196,41 +195,13 @@ public class Notepad : Scenario
 
         // close and dispose the tab
         tv.RemoveTab (tab);
-        tab.View.Dispose ();
+        tab.View?.Dispose ();
         _focusedTabView = tv;
 
+        // If last tab is closed, open a new one
         if (tv.Tabs.Count == 0)
         {
-            var split = (TileView)tv.SuperView.SuperView;
-
-            // if it is the last TabView on screen don't drop it or we will
-            // be unable to open new docs!
-            if (split.IsRootTileView () && split.Tiles.Count == 1)
-            {
-                return;
-            }
-
-            int tileIndex = split.IndexOf (tv);
-            split.RemoveTile (tileIndex);
-
-            if (split.Tiles.Count == 0)
-            {
-                TileView parent = split.GetParentTileView ();
-
-                if (parent == null)
-                {
-                    return;
-                }
-
-                int idx = parent.IndexOf (split);
-
-                if (idx == -1)
-                {
-                    return;
-                }
-
-                parent.RemoveTile (idx);
-            }
+            New ();
         }
     }
 
@@ -245,7 +216,7 @@ public class Notepad : Scenario
         return tv;
     }
 
-    private void New () { Open (null, $"new {_numNewTabs++}"); }
+    private void New () { Open (null!, $"new {_numNewTabs++}"); }
 
     private void Open ()
     {
@@ -274,57 +245,27 @@ public class Notepad : Scenario
 
     /// <summary>Creates a new tab with initial text</summary>
     /// <param name="fileInfo">File that was read or null if a new blank document</param>
+    /// <param name="tabName"></param>
     private void Open (FileInfo fileInfo, string tabName)
     {
         var tab = new OpenedFile (this) { DisplayText = tabName, File = fileInfo };
         tab.View = tab.CreateTextView (fileInfo);
         tab.SavedText = tab.View.Text;
-        tab.RegisterTextViewEvents (_focusedTabView);
+        tab.RegisterTextViewEvents (_focusedTabView!);
 
-        _focusedTabView.AddTab (tab, true);
+        _focusedTabView!.AddTab (tab, true);
     }
 
     private void Quit () { Application.RequestStop (); }
 
-    private void Split (int offset, Orientation orientation, TabView sender, OpenedFile tab)
+    private void TabView_SelectedTabChanged (object? sender, TabChangedEventArgs e)
     {
-        var split = (TileView)sender.SuperView.SuperView;
-        int tileIndex = split.IndexOf (sender);
-
-        if (tileIndex == -1)
-        {
-            return;
-        }
-
-        if (orientation != split.Orientation)
-        {
-            split.TrySplitTile (tileIndex, 1, out split);
-            split.Orientation = orientation;
-            tileIndex = 0;
-        }
-
-        Tile newTile = split.InsertTile (tileIndex + offset);
-        TabView newTabView = CreateNewTabView ();
-        tab.CloneTo (newTabView);
-        newTile.ContentView.Add (newTabView);
-
-        newTabView.FocusDeepest (NavigationDirection.Forward, null);
-        newTabView.AdvanceFocus (NavigationDirection.Forward, null);
-    }
-
-    private void SplitDown (TabView sender, OpenedFile tab) { Split (1, Orientation.Horizontal, sender, tab); }
-    private void SplitLeft (TabView sender, OpenedFile tab) { Split (0, Orientation.Vertical, sender, tab); }
-    private void SplitRight (TabView sender, OpenedFile tab) { Split (1, Orientation.Vertical, sender, tab); }
-    private void SplitUp (TabView sender, OpenedFile tab) { Split (0, Orientation.Horizontal, sender, tab); }
-
-    private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)
-    {
-        LenShortcut.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
+        LenShortcut!.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
 
         e.NewTab?.View?.SetFocus ();
     }
 
-    private void TabView_TabClicked (object sender, TabMouseEventArgs e)
+    private void TabView_TabClicked (object? sender, TabMouseEventArgs e)
     {
         // we are only interested in right clicks
         if (!e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked))
@@ -340,18 +281,13 @@ public class Notepad : Scenario
         }
         else
         {
-            var tv = (TabView)sender;
+            var tv = (TabView)sender!;
             var t = (OpenedFile)e.Tab;
 
             items =
             [
-                new MenuItemv2 ("Save", "", () => Save (_focusedTabView, e.Tab)),
-                new MenuItemv2 ("Close", "", () => Close (tv, e.Tab)),
-                new Line (),
-                new MenuItemv2 ("Split Up", "", () => SplitUp (tv, t)),
-                new MenuItemv2 ("Split Down", "", () => SplitDown (tv, t)),
-                new MenuItemv2 ("Split Right", "", () => SplitRight (tv, t)),
-                new MenuItemv2 ("Split Left", "", () => SplitLeft (tv, t))
+                new MenuItemv2 ("Save", "", () => Save (_focusedTabView!, e.Tab)),
+                new MenuItemv2 ("Close", "", () => Close (tv, e.Tab))
             ];
 
             PopoverMenu? contextMenu = new (items);
@@ -367,12 +303,12 @@ public class Notepad : Scenario
 
     private class OpenedFile (Notepad notepad) : Tab
     {
-        private Notepad _notepad = notepad;
+        private readonly Notepad _notepad = notepad;
 
         public OpenedFile CloneTo (TabView other)
         {
             var newTab = new OpenedFile (_notepad) { DisplayText = base.Text, File = File };
-            newTab.View = newTab.CreateTextView (newTab.File);
+            newTab.View = newTab.CreateTextView (newTab.File!);
             newTab.SavedText = newTab.View.Text;
             newTab.RegisterTextViewEvents (other);
             other.AddTab (newTab, true);
@@ -380,11 +316,11 @@ public class Notepad : Scenario
             return newTab;
         }
 
-        public View CreateTextView (FileInfo file)
+        public View CreateTextView (FileInfo? file)
         {
             var initialText = string.Empty;
 
-            if (file != null && file.Exists)
+            if (file is { Exists: true })
             {
                 initialText = System.IO.File.ReadAllText (file.FullName);
             }
@@ -400,11 +336,11 @@ public class Notepad : Scenario
             };
         }
 
-        public FileInfo File { get; set; }
+        public FileInfo? File { get; set; }
 
         public void RegisterTextViewEvents (TabView parent)
         {
-            var textView = (TextView)View;
+            var textView = (TextView)View!;
 
             // when user makes changes rename tab to indicate unsaved
             textView.ContentsChanged += (s, k) =>
@@ -426,19 +362,20 @@ public class Notepad : Scenario
                                                     DisplayText = Text.TrimEnd ('*');
                                                 }
                                             }
-                                            _notepad.LenShortcut.Title = $"Len:{textView.Text.Length}";
+
+                                            _notepad.LenShortcut!.Title = $"Len:{textView.Text.Length}";
                                         };
         }
 
         /// <summary>The text of the tab the last time it was saved</summary>
         /// <value></value>
-        public string SavedText { get; set; }
+        public string? SavedText { get; set; }
 
-        public bool UnsavedChanges => !string.Equals (SavedText, View.Text);
+        public bool UnsavedChanges => !string.Equals (SavedText, View!.Text);
 
         internal void Save ()
         {
-            string newText = View.Text;
+            string newText = View!.Text;
 
             if (File is null || string.IsNullOrWhiteSpace (File.FullName))
             {

+ 2 - 2
Examples/UICatalog/Scenarios/NumericUpDownDemo.cs

@@ -266,14 +266,14 @@ internal class NumericUpDownEditor<T> : View where T : notnull
 
             void NumericUpDownOnIncrementChanged (object? o, EventArgs<T> eventArgs)
             {
-                _increment.Text = _numericUpDown.Increment.ToString ();
+                _increment.Text = _numericUpDown!.Increment?.ToString ();
             }
 
             Add (_numericUpDown);
 
             _value.Text = _numericUpDown.Text;
             _format.Text = _numericUpDown.Format;
-            _increment.Text = _numericUpDown.Increment.ToString ();
+            _increment.Text = _numericUpDown!.Increment?.ToString ();
         }
     }
 

+ 145 - 139
Examples/UICatalog/Scenarios/PosAlignDemo.cs

@@ -1,14 +1,10 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
 namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("Pos.Align", "Demonstrates Pos.Align")]
 [ScenarioCategory ("Layout")]
 public sealed class PosAlignDemo : Scenario
 {
-    private readonly Aligner _horizAligner = new () { AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems};
+    private readonly Aligner _horizAligner = new () { AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems };
     private int _leftMargin;
     private readonly Aligner _vertAligner = new () { AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems };
     private int _topMargin;
@@ -40,43 +36,42 @@ public sealed class PosAlignDemo : Scenario
 
     private void SetupControls (Window appWindow, Dimension dimension, Schemes scheme)
     {
-        RadioGroup alignRadioGroup = new ()
+        OptionSelector<Alignment> alignOptionSelector = new ()
         {
-            RadioLabels = Enum.GetNames<Alignment> (),
-            SchemeName = SchemeManager.SchemesToSchemeName (scheme),
+            SchemeName = SchemeManager.SchemesToSchemeName (scheme)
         };
 
         if (dimension == Dimension.Width)
         {
-            alignRadioGroup.X = Pos.Align (_horizAligner.Alignment);
-            alignRadioGroup.Y = Pos.Center ();
+            alignOptionSelector.X = Pos.Align (_horizAligner.Alignment);
+            alignOptionSelector.Y = Pos.Center ();
         }
         else
         {
-            alignRadioGroup.X = Pos.Center ();
-            alignRadioGroup.Y = Pos.Align (_vertAligner.Alignment);
+            alignOptionSelector.X = Pos.Center ();
+            alignOptionSelector.Y = Pos.Align (_vertAligner.Alignment);
         }
 
-        alignRadioGroup.SelectedItemChanged += (s, e) =>
-                                               {
-                                                   if (dimension == Dimension.Width)
-                                                   {
-                                                       _horizAligner.Alignment =
-                                                           (Alignment)Enum.Parse (
-                                                                                  typeof (Alignment),
-                                                                                  alignRadioGroup.RadioLabels [alignRadioGroup.SelectedItem]);
-                                                       UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
-                                                   }
-                                                   else
-                                                   {
-                                                       _vertAligner.Alignment =
-                                                           (Alignment)Enum.Parse (
-                                                                                  typeof (Alignment),
-                                                                                  alignRadioGroup.RadioLabels [alignRadioGroup.SelectedItem]);
-                                                       UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
-                                                   }
-                                               };
-        appWindow.Add (alignRadioGroup);
+        alignOptionSelector.ValueChanged += (s, e) =>
+                                            {
+                                                if (alignOptionSelector.Value is null)
+                                                {
+                                                    return;
+                                                }
+
+                                                if (dimension == Dimension.Width)
+                                                {
+                                                    _horizAligner.Alignment = alignOptionSelector.Value.Value;
+
+                                                    UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
+                                                }
+                                                else
+                                                {
+                                                    _vertAligner.Alignment = alignOptionSelector.Value.Value;
+                                                    UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
+                                                }
+                                            };
+        appWindow.Add (alignOptionSelector);
 
         CheckBox endToStartCheckBox = new ()
         {
@@ -88,32 +83,32 @@ public sealed class PosAlignDemo : Scenario
         {
             endToStartCheckBox.CheckedState = _horizAligner.AlignmentModes.HasFlag (AlignmentModes.EndToStart) ? CheckState.Checked : CheckState.UnChecked;
             endToStartCheckBox.X = Pos.Align (_horizAligner.Alignment);
-            endToStartCheckBox.Y = Pos.Top (alignRadioGroup);
+            endToStartCheckBox.Y = Pos.Top (alignOptionSelector);
         }
         else
         {
             endToStartCheckBox.CheckedState = _vertAligner.AlignmentModes.HasFlag (AlignmentModes.EndToStart) ? CheckState.Checked : CheckState.UnChecked;
-            endToStartCheckBox.X = Pos.Left (alignRadioGroup);
+            endToStartCheckBox.X = Pos.Left (alignOptionSelector);
             endToStartCheckBox.Y = Pos.Align (_vertAligner.Alignment);
         }
 
         endToStartCheckBox.CheckedStateChanging += (s, e) =>
-                                      {
-                                          if (dimension == Dimension.Width)
-                                          {
-                                              _horizAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                                                 ? _horizAligner.AlignmentModes | AlignmentModes.EndToStart
-                                                                                 : _horizAligner.AlignmentModes & ~AlignmentModes.EndToStart;
-                                              UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
-                                          }
-                                          else
-                                          {
-                                              _vertAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                                                ? _vertAligner.AlignmentModes | AlignmentModes.EndToStart
-                                                                                : _vertAligner.AlignmentModes & ~AlignmentModes.EndToStart;
-                                              UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
-                                          }
-                                      };
+                                                   {
+                                                       if (dimension == Dimension.Width)
+                                                       {
+                                                           _horizAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                              ? _horizAligner.AlignmentModes | AlignmentModes.EndToStart
+                                                                                              : _horizAligner.AlignmentModes & ~AlignmentModes.EndToStart;
+                                                           UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
+                                                       }
+                                                       else
+                                                       {
+                                                           _vertAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                             ? _vertAligner.AlignmentModes | AlignmentModes.EndToStart
+                                                                                             : _vertAligner.AlignmentModes & ~AlignmentModes.EndToStart;
+                                                           UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
+                                                       }
+                                                   };
         appWindow.Add (endToStartCheckBox);
 
         CheckBox ignoreFirstOrLast = new ()
@@ -124,34 +119,35 @@ public sealed class PosAlignDemo : Scenario
 
         if (dimension == Dimension.Width)
         {
-            ignoreFirstOrLast.CheckedState = _horizAligner.AlignmentModes.HasFlag (AlignmentModes.IgnoreFirstOrLast) ? CheckState.Checked : CheckState.UnChecked;
+            ignoreFirstOrLast.CheckedState =
+                _horizAligner.AlignmentModes.HasFlag (AlignmentModes.IgnoreFirstOrLast) ? CheckState.Checked : CheckState.UnChecked;
             ignoreFirstOrLast.X = Pos.Align (_horizAligner.Alignment);
-            ignoreFirstOrLast.Y = Pos.Top (alignRadioGroup);
+            ignoreFirstOrLast.Y = Pos.Top (alignOptionSelector);
         }
         else
         {
             ignoreFirstOrLast.CheckedState = _vertAligner.AlignmentModes.HasFlag (AlignmentModes.IgnoreFirstOrLast) ? CheckState.Checked : CheckState.UnChecked;
-            ignoreFirstOrLast.X = Pos.Left (alignRadioGroup);
+            ignoreFirstOrLast.X = Pos.Left (alignOptionSelector);
             ignoreFirstOrLast.Y = Pos.Align (_vertAligner.Alignment);
         }
 
         ignoreFirstOrLast.CheckedStateChanging += (s, e) =>
-                                     {
-                                         if (dimension == Dimension.Width)
-                                         {
-                                             _horizAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                     ? _horizAligner.AlignmentModes | AlignmentModes.IgnoreFirstOrLast
-                                                     : _horizAligner.AlignmentModes & ~AlignmentModes.IgnoreFirstOrLast;
-                                             UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
-                                         }
-                                         else
-                                         {
-                                             _vertAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                                               ? _vertAligner.AlignmentModes | AlignmentModes.IgnoreFirstOrLast
-                                                                               : _vertAligner.AlignmentModes & ~AlignmentModes.IgnoreFirstOrLast;
-                                             UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
-                                         }
-                                     };
+                                                  {
+                                                      if (dimension == Dimension.Width)
+                                                      {
+                                                          _horizAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                             ? _horizAligner.AlignmentModes | AlignmentModes.IgnoreFirstOrLast
+                                                                                             : _horizAligner.AlignmentModes & ~AlignmentModes.IgnoreFirstOrLast;
+                                                          UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
+                                                      }
+                                                      else
+                                                      {
+                                                          _vertAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                            ? _vertAligner.AlignmentModes | AlignmentModes.IgnoreFirstOrLast
+                                                                                            : _vertAligner.AlignmentModes & ~AlignmentModes.IgnoreFirstOrLast;
+                                                          UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
+                                                      }
+                                                  };
         appWindow.Add (ignoreFirstOrLast);
 
         CheckBox addSpacesBetweenItems = new ()
@@ -162,34 +158,40 @@ public sealed class PosAlignDemo : Scenario
 
         if (dimension == Dimension.Width)
         {
-            addSpacesBetweenItems.CheckedState = _horizAligner.AlignmentModes.HasFlag (AlignmentModes.AddSpaceBetweenItems) ? CheckState.Checked : CheckState.UnChecked;
+            addSpacesBetweenItems.CheckedState =
+                _horizAligner.AlignmentModes.HasFlag (AlignmentModes.AddSpaceBetweenItems) ? CheckState.Checked : CheckState.UnChecked;
             addSpacesBetweenItems.X = Pos.Align (_horizAligner.Alignment);
-            addSpacesBetweenItems.Y = Pos.Top (alignRadioGroup);
+            addSpacesBetweenItems.Y = Pos.Top (alignOptionSelector);
         }
         else
         {
-            addSpacesBetweenItems.CheckedState = _vertAligner.AlignmentModes.HasFlag (AlignmentModes.AddSpaceBetweenItems) ? CheckState.Checked : CheckState.UnChecked;
-            addSpacesBetweenItems.X = Pos.Left (alignRadioGroup);
+            addSpacesBetweenItems.CheckedState =
+                _vertAligner.AlignmentModes.HasFlag (AlignmentModes.AddSpaceBetweenItems) ? CheckState.Checked : CheckState.UnChecked;
+            addSpacesBetweenItems.X = Pos.Left (alignOptionSelector);
             addSpacesBetweenItems.Y = Pos.Align (_vertAligner.Alignment);
         }
 
         addSpacesBetweenItems.CheckedStateChanging += (s, e) =>
-                                         {
-                                             if (dimension == Dimension.Width)
-                                             {
-                                                 _horizAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                                                    ? _horizAligner.AlignmentModes | AlignmentModes.AddSpaceBetweenItems
-                                                                                    : _horizAligner.AlignmentModes & ~AlignmentModes.AddSpaceBetweenItems;
-                                                 UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
-                                             }
-                                             else
-                                             {
-                                                 _vertAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                                                   ? _vertAligner.AlignmentModes | AlignmentModes.AddSpaceBetweenItems
-                                                                                   : _vertAligner.AlignmentModes & ~AlignmentModes.AddSpaceBetweenItems;
-                                                 UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
-                                             }
-                                         };
+                                                      {
+                                                          if (dimension == Dimension.Width)
+                                                          {
+                                                              _horizAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                                 ? _horizAligner.AlignmentModes
+                                                                                                   | AlignmentModes.AddSpaceBetweenItems
+                                                                                                 : _horizAligner.AlignmentModes
+                                                                                                   & ~AlignmentModes.AddSpaceBetweenItems;
+                                                              UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
+                                                          }
+                                                          else
+                                                          {
+                                                              _vertAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                                ? _vertAligner.AlignmentModes
+                                                                                                  | AlignmentModes.AddSpaceBetweenItems
+                                                                                                : _vertAligner.AlignmentModes
+                                                                                                  & ~AlignmentModes.AddSpaceBetweenItems;
+                                                              UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
+                                                          }
+                                                      };
 
         appWindow.Add (addSpacesBetweenItems);
 
@@ -202,7 +204,7 @@ public sealed class PosAlignDemo : Scenario
         if (dimension == Dimension.Width)
         {
             margin.X = Pos.Align (_horizAligner.Alignment);
-            margin.Y = Pos.Top (alignRadioGroup);
+            margin.Y = Pos.Top (alignOptionSelector);
         }
         else
         {
@@ -211,31 +213,31 @@ public sealed class PosAlignDemo : Scenario
         }
 
         margin.CheckedStateChanging += (s, e) =>
-                          {
-                              if (dimension == Dimension.Width)
-                              {
-                                  _leftMargin = e.Result == CheckState.Checked ? 1 : 0;
-                                  UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
-                              }
-                              else
-                              {
-                                  _topMargin = e.Result == CheckState.Checked ? 1 : 0;
-                                  UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
-                              }
-                          };
+                                       {
+                                           if (dimension == Dimension.Width)
+                                           {
+                                               _leftMargin = e.Result == CheckState.Checked ? 1 : 0;
+                                               UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
+                                           }
+                                           else
+                                           {
+                                               _topMargin = e.Result == CheckState.Checked ? 1 : 0;
+                                               UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
+                                           }
+                                       };
         appWindow.Add (margin);
 
         List<Button> addedViews =
         [
             new ()
             {
-                X = dimension == Dimension.Width ? Pos.Align (_horizAligner.Alignment) : Pos.Left (alignRadioGroup),
-                Y = dimension == Dimension.Width ? Pos.Top (alignRadioGroup) : Pos.Align (_vertAligner.Alignment),
+                X = dimension == Dimension.Width ? Pos.Align (_horizAligner.Alignment) : Pos.Left (alignOptionSelector),
+                Y = dimension == Dimension.Width ? Pos.Top (alignOptionSelector) : Pos.Align (_vertAligner.Alignment),
                 Text = NumberToWords.Convert (0)
             }
         ];
 
-        NumericUpDown<int> addedViewsUpDown = new()
+        NumericUpDown<int> addedViewsUpDown = new ()
         {
             Width = 9,
             Title = "Added",
@@ -247,14 +249,14 @@ public sealed class PosAlignDemo : Scenario
         if (dimension == Dimension.Width)
         {
             addedViewsUpDown.X = Pos.Align (_horizAligner.Alignment);
-            addedViewsUpDown.Y = Pos.Top (alignRadioGroup);
-            addedViewsUpDown.Border.Thickness = new (0, 1, 0, 0);
+            addedViewsUpDown.Y = Pos.Top (alignOptionSelector);
+            addedViewsUpDown.Border!.Thickness = new (0, 1, 0, 0);
         }
         else
         {
-            addedViewsUpDown.X = Pos.Left (alignRadioGroup);
+            addedViewsUpDown.X = Pos.Left (alignOptionSelector);
             addedViewsUpDown.Y = Pos.Align (_vertAligner.Alignment);
-            addedViewsUpDown.Border.Thickness = new (1, 0, 0, 0);
+            addedViewsUpDown.Border!.Thickness = new (1, 0, 0, 0);
         }
 
         addedViewsUpDown.ValueChanging += (s, e) =>
@@ -286,8 +288,10 @@ public sealed class PosAlignDemo : Scenario
                                                   {
                                                       var button = new Button
                                                       {
-                                                          X = dimension == Dimension.Width ? Pos.Align (_horizAligner.Alignment) : Pos.Left (alignRadioGroup),
-                                                          Y = dimension == Dimension.Width ? Pos.Top (alignRadioGroup) : Pos.Align (_vertAligner.Alignment),
+                                                          X = dimension == Dimension.Width
+                                                                  ? Pos.Align (_horizAligner.Alignment)
+                                                                  : Pos.Left (alignOptionSelector),
+                                                          Y = dimension == Dimension.Width ? Pos.Top (alignOptionSelector) : Pos.Align (_vertAligner.Alignment),
                                                           Text = NumberToWords.Convert (i + 1)
                                                       };
                                                       appWindow.Add (button);
@@ -319,7 +323,7 @@ public sealed class PosAlignDemo : Scenario
                                         aligner.Alignment,
                                         aligner.AlignmentModes,
                                         posAlign!.GroupId);
-                    view.Margin.Thickness = new (_leftMargin, view.Margin.Thickness.Top, view.Margin.Thickness.Right, view.Margin.Thickness.Bottom);
+                    view.Margin!.Thickness = new (_leftMargin, view.Margin!.Thickness.Top, view.Margin!.Thickness.Right, view.Margin!.Thickness.Bottom);
                 }
                 else
                 {
@@ -330,7 +334,7 @@ public sealed class PosAlignDemo : Scenario
                                         aligner.AlignmentModes,
                                         posAlign!.GroupId);
 
-                    view.Margin.Thickness = new (view.Margin.Thickness.Left, _topMargin, view.Margin.Thickness.Right, view.Margin.Thickness.Bottom);
+                    view.Margin!.Thickness = new (view.Margin!.Thickness.Left, _topMargin, view.Margin!.Thickness.Right, view.Margin!.Thickness.Bottom);
                 }
             }
         }
@@ -351,46 +355,48 @@ public sealed class PosAlignDemo : Scenario
             Width = Dim.Percent (40),
             Height = Dim.Percent (40)
         };
-        container.Padding.Thickness = new (8, 1, 0, 0);
+        container.Padding!.Thickness = new (8, 1, 0, 0);
         container.Padding.SchemeName = "Error";
 
         Aligner widthAligner = new () { AlignmentModes = AlignmentModes.StartToEnd };
 
-        RadioGroup widthAlignRadioGroup = new ()
+        OptionSelector widthAlignOptionSelector = new ()
         {
-            RadioLabels = Enum.GetNames<Alignment> (),
+            Labels = Enum.GetNames<Alignment> (),
             Orientation = Orientation.Horizontal,
             X = Pos.Center ()
         };
-        container.Padding.Add (widthAlignRadioGroup);
-
-        widthAlignRadioGroup.SelectedItemChanged += (sender, e) =>
-                                                    {
-                                                        widthAligner.Alignment =
-                                                            (Alignment)Enum.Parse (
-                                                                                   typeof (Alignment),
-                                                                                   widthAlignRadioGroup.RadioLabels [widthAlignRadioGroup.SelectedItem]);
-                                                        UpdatePosAlignObjects (container, Dimension.Width, widthAligner);
-                                                    };
+        container.Padding.Add (widthAlignOptionSelector);
+
+        widthAlignOptionSelector.ValueChanged += (_, _) =>
+                                                        {
+                                                            widthAligner.Alignment =
+                                                                (Alignment)Enum.Parse (
+                                                                                       typeof (Alignment),
+                                                                                       widthAlignOptionSelector.Labels [widthAlignOptionSelector
+                                                                                           .Value!.Value]);
+                                                            UpdatePosAlignObjects (container, Dimension.Width, widthAligner);
+                                                        };
 
         Aligner heightAligner = new () { AlignmentModes = AlignmentModes.StartToEnd };
 
-        RadioGroup heightAlignRadioGroup = new ()
+        OptionSelector heightAlignOptionSelector = new ()
         {
-            RadioLabels = Enum.GetNames<Alignment> (),
+            Labels = Enum.GetNames<Alignment> (),
             Orientation = Orientation.Vertical,
             Y = Pos.Center ()
         };
-        container.Padding.Add (heightAlignRadioGroup);
-
-        heightAlignRadioGroup.SelectedItemChanged += (sender, e) =>
-                                                     {
-                                                         heightAligner.Alignment =
-                                                             (Alignment)Enum.Parse (
-                                                                                    typeof (Alignment),
-                                                                                    heightAlignRadioGroup.RadioLabels [heightAlignRadioGroup.SelectedItem]);
-                                                         UpdatePosAlignObjects (container, Dimension.Height, heightAligner);
-                                                     };
+        container.Padding.Add (heightAlignOptionSelector);
+
+        heightAlignOptionSelector.ValueChanged += (sender, e) =>
+                                                         {
+                                                             heightAligner.Alignment =
+                                                                 (Alignment)Enum.Parse (
+                                                                                        typeof (Alignment),
+                                                                                        heightAlignOptionSelector.Labels [heightAlignOptionSelector
+                                                                                            .Value!.Value]);
+                                                             UpdatePosAlignObjects (container, Dimension.Height, heightAligner);
+                                                         };
 
         for (var i = 0; i < 9; i++)
 
@@ -401,7 +407,7 @@ public sealed class PosAlignDemo : Scenario
                 Text = $"{i}",
                 BorderStyle = LineStyle.Dashed,
                 Height = Dim.Auto (),
-                Width = Dim.Auto() + 2
+                Width = Dim.Auto () + 2
             };
 
             v.X = Pos.Align (widthAligner.Alignment, widthAligner.AlignmentModes, i / 3);

+ 17 - 13
Examples/UICatalog/Scenarios/ProgressBarStyles.cs

@@ -132,15 +132,15 @@ public class ProgressBarStyles : Scenario
         List<ProgressBarFormat> pbFormatEnum =
             Enum.GetValues (typeof (ProgressBarFormat)).Cast<ProgressBarFormat> ().ToList ();
 
-        var rbPBFormat = new RadioGroup
+        OptionSelector<ProgressBarFormat> osPbFormat = new ()
         {
             BorderStyle = LineStyle.Single,
             Title = "ProgressBarFormat",
             X = Pos.Center (),
             Y = Pos.Align (Alignment.Start),
-            RadioLabels = pbFormatEnum.Select (e => e.ToString ()).ToArray ()
+            AssignHotKeys = true
         };
-        container.Add (rbPBFormat);
+        container.Add (osPbFormat);
 
         var button = new Button
         {
@@ -161,7 +161,7 @@ public class ProgressBarStyles : Scenario
         };
         container.Add (blocksPB);
 
-        rbPBFormat.SelectedItem = (int)blocksPB.ProgressBarFormat;
+        osPbFormat.Value = blocksPB.ProgressBarFormat;
 
         var continuousPB = new ProgressBar
         {
@@ -198,7 +198,6 @@ public class ProgressBarStyles : Scenario
                                                                       button.Enabled = true;
                                                                   }
 
-                                                                  Application.Wakeup ();
                                                               },
                                                               null,
                                                               0,
@@ -257,13 +256,19 @@ public class ProgressBarStyles : Scenario
                                       };
 
 
-        rbPBFormat.SelectedItemChanged += (s, e) =>
-                                          {
-                                              blocksPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem;
-                                              continuousPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem;
-                                              marqueesBlocksPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem;
-                                              marqueesContinuousPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem;
-                                          };
+        osPbFormat.ValueChanged += (s, e) =>
+                                   {
+                                       if (e.Value is null)
+                                       {
+                                           return;
+                                       }
+
+                                       blocksPB.ProgressBarFormat = e.Value.Value;
+                                       continuousPB.ProgressBarFormat = e.Value.Value;
+                                       marqueesBlocksPB.ProgressBarFormat = e.Value.Value;
+                                       marqueesContinuousPB.ProgressBarFormat = e.Value.Value;
+
+                                   };
 
         ckbBidirectional.CheckedStateChanging += (s, e) =>
                                    {
@@ -282,7 +287,6 @@ public class ProgressBarStyles : Scenario
                                      marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString ();
                                      marqueesBlocksPB.Pulse ();
                                      marqueesContinuousPB.Pulse ();
-                                     Application.Wakeup ();
                                  },
                                  null,
                                  0,

+ 18 - 45
Examples/UICatalog/Scenarios/RegionScenario.cs

@@ -1,7 +1,4 @@
 #nullable enable
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using System.Text;
 using UICatalog;
 using UICatalog.Scenarios;
@@ -40,7 +37,7 @@ public class RegionScenario : Scenario
 
         tools.SetStyle += b =>
                           {
-                              _drawStyle = (RegionDrawStyles)b;
+                              _drawStyle = b;
                               app.SetNeedsDraw ();
                           };
 
@@ -169,8 +166,8 @@ public class ToolsView : Window
 {
     //private Button _addLayerBtn;
     private readonly AttributeView _attributeView = new ();
-    private RadioGroup? _stylePicker;
-    private RegionOpSelector? _regionOpSelector;
+    private OptionSelector<RegionDrawStyles>? _stylePicker;
+    private OptionSelector<RegionOp>? _regionOpSelector;
 
     public Attribute? CurrentAttribute
     {
@@ -197,22 +194,30 @@ public class ToolsView : Window
         _stylePicker = new ()
         {
             Width = Dim.Fill (),
-            X = 0, Y = Pos.Bottom (_attributeView) + 1, RadioLabels = Enum.GetNames<RegionDrawStyles> ().Select (n => n = "_" + n).ToArray ()
+            X = 0, Y = Pos.Bottom (_attributeView) + 1,
+            AssignHotKeys = true
         };
         _stylePicker.BorderStyle = LineStyle.Single;
         _stylePicker.Border!.Thickness = new (0, 1, 0, 0);
         _stylePicker.Title = "Draw Style";
 
-        _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem!); };
-        _stylePicker.SelectedItem = (int)RegionDrawStyles.FillOnly;
+        _stylePicker.ValueChanged += (s, a) => { SetStyle?.Invoke ((RegionDrawStyles)a.Value!); };
+        _stylePicker.Value = RegionDrawStyles.FillOnly;
 
         _regionOpSelector = new ()
         {
             X = 0,
-            Y = Pos.Bottom (_stylePicker) + 1
+            Y = Pos.Bottom (_stylePicker) + 1,
+            AssignHotKeys = true
         };
-        _regionOpSelector.SelectedItemChanged += (s, a) => { RegionOpChanged?.Invoke (this, a); };
-        _regionOpSelector.SelectedItem = RegionOp.MinimalUnion;
+        _regionOpSelector.ValueChanged += (s, a) =>
+                                          {
+                                              if (a.Value is { })
+                                              {
+                                                  RegionOpChanged?.Invoke (this, (RegionOp)a.Value);
+                                              }
+                                          };
+        _regionOpSelector.Value = RegionOp.MinimalUnion;
 
         //_addLayerBtn = new () { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
 
@@ -222,39 +227,7 @@ public class ToolsView : Window
 
     public event EventHandler<Attribute?>? AttributeChanged;
     public event EventHandler<RegionOp>? RegionOpChanged;
-    public event Action<LineStyle>? SetStyle;
-}
-
-public class RegionOpSelector : View
-{
-    private readonly RadioGroup _radioGroup;
-
-    public RegionOpSelector ()
-    {
-        Width = Dim.Auto ();
-        Height = Dim.Auto ();
-
-        BorderStyle = LineStyle.Single;
-        Border!.Thickness = new (0, 1, 0, 0);
-        Title = "RegionOp";
-
-        _radioGroup = new ()
-        {
-            X = 0,
-            Y = 0,
-            RadioLabels = Enum.GetNames<RegionOp> ().Select (n => n = "_" + n).ToArray ()
-        };
-        _radioGroup.SelectedItemChanged += (s, a) => { SelectedItemChanged?.Invoke (this, (RegionOp)a.SelectedItem!); };
-        Add (_radioGroup);
-    }
-
-    public event EventHandler<RegionOp>? SelectedItemChanged;
-
-    public RegionOp SelectedItem
-    {
-        get => (RegionOp)_radioGroup.SelectedItem;
-        set => _radioGroup.SelectedItem = (int)value;
-    }
+    public event Action<RegionDrawStyles>? SetStyle;
 }
 
 public class AttributeView : View

+ 5 - 9
Examples/UICatalog/Scenarios/ScrollBarDemo.cs

@@ -118,23 +118,19 @@ public class ScrollBarDemo : Scenario
         };
         demoFrame.Add (lblOrientationLabel);
 
-        var rgOrientation = new RadioGroup
+        OptionSelector<Orientation> osOrientation = new ()
         {
             X = Pos.Right (lblOrientationLabel) + 1,
             Y = Pos.Top (lblOrientationLabel),
-            RadioLabels = ["Vertical", "Horizontal"],
+            AssignHotKeys = true,
             Orientation = Orientation.Horizontal
         };
-        demoFrame.Add (rgOrientation);
+        demoFrame.Add (osOrientation);
 
-        rgOrientation.SelectedItemChanged += (s, e) =>
+        osOrientation.ValueChanged += (s, e) =>
                                              {
-                                                 if (e.SelectedItem == e.PreviousSelectedItem)
-                                                 {
-                                                     return;
-                                                 }
 
-                                                 if (rgOrientation.SelectedItem == 0)
+                                                 if (osOrientation.Value == Orientation.Horizontal)
                                                  {
                                                      scrollBar.Orientation = Orientation.Vertical;
                                                      scrollBar.X = Pos.AnchorEnd () - 5;

+ 19 - 15
Examples/UICatalog/Scenarios/Scrolling.cs

@@ -1,4 +1,8 @@
-namespace UICatalog.Scenarios;
+#nullable enable
+
+using System.Diagnostics;
+
+namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("Scrolling", "Content scrolling, IScrollBars, etc...")]
 [ScenarioCategory ("Controls")]
@@ -6,6 +10,8 @@
 [ScenarioCategory ("Tests")]
 public class Scrolling : Scenario
 {
+    private object? _progressTimer = null;
+
     public override void Main ()
     {
         Application.Init ();
@@ -38,10 +44,6 @@ public class Scrolling : Scenario
 
         app.Add (demoView);
 
-        //// NOTE: This call to EnableScrollBar is technically not needed because the reference
-        //// NOTE: to demoView.HorizontalScrollBar below will cause it to be lazy created.
-        //// NOTE: The call included in this sample to for illustration purposes.
-        //demoView.EnableScrollBar (Orientation.Horizontal);
         var hCheckBox = new CheckBox
         {
             X = Pos.X (demoView),
@@ -52,10 +54,6 @@ public class Scrolling : Scenario
         app.Add (hCheckBox);
         hCheckBox.CheckedStateChanged += (sender, args) => { demoView.HorizontalScrollBar.Visible = args.Value == CheckState.Checked; };
 
-        //// NOTE: This call to EnableScrollBar is technically not needed because the reference
-        //// NOTE: to demoView.HorizontalScrollBar below will cause it to be lazy created.
-        //// NOTE: The call included in this sample to for illustration purposes.
-        //demoView.EnableScrollBar (Orientation.Vertical);
         var vCheckBox = new CheckBox
         {
             X = Pos.Right (hCheckBox) + 3,
@@ -96,8 +94,6 @@ public class Scrolling : Scenario
 
         app.Add (progress);
 
-        var pulsing = true;
-
         app.Initialized += AppOnInitialized;
         app.Unloaded += AppUnloaded;
 
@@ -108,17 +104,25 @@ public class Scrolling : Scenario
 
         return;
 
-        void AppOnInitialized (object sender, EventArgs e)
+        void AppOnInitialized (object? sender, EventArgs e)
         {
             bool TimerFn ()
             {
                 progress.Pulse ();
 
-                return pulsing;
+                return _progressTimer is { };
             }
 
-            Application.AddTimeout (TimeSpan.FromMilliseconds (200), TimerFn);
+            _progressTimer = Application.AddTimeout (TimeSpan.FromMilliseconds (200), TimerFn);
+        }
+
+        void AppUnloaded (object? sender, EventArgs args)
+        {
+            if (_progressTimer is { })
+            {
+                Application.RemoveTimeout (_progressTimer);
+                _progressTimer = null;
+            }
         }
-        void AppUnloaded (object sender, EventArgs args) { pulsing = false; }
     }
 }

+ 287 - 0
Examples/UICatalog/Scenarios/Selectors.cs

@@ -0,0 +1,287 @@
+#nullable enable
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Selectors", "Demonstrates OptionSelector and FlagSelector.")]
+[ScenarioCategory ("Controls")]
+public sealed class Selectors : Scenario
+{
+    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
+        };
+
+        FrameView? optionSelectorsFrame = null;
+        FrameView? flagSelectorsFrame = null;
+
+        OptionSelector<Orientation> orientationSelector = new ()
+        {
+            Orientation = Orientation.Horizontal,
+            BorderStyle = LineStyle.Dotted,
+            Title = "Selector Or_ientation",
+            Value = Orientation.Vertical
+        };
+        orientationSelector.ValueChanged += OrientationSelectorOnSelectedItemChanged;
+
+        FlagSelector<SelectorStyles> stylesSelector = new ()
+        {
+            X = Pos.Right (orientationSelector) + 1,
+            Orientation = Orientation.Horizontal,
+            BorderStyle = LineStyle.Dotted,
+            Title = "Selector St_yles",
+        };
+        stylesSelector.ValueChanged += StylesSelectorOnValueChanged;
+
+        NumericUpDown<int> horizontalSpace = new ()
+        {
+            X = 0,
+            Y = Pos.Bottom (orientationSelector),
+            Width = 11,
+            Title = "H_. Space",
+            Value = stylesSelector.HorizontalSpace,
+            BorderStyle = LineStyle.Dotted,
+        };
+        horizontalSpace.ValueChanging += HorizontalSpaceOnValueChanging;
+
+        CheckBox showBorderAndTitle = new ()
+        {
+            X = Pos.Right (horizontalSpace) + 1,
+            Y = Pos.Top (horizontalSpace),
+            Title = "Border _& Title",
+            CheckedState = CheckState.Checked,
+            BorderStyle = LineStyle.Dotted,
+        };
+        showBorderAndTitle.CheckedStateChanged += ShowBorderAndTitleOnCheckedStateChanged;
+
+        CheckBox canFocus = new ()
+        {
+            X = Pos.Right (showBorderAndTitle) + 1,
+            Y = Pos.Top (horizontalSpace),
+            Title = "_CanFocus",
+            CheckedState = CheckState.Checked,
+            BorderStyle = LineStyle.Dotted,
+        };
+        canFocus.CheckedStateChanged += CanFocusOnCheckedStateChanged;
+
+        optionSelectorsFrame = new ()
+        {
+            Y = Pos.Bottom (canFocus),
+            Width = Dim.Percent (50),
+            Height = Dim.Fill (),
+            Title = "O_ptionSelectors",
+            TabStop = TabBehavior.TabStop,
+            //InvertFocusAttribute = true
+        };
+        optionSelectorsFrame.ClearingViewport += (sender, args) =>
+                                                 {
+                                                     //               optionSelectorsFrame.SetAttributeForRole (optionSelectorsFrame.HasFocus ? VisualRole.Focus : VisualRole.Normal);
+                                                 };
+
+
+        Label label = new ()
+        {
+            Title = "Fo_ur Options:"
+        };
+
+        OptionSelector optionSelector = new ()
+        {
+            X = Pos.Right (label) + 1,
+            Title = "Fou_r Options",
+            BorderStyle = LineStyle.Dotted,
+            UsedHotKeys = { label.HotKey },
+            AssignHotKeys = true,
+            Labels = ["Option _1 (0)", "Option _2 (1)", "Option _3 (5) 你", "Option _Quattro (4) 你"],
+            Values = [0, 1, 5, 4],
+            Arrangement = ViewArrangement.Resizable,
+        };
+        optionSelectorsFrame.Add (label, optionSelector);
+
+        label = new ()
+        {
+            Y = Pos.Bottom (optionSelector),
+            Title = "<VisualRole_>:"
+        };
+
+        OptionSelector<VisualRole> optionSelectorT = new ()
+        {
+            X = Pos.Right (label) + 1,
+            Y = Pos.Bottom (optionSelector),
+            Title = "<Vi_sualRole>",
+            BorderStyle = LineStyle.Dotted,
+            UsedHotKeys = optionSelector.UsedHotKeys,
+            AssignHotKeys = true,
+        };
+
+        optionSelectorsFrame.Add (label, optionSelectorT);
+
+        flagSelectorsFrame = new ()
+        {
+            Y = Pos.Top (optionSelectorsFrame),
+            X = Pos.Right (optionSelectorsFrame),
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            Title = "_FlagSelectors",
+            TabStop = TabBehavior.TabStop
+        };
+
+        label = new ()
+        {
+            Title = "FlagSelector _(uint):"
+        };
+
+        FlagSelector flagSelector = new ()
+        {
+            X = Pos.Right (label) + 1,
+            UsedHotKeys = optionSelectorT.UsedHotKeys,
+            BorderStyle = LineStyle.Dotted,
+            Title = "FlagSe_lector (uint)",
+            AssignHotKeys = true,
+            Values =
+            [
+                0b_0001,
+                0b_0010,
+                0b_0100,
+                0b_1000,
+                0b_1111
+            ],
+            Labels =
+            [
+                "0x0001 One",
+                "0x0010 Two",
+                "0x0100 Quattro",
+                "0x1000 8",
+                "0x1111 Fifteen"
+            ]
+        };
+        flagSelectorsFrame.Add (label, flagSelector);
+
+        label = new ()
+        {
+            Y = Pos.Bottom (flagSelector),
+            Title = "_<ViewDiagnosticFlags>:"
+        };
+
+        FlagSelector<ViewDiagnosticFlags> flagSelectorT = new ()
+        {
+            X = Pos.Right (label) + 1,
+            BorderStyle = LineStyle.Dotted,
+            Title = "<ViewD_iagnosticFlags>",
+            Y = Pos.Bottom (flagSelector),
+            UsedHotKeys = flagSelector.UsedHotKeys,
+            AssignHotKeys = true,
+            Value = View.Diagnostics
+        };
+        flagSelectorsFrame.Add (label, flagSelectorT);
+        flagSelectorT.ValueChanged += (s, a) =>
+                                      {
+                                          View.Diagnostics = (ViewDiagnosticFlags)a.Value!;
+                                      };
+
+        appWindow.Add (orientationSelector, stylesSelector, horizontalSpace, showBorderAndTitle, canFocus, optionSelectorsFrame, flagSelectorsFrame);
+
+        // Run - Start the application.
+        Application.Run (appWindow);
+        appWindow.Dispose ();
+
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
+
+        return;
+
+        void OrientationSelectorOnSelectedItemChanged (object? sender, EventArgs<Orientation?> e)
+        {
+            if (sender is not OptionSelector<Orientation> s)
+            {
+                return;
+            }
+
+            List<SelectorBase> selectors = GetAllSelectors ();
+            foreach (SelectorBase selector in selectors)
+            {
+                selector.Orientation = s.Value!.Value;
+            }
+        }
+
+        void StylesSelectorOnValueChanged (object? sender, EventArgs<SelectorStyles?> e)
+        {
+            if (sender is not FlagSelector<SelectorStyles> s)
+            {
+                return;
+            }
+
+            List<SelectorBase> selectors = GetAllSelectors ();
+
+            foreach (SelectorBase selector in selectors)
+            {
+                selector.Styles = s.Value!.Value;
+            }
+        }
+
+        void HorizontalSpaceOnValueChanging (object? sender, CancelEventArgs<int> e)
+        {
+            if (sender is not NumericUpDown<int> upDown || e.NewValue < 0)
+            {
+                e.Cancel = true;
+                return;
+            }
+
+            List<SelectorBase> selectors = GetAllSelectors ();
+
+            foreach (SelectorBase selector in selectors)
+            {
+                selector.HorizontalSpace = e.NewValue;
+            }
+        }
+
+
+        void ShowBorderAndTitleOnCheckedStateChanged (object? sender, EventArgs<CheckState> e)
+        {
+            if (sender is not CheckBox cb)
+            {
+                return;
+            }
+
+            List<SelectorBase> selectors = GetAllSelectors ();
+
+            foreach (SelectorBase selector in selectors)
+            {
+                selector.Border!.Thickness = cb.CheckedState == CheckState.Checked ? new (1) : new Thickness (0);
+            }
+        }
+
+        void CanFocusOnCheckedStateChanged (object? sender, EventArgs<CheckState> e)
+        {
+            if (sender is not CheckBox cb)
+            {
+                return;
+            }
+
+            List<SelectorBase> selectors = GetAllSelectors ();
+
+            foreach (SelectorBase selector in selectors)
+            {
+                selector.CanFocus = cb.CheckedState == CheckState.Checked;
+            }
+        }
+
+        List<SelectorBase> GetAllSelectors ()
+        {
+            List<SelectorBase> optionSelectors = [];
+            // ReSharper disable once AccessToModifiedClosure
+            optionSelectors.AddRange (optionSelectorsFrame!.SubViews.OfType<SelectorBase> ());
+            // ReSharper disable once AccessToModifiedClosure
+            optionSelectors.AddRange (flagSelectorsFrame!.SubViews.OfType<FlagSelector> ());
+
+            return optionSelectors;
+        }
+    }
+
+
+}

+ 104 - 66
Examples/UICatalog/Scenarios/Shortcuts.cs

@@ -12,6 +12,7 @@ public class Shortcuts : Scenario
     public override void Main ()
     {
         Application.Init ();
+        var quitKey = Application.QuitKey;
         Window app = new ();
 
         app.Loaded += App_Loaded;
@@ -19,6 +20,7 @@ public class Shortcuts : Scenario
         Application.Run (app);
         app.Dispose ();
         Application.Shutdown ();
+        Application.QuitKey = quitKey;
     }
 
     // Setting everything up in Loaded handler because we change the
@@ -64,47 +66,26 @@ public class Shortcuts : Scenario
             {
                 Text = "_Align Keys",
                 CanFocus = false,
-                HighlightStates = MouseState.None
+                HighlightStates = MouseState.None,
+                CheckedState = CheckState.Checked
             },
             Key = Key.F5.WithCtrl.WithAlt.WithShift
         };
 
-        // ((CheckBox)vShortcut3.CommandView).CheckedStateChanging += (_, args) =>
         ((CheckBox)alignKeysShortcut.CommandView).CheckedStateChanging += (s, e) =>
                                                                           {
                                                                               if (alignKeysShortcut.CommandView is CheckBox cb)
                                                                               {
+                                                                                  bool align = e.Result == CheckState.Checked;
                                                                                   eventSource.Add (
                                                                                                    $"{alignKeysShortcut.Id}.CommandView.CheckedStateChanging: {cb.Text}");
                                                                                   eventLog.MoveDown ();
 
-                                                                                  var max = 0;
-
-                                                                                  IEnumerable<View> toAlign =
-                                                                                      Application.Top.SubViews.Where (
-                                                                                       v => v is Shortcut { Width: not DimAbsolute });
-                                                                                  IEnumerable<View> enumerable = toAlign as View [] ?? toAlign.ToArray ();
-
-                                                                                  if (e.Result == CheckState.Checked)
-                                                                                  {
-                                                                                      max = (from Shortcut? peer in enumerable
-                                                                                             select peer.Key.ToString ().GetColumns ()).Prepend (max)
-                                                                                          .Max ();
-
-                                                                                      foreach (View view in enumerable)
-                                                                                      {
-                                                                                          var peer = (Shortcut)view;
-                                                                                          max = Math.Max (max, peer.KeyView.Text.GetColumns ());
-                                                                                      }
-                                                                                  }
-
-                                                                                  foreach (View view in enumerable)
-                                                                                  {
-                                                                                      var peer = (Shortcut)view;
-                                                                                      peer.MinimumKeyTextSize = max;
-                                                                                  }
+                                                                                  AlignKeys (align);
                                                                               }
                                                                           };
+
+
         Application.Top.Add (alignKeysShortcut);
 
         var commandFirstShortcut = new Shortcut
@@ -134,9 +115,7 @@ public class Shortcuts : Scenario
                                                                                                       $"{commandFirstShortcut.Id}.CommandView.CheckedStateChanging: {cb.Text}");
                                                                                      eventLog.MoveDown ();
 
-                                                                                     IEnumerable<View> toAlign =
-                                                                                         Application.Top.SubViews.Where (
-                                                                                          v => v is Shortcut { Width: not DimAbsolute });
+                                                                                     IEnumerable<View> toAlign = Application.Top.SubViews.OfType<Shortcut> ();
                                                                                      IEnumerable<View> enumerable = toAlign as View [] ?? toAlign.ToArray ();
 
                                                                                      foreach (View view in enumerable)
@@ -164,8 +143,8 @@ public class Shortcuts : Scenario
             Y = Pos.Bottom (commandFirstShortcut),
             Width = Dim.Fill ()! - Dim.Width (eventLog),
             Key = Key.F4,
-            HelpText = "Changes all Command.CanFocus",
-            CommandView = new CheckBox { Text = "_CanFocus" }
+            HelpText = "Changes all CommandView.CanFocus",
+            CommandView = new CheckBox { Text = "_CommandView.CanFocus" },
         };
 
         ((CheckBox)canFocusShortcut.CommandView).CheckedStateChanging += (s, e) =>
@@ -177,13 +156,7 @@ public class Shortcuts : Scenario
 
                                                                                  //cb.CanFocus = e.NewValue == CheckState.Checked;
 
-                                                                                 foreach (Shortcut peer in Application.Top.SubViews.Where (v => v is Shortcut)!)
-                                                                                 {
-                                                                                     if (peer.CanFocus)
-                                                                                     {
-                                                                                         peer.CommandView.CanFocus = e.Result == CheckState.Checked;
-                                                                                     }
-                                                                                 }
+                                                                                 SetCanFocus (e.Result == CheckState.Checked);
                                                                              }
                                                                          };
         Application.Top.Add (canFocusShortcut);
@@ -222,37 +195,39 @@ public class Shortcuts : Scenario
 
         Application.Top.Add (buttonShortcut);
 
-        var radioGroupShortcut = new Shortcut
+        var optionSelectorShortcut = new Shortcut
         {
-            Id = "radioGroupShortcut",
+            Id = "optionSelectorShortcut",
+            HelpText = "Option Selector",
             X = 0,
             Y = Pos.Bottom (buttonShortcut),
             Key = Key.F2,
             Width = Dim.Fill ()! - Dim.Width (eventLog),
-            CommandView = new RadioGroup
+            CommandView = new OptionSelector ()
             {
                 Orientation = Orientation.Vertical,
-                RadioLabels = ["O_ne", "T_wo", "Th_ree", "Fo_ur"]
-            }
+                Labels = ["O_ne", "T_wo", "Th_ree", "Fo_ur"],
+                HighlightStates = MouseState.None,
+            },
         };
 
-        ((RadioGroup)radioGroupShortcut.CommandView).SelectedItemChanged += (o, args) =>
-                                                                            {
-                                                                                if (o is { })
+        ((OptionSelector)optionSelectorShortcut.CommandView).ValueChanged += (o, args) =>
                                                                                 {
-                                                                                    eventSource.Add (
-                                                                                                     $"SelectedItemChanged: {o.GetType ().Name} - {args.SelectedItem}");
-                                                                                    eventLog.MoveDown ();
-                                                                                }
-                                                                            };
+                                                                                    if (o is { })
+                                                                                    {
+                                                                                        eventSource.Add (
+                                                                                                         $"ValueChanged: {o.GetType ().Name} - {args.Value}");
+                                                                                        eventLog.MoveDown ();
+                                                                                    }
+                                                                                };
 
-        Application.Top.Add (radioGroupShortcut);
+        Application.Top.Add (optionSelectorShortcut);
 
         var sliderShortcut = new Shortcut
         {
             Id = "sliderShortcut",
             X = 0,
-            Y = Pos.Bottom (radioGroupShortcut),
+            Y = Pos.Bottom (optionSelectorShortcut),
             Width = Dim.Fill ()! - Dim.Width (eventLog),
             HelpText = "Sliders work!",
             CommandView = new Slider<string>
@@ -275,12 +250,34 @@ public class Shortcuts : Scenario
 
         Application.Top.Add (sliderShortcut);
 
+        ListView listView = new ListView ()
+        {
+            Height = Dim.Auto (),
+            Width = Dim.Auto (),
+            Title = "ListView",
+            BorderStyle = LineStyle.Single
+        };
+        listView.EnableForDesign ();
+
+        var listViewShortcut = new Shortcut ()
+        {
+            Id = "listViewShortcut",
+            X = 0,
+            Y = Pos.Bottom (sliderShortcut),
+            Width = Dim.Fill ()! - Dim.Width (eventLog),
+            HelpText = "A ListView with Border",
+            CommandView = listView,
+            Key = Key.F5.WithCtrl,
+        };
+
+        Application.Top.Add (listViewShortcut);
+
         var noCommandShortcut = new Shortcut
         {
             Id = "noCommandShortcut",
             X = 0,
-            Y = Pos.Bottom (sliderShortcut),
-            Width = Dim.Width (sliderShortcut),
+            Y = Pos.Bottom (listViewShortcut),
+            Width = Dim.Width (listViewShortcut),
             HelpText = "No Command",
             Key = Key.D0
         };
@@ -319,13 +316,15 @@ public class Shortcuts : Scenario
             Id = "framedShortcut",
             X = 0,
             Y = Pos.Bottom (noHelpShortcut) + 1,
-            Title = "Framed",
+            Width = Dim.Width (noHelpShortcut),
+            Title = "Framed Shortcut",
             Key = Key.K.WithCtrl,
-            Text = "Resize frame",
+            Text = "Help: You can resize this",
             BorderStyle = LineStyle.Dotted,
             Arrangement = ViewArrangement.RightResizable | ViewArrangement.BottomResizable
         };
-        framedShortcut.Orientation = Orientation.Horizontal;
+        framedShortcut.Border!.Settings = BorderSettings.Title;
+        //framedShortcut.Orientation = Orientation.Horizontal;
 
         if (framedShortcut.Padding is { })
         {
@@ -335,12 +334,12 @@ public class Shortcuts : Scenario
 
         if (framedShortcut.CommandView.Margin is { })
         {
-            framedShortcut.CommandView.Margin.SchemeName = framedShortcut.CommandView.SchemeName = "Error";
-            framedShortcut.HelpView.Margin!.SchemeName = framedShortcut.HelpView.SchemeName = "Dialog";
-            framedShortcut.KeyView.Margin!.SchemeName = framedShortcut.KeyView.SchemeName = "Menu";
+            framedShortcut.CommandView.SchemeName = framedShortcut.CommandView.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Dialog);
+            framedShortcut.HelpView.SchemeName = framedShortcut.HelpView.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Error);
+            framedShortcut.KeyView.SchemeName = framedShortcut.KeyView.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base);
         }
 
-        framedShortcut.SchemeName = "TopLevel";
+        framedShortcut.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Toplevel);
         Application.Top.Add (framedShortcut);
 
         // Horizontal
@@ -382,7 +381,6 @@ public class Shortcuts : Scenario
 
                                  pb.Fraction += 0.01f;
 
-                                 Application.Wakeup ();
 
                                  pb.SetNeedsDraw ();
                              }
@@ -503,7 +501,7 @@ public class Shortcuts : Scenario
                                                   eventSource.Add (
                                                                    $"{shortcut!.Id}.CommandView.Selecting: {shortcut!.CommandView.Text} {shortcut!.CommandView.GetType ().Name}");
                                                   eventLog.MoveDown ();
-                                                  args.Handled = true;
+                                                  //args.Handled = true;
                                               };
 
             shortcut.Accepting += (o, args) =>
@@ -512,7 +510,7 @@ public class Shortcuts : Scenario
                                       eventLog.MoveDown ();
 
                                       // We don't want this to exit the Scenario
-                                      args.Handled = true;
+                                      //args.Handled = true;
                                   };
 
             shortcut.CommandView.Accepting += (o, args) =>
@@ -522,6 +520,46 @@ public class Shortcuts : Scenario
                                                   eventLog.MoveDown ();
                                               };
         }
+
+        SetCanFocus (false);
+
+        AlignKeys (true);
+
+        return;
+
+        void SetCanFocus (bool canFocus)
+        {
+            foreach (Shortcut peer in Application.Top!.SubViews.OfType<Shortcut> ())
+            {
+                if (peer.CanFocus)
+                {
+                    peer.CommandView.CanFocus = canFocus;
+                }
+            }
+        }
+
+        void AlignKeys (bool align)
+        {
+            var max = 0;
+
+            IEnumerable<Shortcut> toAlign = Application.Top!.SubViews.OfType<Shortcut> ().Where(s => !s.Y.Has<PosAnchorEnd>(out _)).Cast<Shortcut>();
+            IEnumerable<Shortcut> enumerable = toAlign as Shortcut [] ?? toAlign.ToArray ();
+
+            if (align)
+            {
+                max = (from Shortcut? peer in enumerable
+                       select peer!.Key.ToString ().GetColumns ()).Prepend (max)
+                                                                  .Max ();
+
+                max = enumerable.Select (peer => peer.KeyView.Text.GetColumns ()).Prepend (max).Max ();
+            }
+
+            foreach (Shortcut shortcut in enumerable)
+            {
+                Shortcut peer = shortcut;
+                peer.MinimumKeyTextSize = max;
+            }
+        }
     }
 
     private void Button_Clicked (object? sender, CommandEventArgs e)

File diff suppressed because it is too large
+ 246 - 107
Examples/UICatalog/Scenarios/TableEditor.cs


+ 10 - 10
Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs

@@ -492,18 +492,18 @@ public class TextAlignmentAndDirection : Scenario
 
         // JUSTIFY OPTIONS
 
-        var justifyOptions = new RadioGroup
+        var justifyOptions = new OptionSelector
         {
             X = Pos.Left (justifyCheckbox) + 1,
             Y = Pos.Y (justifyCheckbox) + 1,
             Width = Dim.Fill (9),
-            RadioLabels = ["Current direction", "Opposite direction", "FIll Both"],
+            Labels = ["Current direction", "Opposite direction", "FIll Both"],
             Enabled = false
         };
 
         justifyCheckbox.CheckedStateChanging += (s, e) => ToggleJustify (e.Result != CheckState.Checked);
 
-        justifyOptions.SelectedItemChanged += (s, e) => { ToggleJustify (false, true); };
+        justifyOptions.ValueChanged += (_, _) => { ToggleJustify (false, true); };
 
         app.Add (justifyOptions);
 
@@ -541,17 +541,17 @@ public class TextAlignmentAndDirection : Scenario
 
         List<TextDirection> directionsEnum = Enum.GetValues (typeof (TextDirection)).Cast<TextDirection> ().ToList ();
 
-        var directionOptions = new RadioGroup
+        var directionOptions = new OptionSelector
         {
             X = Pos.Right (container) + 1,
             Y = Pos.Bottom (wrapCheckbox) + 1,
             Width = Dim.Fill (10),
             Height = Dim.Fill (1),
             HotKeySpecifier = (Rune)'\xffff',
-            RadioLabels = directionsEnum.Select (e => e.ToString ()).ToArray ()
+            Labels = directionsEnum.Select (e => e.ToString ()).ToArray ()
         };
 
-        directionOptions.SelectedItemChanged += (s, ev) =>
+        directionOptions.ValueChanged += (s, ev) =>
                                                 {
                                                     bool justChecked = justifyCheckbox.CheckedState == CheckState.Checked;
 
@@ -560,9 +560,9 @@ public class TextAlignmentAndDirection : Scenario
                                                         ToggleJustify (true);
                                                     }
 
-                                                    foreach (View v in multiLineLabels)
+                                                    foreach (View v in multiLineLabels.Where (v => ev.Value is { }))
                                                     {
-                                                        v.TextDirection = (TextDirection)ev.SelectedItem;
+                                                        v.TextDirection = (TextDirection)ev.Value!.Value;
                                                     }
 
                                                     if (justChecked)
@@ -612,7 +612,7 @@ public class TextAlignmentAndDirection : Scenario
 
                     if (TextFormatter.IsVerticalDirection (t.TextDirection))
                     {
-                        switch (justifyOptions.SelectedItem)
+                        switch (justifyOptions.Value)
                         {
                             case 0:
                                 t.VerticalTextAlignment = Alignment.Fill;
@@ -630,7 +630,7 @@ public class TextAlignmentAndDirection : Scenario
                     }
                     else
                     {
-                        switch (justifyOptions.SelectedItem)
+                        switch (justifyOptions.Value)
                         {
                             case 0:
                                 t.TextAlignment = Alignment.Fill;

+ 2 - 2
Examples/UICatalog/Scenarios/TextInputControls.cs

@@ -68,7 +68,7 @@ public class TextInputControls : Scenario
             X = Pos.Right (label) + 1,
             Y = Pos.Bottom (textField),
             Width = Dim.Percent (50) - 1,
-            Caption = "TextField with caption"
+            Title = "TextField with caption"
         };
 
         win.Add (textField);
@@ -498,5 +498,5 @@ public class TextInputControls : Scenario
 
 
 
-    private void TimeChanged (object sender, DateTimeEventArgs<TimeSpan> e) { _labelMirroringTimeField.Text = _timeField.Text; }
+    private void TimeChanged (object sender, EventArgs<TimeSpan> e) { _labelMirroringTimeField.Text = _timeField.Text; }
 }

+ 7 - 7
Examples/UICatalog/Scenarios/Themes.cs

@@ -23,26 +23,26 @@ public sealed class Themes : Scenario
         };
 
         string[]  options = ThemeManager.GetThemeNames ().Select (option => option = "_" + option).ToArray ();
-        RadioGroup themeOptionSelector = new ()
+        OptionSelector themeOptionSelector = new ()
         {
             Title = "_Themes",
             BorderStyle = LineStyle.Rounded,
             Width = Dim.Auto (),
             Height = Dim.Auto (),
-            RadioLabels= options,
-            SelectedItem = ThemeManager.GetThemeNames ().IndexOf (ThemeManager.Theme)
+            Labels= options,
+            Value = ThemeManager.GetThemeNames ().IndexOf (ThemeManager.Theme)
         };
         themeOptionSelector.Border!.Thickness = new (0, 1, 0, 0);
         themeOptionSelector.Margin!.Thickness = new (0, 0, 1, 0);
 
-        themeOptionSelector.SelectedItemChanged += (sender, args) =>
+        themeOptionSelector.ValueChanged += (sender, args) =>
                                              {
-                                                 RadioGroup? optionSelector = sender as RadioGroup;
+                                                 OptionSelector? optionSelector = sender as OptionSelector;
                                                  if (optionSelector is null)
                                                  {
                                                      return;
                                                  }
-                                                 var newTheme = optionSelector!.RadioLabels! [(int)args.SelectedItem!] as string;
+                                                 var newTheme = optionSelector!.Labels! [(int)args.Value!] as string;
                                                  // strip off the leading underscore
                                                  ThemeManager.Theme = newTheme!.Substring (1);
                                                  ConfigurationManager.Apply ();
@@ -172,7 +172,7 @@ public sealed class Themes : Scenario
                                                     else
                                                     {
                                                         appWindow.Remove (allViewsView);
-                                                        allViewsView.Dispose ();
+                                                        allViewsView!.Dispose ();
                                                         allViewsView = null;
 
                                                         appWindow.Add (viewFrame);

+ 0 - 228
Examples/UICatalog/Scenarios/TileViewNesting.cs

@@ -1,228 +0,0 @@
-using System.Linq;
-
-namespace UICatalog.Scenarios;
-
-[ScenarioMetadata ("Tile View Nesting", "Demonstrates recursive nesting of TileViews")]
-[ScenarioCategory ("Controls")]
-[ScenarioCategory ("LineView")]
-public class TileViewNesting : Scenario
-{
-    private CheckBox _cbBorder;
-    private CheckBox _cbHorizontal;
-    private CheckBox _cbTitles;
-    private CheckBox _cbUseLabels;
-    private TextField _textField;
-    private int _viewsCreated;
-    private int _viewsToCreate;
-    private View _workArea;
-
-    /// <summary>Setup the scenario.</summary>
-    public override void Main ()
-    {
-        Application.Init ();
-        // Scenario Windows.
-        var win = new Window
-        {
-            Title = GetName (),
-            Y = 1
-        };
-
-        var lblViews = new Label { Text = "Number Of Views:" };
-        _textField = new() { X = Pos.Right (lblViews), Width = 10, Text = "2" };
-
-        _textField.TextChanged += (s, e) => SetupTileView ();
-
-        _cbHorizontal = new() { X = Pos.Right (_textField) + 1, Text = "Horizontal" };
-        _cbHorizontal.CheckedStateChanged += (s, e) => SetupTileView ();
-
-        _cbBorder = new() { X = Pos.Right (_cbHorizontal) + 1, Text = "Border" };
-        _cbBorder.CheckedStateChanged += (s, e) => SetupTileView ();
-
-        _cbTitles = new() { X = Pos.Right (_cbBorder) + 1, Text = "Titles" };
-        _cbTitles.CheckedStateChanged += (s, e) => SetupTileView ();
-
-        _cbUseLabels = new() { X = Pos.Right (_cbTitles) + 1, Text = "Use Labels" };
-        _cbUseLabels.CheckedStateChanged += (s, e) => SetupTileView ();
-
-        _workArea = new() { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill () };
-
-        var menu = new MenuBar
-        {
-            Menus =
-            [
-                new ("_File", new MenuItem [] { new ("_Quit", "", () => Quit ()) })
-            ]
-        };
-
-        win.Add (lblViews);
-        win.Add (_textField);
-        win.Add (_cbHorizontal);
-        win.Add (_cbBorder);
-        win.Add (_cbTitles);
-        win.Add (_cbUseLabels);
-        win.Add (_workArea);
-
-        SetupTileView ();
-
-        var top = new Toplevel ();
-        top.Add (menu);
-        top.Add (win);
-
-        Application.Run (top);
-        top.Dispose ();
-        Application.Shutdown ();
-    }
-
-    private void AddMoreViews (TileView to)
-    {
-        if (_viewsCreated == _viewsToCreate)
-        {
-            return;
-        }
-
-        if (!(to.Tiles.ElementAt (0).ContentView is TileView))
-        {
-            Split (to, true);
-        }
-
-        if (!(to.Tiles.ElementAt (1).ContentView is TileView))
-        {
-            Split (to, false);
-        }
-
-        if (to.Tiles.ElementAt (0).ContentView is TileView && to.Tiles.ElementAt (1).ContentView is TileView)
-        {
-            AddMoreViews ((TileView)to.Tiles.ElementAt (0).ContentView);
-            AddMoreViews ((TileView)to.Tiles.ElementAt (1).ContentView);
-        }
-    }
-
-    private View CreateContentControl (int number) { return _cbUseLabels.CheckedState == CheckState.Checked ? CreateLabelView (number) : CreateTextView (number); }
-
-    private View CreateLabelView (int number)
-    {
-        return new Label
-        {
-            Width = Dim.Fill (),
-            Height = 1,
-
-            Text = number.ToString ().Repeat (1000),
-            CanFocus = true
-        };
-    }
-
-    private View CreateTextView (int number)
-    {
-        return new TextView
-        {
-            Width = Dim.Fill (), Height = Dim.Fill (), Text = number.ToString ().Repeat (1000), AllowsTab = false
-
-            //WordWrap = true,  // TODO: This is very slow (like 10s to render with 45 views)
-        };
-    }
-
-    private TileView CreateTileView (int titleNumber, Orientation orientation)
-    {
-        var toReturn = new TileView
-        {
-            Width = Dim.Fill (),
-            Height = Dim.Fill (),
-
-            // flip the orientation
-            Orientation = orientation
-        };
-
-        toReturn.Tiles.ElementAt (0).Title = _cbTitles.CheckedState == CheckState.Checked ? $"View {titleNumber}" : string.Empty;
-        toReturn.Tiles.ElementAt (1).Title = _cbTitles.CheckedState == CheckState.Checked ? $"View {titleNumber + 1}" : string.Empty;
-
-        return toReturn;
-    }
-
-    private int GetNumberOfViews ()
-    {
-        if (int.TryParse (_textField.Text, out int views) && views >= 0)
-        {
-            return views;
-        }
-
-        return 0;
-    }
-
-    private void Quit () { Application.RequestStop (); }
-
-    private void SetupTileView ()
-    {
-        int numberOfViews = GetNumberOfViews ();
-
-        CheckState titles = _cbTitles.CheckedState;
-        CheckState border = _cbBorder.CheckedState;
-        CheckState startHorizontal = _cbHorizontal.CheckedState;
-
-        foreach (View sub in _workArea.SubViews)
-        {
-            sub.Dispose ();
-        }
-
-        _workArea.RemoveAll ();
-
-        if (numberOfViews <= 0)
-        {
-            return;
-        }
-
-        TileView root = CreateTileView (1, startHorizontal == CheckState.Checked ? Orientation.Horizontal : Orientation.Vertical);
-
-        root.Tiles.ElementAt (0).ContentView.Add (CreateContentControl (1));
-        root.Tiles.ElementAt (0).Title = _cbTitles.CheckedState == CheckState.Checked ? "View 1" : string.Empty;
-        root.Tiles.ElementAt (1).ContentView.Add (CreateContentControl (2));
-        root.Tiles.ElementAt (1).Title = _cbTitles.CheckedState == CheckState.Checked ? "View 2" : string.Empty;
-
-        root.LineStyle = border  == CheckState.Checked? LineStyle.Rounded : LineStyle.None;
-
-        _workArea.Add (root);
-
-        if (numberOfViews == 1)
-        {
-            root.Tiles.ElementAt (1).ContentView.Visible = false;
-        }
-
-        if (numberOfViews > 2)
-        {
-            _viewsCreated = 2;
-            _viewsToCreate = numberOfViews;
-            AddMoreViews (root);
-        }
-    }
-
-    private void Split (TileView to, bool left)
-    {
-        if (_viewsCreated == _viewsToCreate)
-        {
-            return;
-        }
-
-        TileView newView;
-
-        if (left)
-        {
-            to.TrySplitTile (0, 2, out newView);
-        }
-        else
-        {
-            to.TrySplitTile (1, 2, out newView);
-        }
-
-        _viewsCreated++;
-
-        // During splitting the old Title will have been migrated to View1 so we only need
-        // to set the Title on View2 (the one that gets our new TextView)
-        newView.Tiles.ElementAt (1).Title = _cbTitles.CheckedState == CheckState.Checked ? $"View {_viewsCreated}" : string.Empty;
-
-        // Flip orientation
-        newView.Orientation = to.Orientation == Orientation.Vertical
-                                  ? Orientation.Horizontal
-                                  : Orientation.Vertical;
-
-        newView.Tiles.ElementAt (1).ContentView.Add (CreateContentControl (_viewsCreated));
-    }
-}

+ 12 - 15
Examples/UICatalog/Scenarios/TimeAndDate.cs

@@ -1,4 +1,5 @@
-using System;
+#nullable enable
+using System;
 
 namespace UICatalog.Scenarios;
 
@@ -7,12 +8,12 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("DateTime")]
 public class TimeAndDate : Scenario
 {
-    private Label _lblDateFmt;
-    private Label _lblNewDate;
-    private Label _lblNewTime;
-    private Label _lblOldDate;
-    private Label _lblOldTime;
-    private Label _lblTimeFmt;
+    private Label? _lblDateFmt;
+    private Label? _lblNewDate;
+    private Label? _lblNewTime;
+    private Label? _lblOldDate;
+    private Label? _lblOldTime;
+    private Label? _lblTimeFmt;
 
     public override void Main ()
     {
@@ -143,17 +144,13 @@ public class TimeAndDate : Scenario
         Application.Shutdown ();
     }
 
-    private void DateChanged (object sender, DateTimeEventArgs<DateTime> e)
+    private void DateChanged (object? sender, EventArgs<DateTime> e)
     {
-        _lblOldDate.Text = $"Old Date: {e.OldValue}";
-        _lblNewDate.Text = $"New Date: {e.NewValue}";
-        _lblDateFmt.Text = $"Date Format: {e.Format}";
+        _lblNewDate!.Text = $"New Date: {e.Value}";
     }
 
-    private void TimeChanged (object sender, DateTimeEventArgs<TimeSpan> e)
+    private void TimeChanged (object? sender, EventArgs<TimeSpan> e)
     {
-        _lblOldTime.Text = $"Old Time: {e.OldValue}";
-        _lblNewTime.Text = $"New Time: {e.NewValue}";
-        _lblTimeFmt.Text = $"Time Format: {e.Format}";
+        _lblNewTime!.Text = $"New Time: {e.Value}";
     }
 }

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

@@ -414,7 +414,7 @@ public class TreeViewFileSystem : Scenario
 
     private void ShowContextMenu (Point screenPoint, IFileSystemInfo forObject)
     {
-        PopoverMenu? contextMenu = new ([new ("Properties", $"Show {forObject.Name} properties", () => ShowPropertiesOf (forObject))]);
+        PopoverMenu contextMenu = new ([new ("Properties", $"Show {forObject.Name} properties", () => ShowPropertiesOf (forObject))]);
 
         // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused
         // and the context menu is disposed when it is closed.

+ 13 - 14
Examples/UICatalog/Scenarios/Unicode.cs

@@ -71,8 +71,7 @@ public class UnicodeInMenu : Scenario
         appWindow.Add (menu);
 
         var statusBar = new StatusBar (
-                                       new Shortcut []
-                                       {
+                                       [
                                            new (
                                                 Application.QuitKey,
                                                 "Выход",
@@ -80,7 +79,7 @@ public class UnicodeInMenu : Scenario
                                                ),
                                            new (Key.F2, "Создать", null),
                                            new (Key.F3, "Со_хранить", null)
-                                       }
+                                       ]
                                       );
         appWindow.Add (statusBar);
 
@@ -145,13 +144,13 @@ public class UnicodeInMenu : Scenario
         };
         appWindow.Add (checkBox, checkBoxRight);
 
-        label = new () { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1, Text = "ComboBox:" };
-        appWindow.Add (label);
-        var comboBox = new ComboBox { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) };
-        comboBox.SetSource (new ObservableCollection<string> { gitString, "Со_хранить" });
+        //label = new () { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1, Text = "ComboBox:" };
+        //appWindow.Add (label);
+        //var comboBox = new ComboBox { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) };
+        //comboBox.SetSource (new ObservableCollection<string> { gitString, "Со_хранить" });
 
-        appWindow.Add (comboBox);
-        comboBox.Text = gitString;
+        //appWindow.Add (comboBox);
+        //comboBox.Text = gitString;
 
         label = new () { X = Pos.X (label), Y = Pos.Bottom (label) + 2, Text = "HexView:" };
         appWindow.Add (label);
@@ -177,19 +176,19 @@ public class UnicodeInMenu : Scenario
         };
         appWindow.Add (listView);
 
-        label = new () { X = Pos.X (label), Y = Pos.Bottom (listView) + 1, Text = "RadioGroup:" };
+        label = new () { X = Pos.X (label), Y = Pos.Bottom (listView) + 1, Text = "OptionSelector:" };
         appWindow.Add (label);
 
-        var radioGroup = new RadioGroup
+        var optionSelector = new OptionSelector
         {
             X = 20,
             Y = Pos.Y (label),
             Width = Dim.Percent (60),
-            RadioLabels = new [] { "item #1", gitString, "Со_хранить", "𝔽𝕆𝕆𝔹𝔸ℝ" }
+            Labels = new [] { "item #1", gitString, "Со_хранить", "𝔽𝕆𝕆𝔹𝔸ℝ" }
         };
-        appWindow.Add (radioGroup);
+        appWindow.Add (optionSelector);
 
-        label = new () { X = Pos.X (label), Y = Pos.Bottom (radioGroup) + 1, Text = "TextField:" };
+        label = new () { X = Pos.X (label), Y = Pos.Bottom (optionSelector) + 1, Text = "TextField:" };
         appWindow.Add (label);
 
         var textField = new TextField

+ 2 - 2
Examples/UICatalog/Scenarios/ViewportSettings.cs

@@ -178,10 +178,10 @@ public class ViewportSettings : Scenario
         buttonAnchored.Accepting += (sender, args) => MessageBox.Query ("Hi", $"You pressed {((Button)sender)?.Text}", "_Ok");
 
         view.Margin!.Data = "Margin";
-        view.Margin.Thickness = new (0);
+        view.Margin!.Thickness = new (0);
 
         view.Border!.Data = "Border";
-        view.Border.Thickness = new (3);
+        view.Border!.Thickness = new (3);
 
         view.Padding.Data = "Padding";
 

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

@@ -43,7 +43,7 @@ public class WindowsAndFrameViews : Scenario
             Arrangement = ViewArrangement.Overlapped | ViewArrangement.Movable | ViewArrangement.Resizable
         };
         win.Padding.Thickness = new (padding);
-        win.Margin.Thickness = new (margin);
+        win.Margin!.Thickness = new (margin);
 
         var paddingButton = new Button
         {

+ 6 - 6
Examples/UICatalog/Scenarios/Wizards.cs

@@ -162,11 +162,11 @@ public class Wizards : Scenario
                                            firstStep.HelpText =
                                                "This is the End User License Agreement.\n\n\n\n\n\nThis is a test of the emergency broadcast system. This is a test of the emergency broadcast system.\nThis is a test of the emergency broadcast system.\n\n\nThis is a test of the emergency broadcast system.\n\nThis is a test of the emergency broadcast system.\n\n\n\nThe end of the EULA.";
 
-                                           RadioGroup radioGroup = new ()
+                                           OptionSelector optionSelector = new ()
                                            {
-                                               RadioLabels = ["_One", "_Two", "_3"]
+                                               Labels = ["_One", "_Two", "_3"]
                                            };
-                                           firstStep.Add (radioGroup);
+                                           firstStep.Add (optionSelector);
 
                                            wizard.AddStep (firstStep);
 
@@ -184,12 +184,12 @@ public class Wizards : Scenario
                                                Text = "Press Me to Rename Step", X = Pos.Right (buttonLbl), Y = Pos.Top (buttonLbl)
                                            };
 
-                                           RadioGroup radioGroup2 = new ()
+                                           OptionSelector optionSelecor2 = new ()
                                            {
-                                               RadioLabels = ["_A", "_B", "_C"],
+                                               Labels = ["_A", "_B", "_C"],
                                                Orientation = Orientation.Horizontal
                                            };
-                                           secondStep.Add (radioGroup2);
+                                           secondStep.Add (optionSelecor2);
 
                                            button.Accepting += (s, e) =>
                                                             {

+ 44 - 19
Examples/UICatalog/UICatalog.cs

@@ -18,6 +18,7 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Reflection;
+using System.Reflection.Metadata;
 using System.Text;
 using System.Text.Json;
 using Microsoft.Extensions.Logging;
@@ -76,12 +77,25 @@ public class UICatalog
         // Process command line args
 
         // If no driver is provided, the default driver is used.
-        Option<string> driverOption = new Option<string> ("--driver", "The IConsoleDriver to use.").FromAmong (
-             Application.GetDriverTypes ().Item2.ToArray ()!
-            );
+        // Get allowed driver names
+        string? [] allowedDrivers = Application.GetDriverTypes ().Item2.ToArray ();
+
+        Option<string> driverOption = new Option<string> ("--driver", "The IDriver to use.")
+            .FromAmong (allowedDrivers!);
+        driverOption.SetDefaultValue (string.Empty);
         driverOption.AddAlias ("-d");
         driverOption.AddAlias ("--d");
 
+        // Add validator separately (not chained)
+        driverOption.AddValidator (result =>
+        {
+            var value = result.GetValueOrDefault<string> ();
+            if (result.Tokens.Count > 0 && !allowedDrivers.Contains (value))
+            {
+                result.ErrorMessage = $"Invalid driver name '{value}'. Allowed values: {string.Join (", ", allowedDrivers)}";
+            }
+        });
+
         // Configuration Management
         Option<bool> disableConfigManagement = new (
                                                     "--disable-cm",
@@ -163,6 +177,17 @@ public class UICatalog
             return 0;
         }
 
+        var parseResult = parser.Parse (args);
+
+        if (parseResult.Errors.Count > 0)
+        {
+            foreach (var error in parseResult.Errors)
+            {
+                Console.Error.WriteLine (error.Message);
+            }
+            return 1; // Non-zero exit code for error
+        }
+
         Scenario.BenchmarkTimeout = Options.BenchmarkTimeout;
 
         Logging.Logger = CreateLogger ();
@@ -175,16 +200,16 @@ public class UICatalog
     public static LogEventLevel LogLevelToLogEventLevel (LogLevel logLevel)
     {
         return logLevel switch
-               {
-                   LogLevel.Trace => LogEventLevel.Verbose,
-                   LogLevel.Debug => LogEventLevel.Debug,
-                   LogLevel.Information => LogEventLevel.Information,
-                   LogLevel.Warning => LogEventLevel.Warning,
-                   LogLevel.Error => LogEventLevel.Error,
-                   LogLevel.Critical => LogEventLevel.Fatal,
-                   LogLevel.None => LogEventLevel.Fatal, // Default to Fatal if None is specified
-                   _ => LogEventLevel.Fatal // Default to Information for any unspecified LogLevel
-               };
+        {
+            LogLevel.Trace => LogEventLevel.Verbose,
+            LogLevel.Debug => LogEventLevel.Debug,
+            LogLevel.Information => LogEventLevel.Information,
+            LogLevel.Warning => LogEventLevel.Warning,
+            LogLevel.Error => LogEventLevel.Error,
+            LogLevel.Critical => LogEventLevel.Fatal,
+            LogLevel.None => LogEventLevel.Fatal, // Default to Fatal if None is specified
+            _ => LogEventLevel.Fatal // Default to Information for any unspecified LogLevel
+        };
     }
 
     private static ILogger CreateLogger ()
@@ -426,7 +451,7 @@ public class UICatalog
             scenario.StartBenchmark ();
         }
 
-        Application.Init (driverName: _forceDriver);
+        Application.ForceDriver = _forceDriver!;
 
         scenario.Main ();
 
@@ -492,7 +517,7 @@ public class UICatalog
 
         if (benchmarkWindow.Border is { })
         {
-            benchmarkWindow.Border.Thickness = new (0, 0, 0, 0);
+            benchmarkWindow.Border!.Thickness = new (0, 0, 0, 0);
         }
 
         TableView resultsTableView = new ()
@@ -610,7 +635,7 @@ public class UICatalog
         if (!View.EnableDebugIDisposableAsserts)
         {
             View.Instances.Clear ();
-            RunState.Instances.Clear ();
+            SessionToken.Instances.Clear ();
 
             return;
         }
@@ -625,15 +650,15 @@ public class UICatalog
 
         View.Instances.Clear ();
 
-        // Validate there are no outstanding Application.RunState-based instances 
+        // Validate there are no outstanding Application sessions
         // after a scenario was selected to run. This proves the main UI Catalog
         // 'app' closed cleanly.
-        foreach (RunState? inst in RunState.Instances)
+        foreach (SessionToken? inst in SessionToken.Instances)
         {
             Debug.Assert (inst.WasDisposed);
         }
 
-        RunState.Instances.Clear ();
+        SessionToken.Instances.Clear ();
 #endif
     }
 }

+ 52 - 44
Examples/UICatalog/UICatalogTop.cs

@@ -96,9 +96,9 @@ public class UICatalogTop : Toplevel
 
     private readonly MenuBarv2? _menuBar;
     private CheckBox? _force16ColorsMenuItemCb;
-    private OptionSelector? _themesRg;
-    private OptionSelector? _topSchemeRg;
-    private OptionSelector? _logLevelRg;
+    private OptionSelector? _themesSelector;
+    private OptionSelector? _topSchemesSelector;
+    private OptionSelector? _logLevelSelector;
     private FlagSelector<ViewDiagnosticFlags>? _diagnosticFlagsSelector;
     private CheckBox? _disableMouseCb;
 
@@ -126,13 +126,13 @@ public class UICatalogTop : Toplevel
                                           [
                                               new MenuItemv2 (
                                                               "_Documentation",
-                                                              "",
+                                                              "API docs",
                                                               () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui"),
                                                               Key.F1
                                                              ),
                                               new MenuItemv2 (
                                                               "_README",
-                                                              "",
+                                                              "Project readme",
                                                               () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"),
                                                               Key.F2
                                                              ),
@@ -163,7 +163,10 @@ public class UICatalogTop : Toplevel
             _force16ColorsMenuItemCb = new ()
             {
                 Title = "Force _16 Colors",
-                CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked
+                CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
+                // Best practice for CheckBoxes in menus is to disable focus and highlight states
+                CanFocus = false,
+                HighlightStates = MouseState.None
             };
 
             _force16ColorsMenuItemCb.CheckedStateChanging += (sender, args) =>
@@ -194,23 +197,25 @@ public class UICatalogTop : Toplevel
 
             if (ConfigurationManager.IsEnabled)
             {
-                _themesRg = new ()
+                _themesSelector = new ()
                 {
-                    HighlightStates = Terminal.Gui.ViewBase.MouseState.None,
+                    // HighlightStates = MouseState.In,
+                    CanFocus = true,
+                    // InvertFocusAttribute = true
                 };
 
-                _themesRg.SelectedItemChanged += (_, args) =>
+                _themesSelector.ValueChanged += (_, args) =>
                                                  {
-                                                     if (args.SelectedItem is null)
+                                                     if (args.Value is null)
                                                      {
                                                          return;
                                                      }
-                                                     ThemeManager.Theme = ThemeManager.GetThemeNames () [args.SelectedItem!.Value];
+                                                     ThemeManager.Theme = ThemeManager.GetThemeNames () [(int)args.Value];
                                                  };
 
                 var menuItem = new MenuItemv2
                 {
-                    CommandView = _themesRg,
+                    CommandView = _themesSelector,
                     HelpText = "Cycle Through Themes",
                     Key = Key.T.WithCtrl
                 };
@@ -218,18 +223,18 @@ public class UICatalogTop : Toplevel
 
                 menuItems.Add (new Line ());
 
-                _topSchemeRg = new ()
+                _topSchemesSelector = new ()
                 {
-                    HighlightStates = Terminal.Gui.ViewBase.MouseState.None,
+                    //  HighlightStates = MouseState.In,
                 };
 
-                _topSchemeRg.SelectedItemChanged += (_, args) =>
+                _topSchemesSelector.ValueChanged += (_, args) =>
                                                     {
-                                                        if (args.SelectedItem is null)
+                                                        if (args.Value is null)
                                                         {
                                                             return;
                                                         }
-                                                        CachedTopLevelScheme = SchemeManager.GetSchemesForCurrentTheme ()!.Keys.ToArray () [args.SelectedItem!.Value];
+                                                        CachedTopLevelScheme = SchemeManager.GetSchemesForCurrentTheme ()!.Keys.ToArray () [(int)args.Value];
                                                         SchemeName = CachedTopLevelScheme;
                                                         SetNeedsDraw ();
                                                     };
@@ -241,7 +246,7 @@ public class UICatalogTop : Toplevel
                                    [
                                        new ()
                                        {
-                                           CommandView = _topSchemeRg,
+                                           CommandView = _topSchemesSelector,
                                            HelpText = "Cycle Through schemes",
                                            Key = Key.S.WithCtrl
                                        }
@@ -269,12 +274,12 @@ public class UICatalogTop : Toplevel
 
             _diagnosticFlagsSelector = new ()
             {
-                CanFocus = true,
-                Styles = FlagSelectorStyles.ShowNone,
-                HighlightStates = Terminal.Gui.ViewBase.MouseState.None,
+                Styles = SelectorStyles.ShowNoneFlag,
+                CanFocus = true
+
             };
             _diagnosticFlagsSelector.UsedHotKeys.Add (Key.D);
-            _diagnosticFlagsSelector.AssignHotKeysToCheckBoxes = true;
+            _diagnosticFlagsSelector.AssignHotKeys = true;
             _diagnosticFlagsSelector.Value = Diagnostics;
             _diagnosticFlagsSelector.ValueChanged += (sender, args) =>
                                                      {
@@ -294,7 +299,10 @@ public class UICatalogTop : Toplevel
             _disableMouseCb = new ()
             {
                 Title = "_Disable Mouse",
-                CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked
+                CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked,
+                // Best practice for CheckBoxes in menus is to disable focus and highlight states
+                CanFocus = false,
+                HighlightStates = MouseState.None
             };
 
             _disableMouseCb.CheckedStateChanged += (_, args) => { Application.IsMouseDisabled = args.Value == CheckState.Checked; };
@@ -315,17 +323,17 @@ public class UICatalogTop : Toplevel
 
             LogLevel [] logLevels = Enum.GetValues<LogLevel> ();
 
-            _logLevelRg = new ()
+            _logLevelSelector = new ()
             {
-                AssignHotKeysToCheckBoxes = true,
-                Options = Enum.GetNames<LogLevel> (),
-                SelectedItem = logLevels.ToList ().IndexOf (Enum.Parse<LogLevel> (UICatalog.Options.DebugLogLevel)),
-                HighlightStates = Terminal.Gui.ViewBase.MouseState.In
+                AssignHotKeys = true,
+                Labels = Enum.GetNames<LogLevel> (),
+                Value = logLevels.ToList ().IndexOf (Enum.Parse<LogLevel> (UICatalog.Options.DebugLogLevel)),
+                // HighlightStates = MouseState.In,
             };
 
-            _logLevelRg.SelectedItemChanged += (_, args) =>
+            _logLevelSelector.ValueChanged += (_, args) =>
             {
-                UICatalog.Options = UICatalog.Options with { DebugLogLevel = Enum.GetName (logLevels [args.SelectedItem!.Value])! };
+                UICatalog.Options = UICatalog.Options with { DebugLogLevel = Enum.GetName (logLevels [args.Value!.Value])! };
 
                 UICatalog.LogLevelSwitch.MinimumLevel =
                     UICatalog.LogLevelToLogEventLevel (Enum.Parse<LogLevel> (UICatalog.Options.DebugLogLevel));
@@ -334,7 +342,7 @@ public class UICatalogTop : Toplevel
             menuItems.Add (
                            new MenuItemv2
                            {
-                               CommandView = _logLevelRg,
+                               CommandView = _logLevelSelector,
                                HelpText = "Cycle Through Log Levels",
                                Key = Key.L.WithCtrl
                            });
@@ -356,27 +364,27 @@ public class UICatalogTop : Toplevel
 
     private void UpdateThemesMenu ()
     {
-        if (_themesRg is null)
+        if (_themesSelector is null)
         {
             return;
         }
 
-        _themesRg.SelectedItem = null;
-        _themesRg.AssignHotKeysToCheckBoxes = true;
-        _themesRg.UsedHotKeys.Clear ();
-        _themesRg.Options = ThemeManager.GetThemeNames ();
-        _themesRg.SelectedItem =ThemeManager.GetThemeNames ().IndexOf (ThemeManager.GetCurrentThemeName ());
+        _themesSelector.Value = null;
+        _themesSelector.AssignHotKeys = true;
+        _themesSelector.UsedHotKeys.Clear ();
+        _themesSelector.Labels = ThemeManager.GetThemeNames ().ToArray ();
+        _themesSelector.Value = ThemeManager.GetThemeNames ().IndexOf (ThemeManager.GetCurrentThemeName ());
 
-        if (_topSchemeRg is null)
+        if (_topSchemesSelector is null)
         {
             return;
         }
 
-        _topSchemeRg.AssignHotKeysToCheckBoxes = true;
-        _topSchemeRg.UsedHotKeys.Clear ();
-        int? selectedScheme = _topSchemeRg.SelectedItem;
-        _topSchemeRg.Options = SchemeManager.GetSchemeNames ();
-        _topSchemeRg.SelectedItem = selectedScheme;
+        _topSchemesSelector.AssignHotKeys = true;
+        _topSchemesSelector.UsedHotKeys.Clear ();
+        int? selectedScheme = _topSchemesSelector.Value;
+        _topSchemesSelector.Labels = SchemeManager.GetSchemeNames ().ToArray ();
+        _topSchemesSelector.Value = selectedScheme;
 
         if (CachedTopLevelScheme is null || !SchemeManager.GetSchemeNames ().Contains (CachedTopLevelScheme))
         {
@@ -387,7 +395,7 @@ public class UICatalogTop : Toplevel
         // if the item is in bounds then select it
         if (newSelectedItem >= 0 && newSelectedItem < SchemeManager.GetSchemeNames ().Count)
         {
-            _topSchemeRg.SelectedItem = newSelectedItem;
+            _topSchemesSelector.Value = newSelectedItem;
         }
     }
 

+ 2 - 3
README.md

@@ -1,6 +1,5 @@
-![.NET Core](https://github.com/gui-cs/Terminal.Gui/workflows/.NET%20Core/badge.svg?branch=develop)
 [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui)
-![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27/raw/code-coverage.json)
+[![codecov](https://codecov.io/gh/gui-cs/Terminal.Gui/graph/badge.svg?token=1Ac9gyGtrj)](https://codecov.io/gh/gui-cs/Terminal.Gui)
 [![Downloads](https://img.shields.io/nuget/dt/Terminal.Gui)](https://www.nuget.org/packages/Terminal.Gui)
 [![License](https://img.shields.io/github/license/gui-cs/gui.cs.svg)](LICENSE)
 ![Bugs](https://img.shields.io/github/issues/gui-cs/gui.cs/bug)
@@ -86,7 +85,7 @@ Or, you can use the [Terminal.Gui.Templates](https://github.com/gui-cs/Terminal.
 
 # Contributing
 
-See [CONTRIBUTING.md](./CONTRIBUTING.md).
+See [CONTRIBUTING.md](CONTRIBUTING.md) for complete contribution guidelines.
 
 Debates on architecture and design can be found in Issues tagged with [design](https://github.com/gui-cs/Terminal.Gui/issues?q=is%3Aopen+is%3Aissue+label%3Av2+label%3Adesign).
 

+ 109 - 0
Scripts/Run-LocalCoverage.ps1

@@ -0,0 +1,109 @@
+# ------------------------------------------------------------
+# Run-LocalCoverage.ps1
+# Local-only: Unit + Parallel + Integration tests
+# Quiet, merged coverage in /Tests
+# ------------------------------------------------------------
+
+# 1. Define paths
+$testDir    = Join-Path $PWD "Tests"
+$covDir     = Join-Path $testDir "coverage"
+$reportDir  = Join-Path $testDir "report"
+$resultsDir = Join-Path $testDir "TestResults"
+$mergedFile = Join-Path $covDir "coverage.merged.cobertura.xml"
+
+# 2. Clean old results - INCLUDING TestResults directory
+Write-Host "Cleaning old coverage files and test results..."
+Remove-Item -Recurse -Force $covDir, $reportDir, $resultsDir -ErrorAction SilentlyContinue
+New-Item -ItemType Directory -Path $covDir, $reportDir -Force | Out-Null
+
+dotnet build --configuration Debug --no-restore
+
+# ------------------------------------------------------------
+# 3. Run UNIT TESTS (non-parallel)
+# ------------------------------------------------------------
+Write-Host "`nRunning UnitTests (quiet)..."
+dotnet test Tests/UnitTests `
+  --no-build `
+  --verbosity minimal `
+  --collect:"XPlat Code Coverage" `
+  --settings Tests/UnitTests/runsettings.coverage.xml `
+  --blame-hang-timeout 10s
+
+# ------------------------------------------------------------
+# 4. Run UNIT TESTS (parallel)
+# ------------------------------------------------------------
+Write-Host "`nRunning UnitTestsParallelizable (quiet)..."
+dotnet test Tests/UnitTestsParallelizable `
+  --no-build `
+  --verbosity minimal `
+  --collect:"XPlat Code Coverage" `
+  --settings Tests/UnitTestsParallelizable/runsettings.coverage.xml
+
+# ------------------------------------------------------------
+# 5. Run INTEGRATION TESTS
+# ------------------------------------------------------------
+Write-Host "`nRunning IntegrationTests (quiet)..."
+dotnet test Tests/IntegrationTests `
+  --no-build `
+  --verbosity minimal `
+  --collect:"XPlat Code Coverage" `
+  --settings Tests/IntegrationTests/runsettings.coverage.xml
+
+# ------------------------------------------------------------
+# 6. Find ALL coverage files (from all 3 projects) - NOW SCOPED TO Tests/TestResults
+# ------------------------------------------------------------
+Write-Host "`nCollecting coverage files..."
+$covFiles = Get-ChildItem -Path $resultsDir -Recurse -Filter coverage.cobertura.xml -File -ErrorAction SilentlyContinue
+
+if (-not $covFiles) {
+    Write-Error "No coverage files found in $resultsDir. Did all tests run successfully?"
+    exit 1
+}
+
+# ------------------------------------------------------------
+# 7. Move to Tests/coverage
+# ------------------------------------------------------------
+Write-Host "Moving $($covFiles.Count) coverage file(s) to $covDir..."
+$fileIndex = 1
+foreach ($f in $covFiles) {
+    $destFile = Join-Path $covDir "coverage.$fileIndex.cobertura.xml"
+    Copy-Item $f.FullName -Destination $destFile -Force
+    $fileIndex++
+}
+
+# ------------------------------------------------------------
+# 8. Merge into one file
+# ------------------------------------------------------------
+Write-Host "Merging coverage from all test projects..."
+dotnet-coverage merge `
+    -o $mergedFile `
+    -f cobertura `
+    "$covDir\*.cobertura.xml"
+
+# ------------------------------------------------------------
+# 9. Generate HTML + text report
+# ------------------------------------------------------------
+Write-Host "Generating final HTML report..."
+reportgenerator `
+    -reports:$mergedFile `
+    -targetdir:$reportDir `
+    -reporttypes:"Html;TextSummary"
+
+# ------------------------------------------------------------
+# 10. Show summary + open report
+# ------------------------------------------------------------
+Write-Host "`n=== Final Coverage Summary (Unit + Integration) ==="
+Get-Content "$reportDir\Summary.txt"
+
+$indexHtml = Join-Path $reportDir "index.html"
+if (Test-Path $indexHtml) {
+    Write-Host "Opening report in browser..."
+    Start-Process $indexHtml
+} else {
+    Write-Warning "HTML report not found at $indexHtml"
+}
+
+Write-Host "`nCoverage artifacts:"
+Write-Host "  - Merged coverage: $mergedFile"
+Write-Host "  - HTML report:     $reportDir\index.html"
+Write-Host "  - Test results:    $resultsDir"

+ 3 - 2
Terminal.Gui.Analyzers.Tests/HandledEventArgsAnalyzerTests.cs

@@ -1,7 +1,8 @@
-using Terminal.Gui.Input;
+using Terminal.Gui.Analyzers;
+using Terminal.Gui.Input;
 using Terminal.Gui.Views;
 
-namespace Terminal.Gui.Analyzers.Tests;
+namespace Analyzers.Tests;
 
 public class HandledEventArgsAnalyzerTests
 {

+ 62 - 56
Terminal.Gui.Analyzers.Tests/ProjectBuilder.cs

@@ -17,10 +17,10 @@ using JetBrains.Annotations;
 
 public sealed class ProjectBuilder
 {
-    private string _sourceCode;
-    private string _expectedFixedCode;
-    private DiagnosticAnalyzer _analyzer;
-    private CodeFixProvider _codeFix;
+    private string? _sourceCode;
+    private string? _expectedFixedCode;
+    private DiagnosticAnalyzer? _analyzer;
+    private CodeFixProvider? _codeFix;
 
     public ProjectBuilder WithSourceCode (string source)
     {
@@ -59,20 +59,22 @@ public sealed class ProjectBuilder
         }
 
         // Parse original document
-        var document = CreateDocument (_sourceCode);
-        var compilation = await document.Project.GetCompilationAsync ();
+        Document document = CreateDocument (_sourceCode);
+        Compilation? compilation = await document.Project.GetCompilationAsync ();
 
-        var diagnostics = compilation.GetDiagnostics ();
-        var errors = diagnostics.Where (d => d.Severity == DiagnosticSeverity.Error);
+        ImmutableArray<Diagnostic> diagnostics = compilation!.GetDiagnostics ();
+        IEnumerable<Diagnostic> errors = diagnostics.Where (d => d.Severity == DiagnosticSeverity.Error);
 
-        if (errors.Any ())
+        IEnumerable<Diagnostic> enumerable = errors as Diagnostic [] ?? errors.ToArray ();
+
+        if (enumerable.Any ())
         {
-            var errorMessages = string.Join (Environment.NewLine, errors.Select (e => e.ToString ()));
+            string errorMessages = string.Join (Environment.NewLine, enumerable.Select (e => e.ToString ()));
             throw new Exception ("Compilation failed with errors:" + Environment.NewLine + errorMessages);
         }
 
         // Run analyzer
-        var analyzerDiagnostics = await GetAnalyzerDiagnosticsAsync (compilation, _analyzer);
+        ImmutableArray<Diagnostic> analyzerDiagnostics = await GetAnalyzerDiagnosticsAsync (compilation, _analyzer);
 
         Assert.NotEmpty (analyzerDiagnostics);
 
@@ -83,83 +85,87 @@ public sealed class ProjectBuilder
                 throw new InvalidOperationException ("Expected code fix but none was set.");
             }
 
-            var fixedDocument = await ApplyCodeFixAsync (document, analyzerDiagnostics.First (), _codeFix);
+            Document? fixedDocument = await ApplyCodeFixAsync (document, analyzerDiagnostics.First (), _codeFix);
 
-            var formattedDocument = await Formatter.FormatAsync (fixedDocument);
-            var fixedSource = (await formattedDocument.GetTextAsync ()).ToString ();
+            if (fixedDocument is { })
+            {
+                Document formattedDocument = await Formatter.FormatAsync (fixedDocument);
+                string fixedSource = (await formattedDocument.GetTextAsync ()).ToString ();
 
-            Assert.Equal (_expectedFixedCode, fixedSource);
+                Assert.Equal (_expectedFixedCode, fixedSource);
+            }
         }
     }
 
     private static Document CreateDocument (string source)
     {
-        var dd = typeof (Enumerable).GetTypeInfo ().Assembly.Location;
-        var coreDir = Directory.GetParent (dd) ?? throw new Exception ($"Could not find parent directory of dotnet sdk.  Sdk directory was {dd}");
-
-        var workspace = new AdhocWorkspace ();
-        var projectId = ProjectId.CreateNewId ();
-        var documentId = DocumentId.CreateNewId (projectId);
-
-        var references = new List<MetadataReference> ()
-        {
-            MetadataReference.CreateFromFile(typeof(Button).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(View).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(System.IO.FileSystemInfo).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(MarshalByValueComponent).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(ObservableCollection<string>).Assembly.Location),
+        string dd = typeof (Enumerable).GetTypeInfo ().Assembly.Location;
+        DirectoryInfo coreDir = Directory.GetParent (dd) ?? throw new Exception ($"Could not find parent directory of dotnet sdk.  Sdk directory was {dd}");
+
+        AdhocWorkspace workspace = new AdhocWorkspace ();
+        ProjectId projectId = ProjectId.CreateNewId ();
+        DocumentId documentId = DocumentId.CreateNewId (projectId);
+
+        List<MetadataReference> references =
+        [
+            MetadataReference.CreateFromFile (typeof (Button).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (View).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (System.IO.FileSystemInfo).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (System.Linq.Enumerable).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (object).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (MarshalByValueComponent).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (ObservableCollection<string>).Assembly.Location),
 
             // New assemblies required by Terminal.Gui version 2
-            MetadataReference.CreateFromFile(typeof(Size).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(CanBeNullAttribute).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (Size).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (CanBeNullAttribute).Assembly.Location),
+
 
+            MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "mscorlib.dll")),
+            MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "System.Runtime.dll")),
+            MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "System.Collections.dll")),
+            MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "System.Data.Common.dll"))
 
-            MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "mscorlib.dll")),
-            MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Runtime.dll")),
-            MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Collections.dll")),
-            MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Data.Common.dll")),
             // Add more as necessary
-        };
+        ];
 
 
-        var projectInfo = ProjectInfo.Create (
-                                              projectId,
-                                              VersionStamp.Create (),
-                                              "TestProject",
-                                              "TestAssembly",
-                                              LanguageNames.CSharp,
-                                              compilationOptions: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary),
-                                              metadataReferences: references);
+        ProjectInfo projectInfo = ProjectInfo.Create (
+                                                      projectId,
+                                                      VersionStamp.Create (),
+                                                      "TestProject",
+                                                      "TestAssembly",
+                                                      LanguageNames.CSharp,
+                                                      compilationOptions: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary),
+                                                      metadataReferences: references);
 
-        var solution = workspace.CurrentSolution
-                                .AddProject (projectInfo)
-                                .AddDocument (documentId, "Test.cs", SourceText.From (source));
+        Solution solution = workspace.CurrentSolution
+                                     .AddProject (projectInfo)
+                                     .AddDocument (documentId, "Test.cs", SourceText.From (source));
 
         return solution.GetDocument (documentId)!;
     }
 
     private static async Task<ImmutableArray<Diagnostic>> GetAnalyzerDiagnosticsAsync (Compilation compilation, DiagnosticAnalyzer analyzer)
     {
-        var compilationWithAnalyzers = compilation.WithAnalyzers (ImmutableArray.Create (analyzer));
+        CompilationWithAnalyzers compilationWithAnalyzers = compilation.WithAnalyzers ([analyzer]);
         return await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync ();
     }
 
-    private static async Task<Document> ApplyCodeFixAsync (Document document, Diagnostic diagnostic, CodeFixProvider codeFix)
+    private static async Task<Document?> ApplyCodeFixAsync (Document document, Diagnostic diagnostic, CodeFixProvider codeFix)
     {
-        CodeAction _codeAction = null;
-        var context = new CodeFixContext ((TextDocument)document, diagnostic, (action, _) => _codeAction = action, CancellationToken.None);
+        CodeAction? codeAction = null;
+        var context = new CodeFixContext ((TextDocument)document, diagnostic, (action, _) => codeAction = action, CancellationToken.None);
 
         await codeFix.RegisterCodeFixesAsync (context);
 
-        if (_codeAction == null)
+        if (codeAction == null)
         {
             throw new InvalidOperationException ("Code fix did not register a fix.");
         }
 
-        var operations = await _codeAction.GetOperationsAsync (CancellationToken.None);
-        var solution = operations.OfType<ApplyChangesOperation> ().First ().ChangedSolution;
+        ImmutableArray<CodeActionOperation> operations = await codeAction.GetOperationsAsync (CancellationToken.None);
+        Solution solution = operations.OfType<ApplyChangesOperation> ().First ().ChangedSolution;
         return solution.GetDocument (document.Id);
     }
 }

+ 53 - 29
Terminal.Gui/App/Application.Driver.cs

@@ -1,38 +1,62 @@
 #nullable enable
 
+using System.Diagnostics.CodeAnalysis;
+
 namespace Terminal.Gui.App;
 
 public static partial class Application // Driver abstractions
 {
-    internal static bool _forceFakeConsole;
-
-    /// <summary>Gets the <see cref="IConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.</summary>
-    public static IConsoleDriver? Driver { get; internal set; }
+    /// <inheritdoc cref="IApplication.Driver"/>
+    public static IDriver? Driver
+    {
+        get => ApplicationImpl.Instance.Driver;
+        internal set => ApplicationImpl.Instance.Driver = value;
+    }
 
-    // BUGBUG: Force16Colors should be nullable.
-    /// <summary>
-    ///     Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in
-    ///     <see cref="ColorName16"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be output
-    ///     as long as the selected <see cref="IConsoleDriver"/> supports TrueColor.
-    /// </summary>
+    /// <inheritdoc cref="IApplication.Force16Colors"/>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
-    public static bool Force16Colors { get; set; }
-
-    // BUGBUG: ForceDriver should be nullable.
-    /// <summary>
-    ///     Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If not
-    ///     specified, the driver is selected based on the platform.
-    /// </summary>
-    /// <remarks>
-    ///     Note, <see cref="Application.Init(IConsoleDriver, string)"/> will override this configuration setting if called
-    ///     with either `driver` or `driverName` specified.
-    /// </remarks>
+    public static bool Force16Colors
+    {
+        get => ApplicationImpl.Instance.Force16Colors;
+        set => ApplicationImpl.Instance.Force16Colors = value;
+    }
+
+    /// <inheritdoc cref="IApplication.ForceDriver"/>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
-    public static string ForceDriver { get; set; } = string.Empty;
-
-    /// <summary>
-    /// Collection of sixel images to write out to screen when updating.
-    /// Only add to this collection if you are sure terminal supports sixel format.
-    /// </summary>
-    public static List<SixelToRender> Sixel = new List<SixelToRender> ();
-}
+    public static string ForceDriver
+    {
+        get => ApplicationImpl.Instance.ForceDriver;
+        set => ApplicationImpl.Instance.ForceDriver = value;
+    }
+
+    /// <inheritdoc cref="IApplication.Sixel"/>
+    public static List<SixelToRender> Sixel => ApplicationImpl.Instance.Sixel;
+
+    /// <summary>Gets a list of <see cref="IDriver"/> types and type names that are available.</summary>
+    /// <returns></returns>
+    [RequiresUnreferencedCode ("AOT")]
+    public static (List<Type?>, List<string?>) GetDriverTypes ()
+    {
+        // use reflection to get the list of drivers
+        List<Type?> driverTypes = new ();
+
+        // Only inspect the IDriver assembly
+        var asm = typeof (IDriver).Assembly;
+
+        foreach (Type? type in asm.GetTypes ())
+        {
+            if (typeof (IDriver).IsAssignableFrom (type) && type is { IsAbstract: false, IsClass: true })
+            {
+                driverTypes.Add (type);
+            }
+        }
+
+        List<string?> driverTypeNames = driverTypes
+                                        .Where (d => !typeof (IDriver).IsAssignableFrom (d))
+                                        .Select (d => d!.Name)
+                                        .Union (["dotnet", "windows", "unix", "fake"])
+                                        .ToList ()!;
+
+        return (driverTypes, driverTypeNames);
+    }
+}

+ 0 - 260
Terminal.Gui/App/Application.Initialization.cs

@@ -1,260 +0,0 @@
-#nullable enable
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Reflection;
-
-namespace Terminal.Gui.App;
-
-public static partial class Application // Initialization (Init/Shutdown)
-{
-
-    /// <summary>Initializes a new instance of a Terminal.Gui Application. <see cref="Shutdown"/> must be called when the application is closing.</summary>
-    /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
-    /// <para>
-    ///     This function loads the right <see cref="IConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
-    ///     assigns it to <see cref="Top"/>
-    /// </para>
-    /// <para>
-    ///     <see cref="Shutdown"/> must be called when the application is closing (typically after
-    ///     <see cref="Run{T}"/> has returned) to ensure resources are cleaned up and
-    ///     terminal settings
-    ///     restored.
-    /// </para>
-    /// <para>
-    ///     The <see cref="Run{T}"/> function combines
-    ///     <see cref="Init(IConsoleDriver,string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
-    ///     into a single
-    ///     call. An application cam use <see cref="Run{T}"/> without explicitly calling
-    ///     <see cref="Init(IConsoleDriver,string)"/>.
-    /// </para>
-    /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or
-    ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
-    /// </param>
-    /// <param name="driverName">
-    ///     The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the
-    ///     <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
-    ///     specified the default driver for the platform will be used.
-    /// </param>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    public static void Init (IConsoleDriver? driver = null, string? driverName = null)
-    {
-        ApplicationImpl.Instance.Init (driver, driverName);
-    }
-
-    internal static int MainThreadId { get; set; } = -1;
-
-    // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop.
-    //
-    // Called from:
-    //
-    // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset.
-    // Run<T>() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run<T>() to be called without calling Init first.
-    // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset.
-    //
-    // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset.
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    internal static void InternalInit (
-        IConsoleDriver? driver = null,
-        string? driverName = null,
-        bool calledViaRunT = false
-    )
-    {
-        if (Initialized && driver is null)
-        {
-            return;
-        }
-
-        if (Initialized)
-        {
-            throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown.");
-        }
-
-        if (!calledViaRunT)
-        {
-            // Reset all class variables (Application is a singleton).
-            ResetState (ignoreDisposed: true);
-        }
-
-        // For UnitTests
-        if (driver is { })
-        {
-            Driver = driver;
-        }
-
-        // Ignore Configuration for ForceDriver if driverName is specified
-        if (!string.IsNullOrEmpty (driverName))
-        {
-            ForceDriver = driverName;
-        }
-
-        if (Driver is null)
-        {
-            PlatformID p = Environment.OSVersion.Platform;
-
-            if (string.IsNullOrEmpty (ForceDriver))
-            {
-                if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
-                {
-                    Driver = new WindowsDriver ();
-                }
-                else
-                {
-                    Driver = new CursesDriver ();
-                }
-            }
-            else
-            {
-                (List<Type?> drivers, List<string?> driverTypeNames) = GetDriverTypes ();
-                Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase));
-
-                if (driverType is { })
-                {
-                    Driver = (IConsoleDriver)Activator.CreateInstance (driverType)!;
-                }
-                else if (ForceDriver?.StartsWith ("v2") ?? false)
-                {
-                    ApplicationImpl.ChangeInstance (new ApplicationV2 ());
-                    ApplicationImpl.Instance.Init (driver, ForceDriver);
-                    Debug.Assert (Driver is { });
-
-                    return;
-                }
-                else
-                {
-                    throw new ArgumentException (
-                                                 $"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t!.Name))}"
-                                                );
-                }
-            }
-        }
-
-        Debug.Assert (Navigation is null);
-        Navigation = new ();
-
-        Debug.Assert (Popover is null);
-        Popover = new ();
-
-        AddKeyBindings ();
-
-        try
-        {
-            MainLoop = Driver!.Init ();
-            SubscribeDriverEvents ();
-        }
-        catch (InvalidOperationException ex)
-        {
-            // This is a case where the driver is unable to initialize the console.
-            // This can happen if the console is already in use by another process or
-            // if running in unit tests.
-            // In this case, we want to throw a more specific exception.
-            throw new InvalidOperationException (
-                                                 "Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.",
-                                                 ex
-                                                );
-        }
-
-        SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
-
-        // TODO: This is probably not needed
-        if (Popover.GetActivePopover () is View popover)
-        {
-            popover.Visible = false;
-        }
-
-        MainThreadId = Thread.CurrentThread.ManagedThreadId;
-        bool init = Initialized = true;
-        InitializedChanged?.Invoke (null, new (init));
-    }
-
-    internal static void SubscribeDriverEvents ()
-    {
-        ArgumentNullException.ThrowIfNull (Driver);
-
-        Driver.SizeChanged += Driver_SizeChanged;
-        Driver.KeyDown += Driver_KeyDown;
-        Driver.KeyUp += Driver_KeyUp;
-        Driver.MouseEvent += Driver_MouseEvent;
-    }
-
-    internal static void UnsubscribeDriverEvents ()
-    {
-        ArgumentNullException.ThrowIfNull (Driver);
-
-        Driver.SizeChanged -= Driver_SizeChanged;
-        Driver.KeyDown -= Driver_KeyDown;
-        Driver.KeyUp -= Driver_KeyUp;
-        Driver.MouseEvent -= Driver_MouseEvent;
-    }
-
-    private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
-    private static void Driver_KeyDown (object? sender, Key e) { RaiseKeyDownEvent (e); }
-    private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); }
-    private static void Driver_MouseEvent (object? sender, MouseEventArgs e) { RaiseMouseEvent (e); }
-
-    /// <summary>Gets of list of <see cref="IConsoleDriver"/> types and type names that are available.</summary>
-    /// <returns></returns>
-    [RequiresUnreferencedCode ("AOT")]
-    public static (List<Type?>, List<string?>) GetDriverTypes ()
-    {
-        // use reflection to get the list of drivers
-        List<Type?> driverTypes = new ();
-
-        // Only inspect the IConsoleDriver assembly
-        var asm = typeof (IConsoleDriver).Assembly;
-
-        foreach (Type? type in asm.GetTypes ())
-        {
-            if (typeof (IConsoleDriver).IsAssignableFrom (type) &&
-                type is { IsAbstract: false, IsClass: true })
-            {
-                driverTypes.Add (type);
-            }
-        }
-
-        List<string?> driverTypeNames = driverTypes
-                                        .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
-                                        .Select (d => d!.Name)
-                                        .Union (["v2", "v2win", "v2net", "v2unix"])
-                                        .ToList ()!;
-
-        return (driverTypes, driverTypeNames);
-    }
-
-    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
-    /// <remarks>
-    ///     Shutdown must be called for every call to <see cref="Init"/> or
-    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
-    ///     up (Disposed)
-    ///     and terminal settings are restored.
-    /// </remarks>
-    public static void Shutdown () => ApplicationImpl.Instance.Shutdown ();
-
-    /// <summary>
-    ///     Gets whether the application has been initialized with <see cref="Init"/> and not yet shutdown with <see cref="Shutdown"/>.
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    ///     The <see cref="InitializedChanged"/> event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
-    /// </para>
-    /// </remarks>
-    public static bool Initialized { get; internal set; }
-
-    /// <summary>
-    ///     This event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
-    /// </summary>
-    /// <remarks>
-    ///     Intended to support unit tests that need to know when the application has been initialized.
-    /// </remarks>
-    public static event EventHandler<EventArgs<bool>>? InitializedChanged;
-
-    /// <summary>
-    ///  Raises the <see cref="InitializedChanged"/> event.
-    /// </summary>
-    internal static void OnInitializedChanged (object sender, EventArgs<bool> e)
-    {
-        Application.InitializedChanged?.Invoke (sender, e);
-    }
-}

+ 19 - 305
Terminal.Gui/App/Application.Keyboard.cs

@@ -4,144 +4,22 @@ namespace Terminal.Gui.App;
 
 public static partial class Application // Keyboard handling
 {
-    /// <summary>
-    ///     Called when the user presses a key (by the <see cref="IConsoleDriver"/>). Raises the cancelable
-    ///     <see cref="KeyDown"/> event, then calls <see cref="View.NewKeyDownEvent"/> on all top level views, and finally
-    ///     if the key was not handled, invokes any Application-scoped <see cref="KeyBindings"/>.
-    /// </summary>
-    /// <remarks>Can be used to simulate key press events.</remarks>
-    /// <param name="key"></param>
-    /// <returns><see langword="true"/> if the key was handled.</returns>
-    public static bool RaiseKeyDownEvent (Key key)
+    /// <inheritdoc cref="IApplication.Keyboard"/>
+    public static IKeyboard Keyboard
     {
-        Logging.Debug ($"{key}");
-
-        // TODO: Add a way to ignore certain keys, esp for debugging.
-        //#if DEBUG
-        //        if (key == Key.Empty.WithAlt || key == Key.Empty.WithCtrl)
-        //        {
-        //            Logging.Debug ($"Ignoring {key}");
-        //            return false;
-        //        }
-        //#endif
-
-        // TODO: This should match standard event patterns
-        KeyDown?.Invoke (null, key);
-
-        if (key.Handled)
-        {
-            return true;
-        }
-
-        if (Popover?.DispatchKeyDown (key) is true)
-        {
-            return true;
-        }
-
-        if (Top is null)
-        {
-            foreach (Toplevel topLevel in TopLevels.ToList ())
-            {
-                if (topLevel.NewKeyDownEvent (key))
-                {
-                    return true;
-                }
-
-                if (topLevel.Modal)
-                {
-                    break;
-                }
-            }
-        }
-        else
-        {
-            if (Top.NewKeyDownEvent (key))
-            {
-                return true;
-            }
-        }
-
-        bool? commandHandled = InvokeCommandsBoundToKey (key);
-        if(commandHandled is true)
-        {
-            return true;
-        }
-
-        return false;
+        get => ApplicationImpl.Instance.Keyboard;
+        set => ApplicationImpl.Instance.Keyboard = value ??
+                                                           throw new ArgumentNullException(nameof(value));
     }
 
-    /// <summary>
-    ///     Invokes any commands bound at the Application-level to <paramref name="key"/>.
-    /// </summary>
-    /// <param name="key"></param>
-    /// <returns>
-    ///     <see langword="null"/> if no command was found; input processing should continue.
-    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input processing should continue.
-    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
-    /// </returns>
-    public static bool? InvokeCommandsBoundToKey (Key key)
-    {
-        bool? handled = null;
-        // Invoke any Application-scoped KeyBindings.
-        // The first view that handles the key will stop the loop.
-        // foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
-        if (KeyBindings.TryGet (key, out KeyBinding binding))
-        {
-            if (binding.Target is { })
-            {
-                if (!binding.Target.Enabled)
-                {
-                    return null;
-                }
+    /// <inheritdoc cref="IKeyboard.RaiseKeyDownEvent"/>
+    public static bool RaiseKeyDownEvent (Key key) => ApplicationImpl.Instance.Keyboard.RaiseKeyDownEvent (key);
 
-                handled = binding.Target?.InvokeCommands (binding.Commands, binding);
-            }
-            else
-            {
-                bool? toReturn = null;
+    /// <inheritdoc cref="IKeyboard.InvokeCommandsBoundToKey"/>
+    public static bool? InvokeCommandsBoundToKey (Key key) => ApplicationImpl.Instance.Keyboard.InvokeCommandsBoundToKey (key);
 
-                foreach (Command command in binding.Commands)
-                {
-                    toReturn = InvokeCommand (command, key, binding);
-                }
-
-                handled = toReturn ?? true;
-            }
-        }
-
-        return handled;
-    }
-
-    /// <summary>
-    ///     Invokes an Application-bound command.
-    /// </summary>
-    /// <param name="command">The Command to invoke</param>
-    /// <param name="key">The Application-bound Key that was pressed.</param>
-    /// <param name="binding">Describes the binding.</param>
-    /// <returns>
-    ///     <see langword="null"/> if no command was found; input processing should continue.
-    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input processing should continue.
-    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
-    /// </returns>
-    /// <exception cref="NotSupportedException"></exception>
-    public static bool? InvokeCommand (Command command, Key key, KeyBinding binding)
-    {
-        if (!_commandImplementations!.ContainsKey (command))
-        {
-            throw new NotSupportedException (
-                                             @$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by Application."
-                                            );
-        }
-
-        if (_commandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
-        {
-            CommandContext<KeyBinding> context = new (command, null, binding); // Create the context here
-
-            return implementation (context);
-        }
-
-        return null;
-    }
+    /// <inheritdoc cref="IKeyboard.InvokeCommand"/>
+    public static bool? InvokeCommand (Command command, Key key, KeyBinding binding) => ApplicationImpl.Instance.Keyboard.InvokeCommand (command, key, binding);
 
     /// <summary>
     ///     Raised when the user presses a key.
@@ -151,183 +29,19 @@ public static partial class Application // Keyboard handling
     ///     </para>
     /// </summary>
     /// <remarks>
-    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Unix) do not support firing the
     ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
     ///     <para>Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.</para>
     /// </remarks>
-    public static event EventHandler<Key>? KeyDown;
-
-    /// <summary>
-    ///     Called when the user releases a key (by the <see cref="IConsoleDriver"/>). Raises the cancelable
-    ///     <see cref="KeyUp"/>
-    ///     event
-    ///     then calls <see cref="View.NewKeyUpEvent"/> on all top level views. Called after <see cref="RaiseKeyDownEvent"/>.
-    /// </summary>
-    /// <remarks>Can be used to simulate key release events.</remarks>
-    /// <param name="key"></param>
-    /// <returns><see langword="true"/> if the key was handled.</returns>
-    public static bool RaiseKeyUpEvent (Key key)
+    public static event EventHandler<Key>? KeyDown
     {
-        if (!Initialized)
-        {
-            return true;
-        }
-
-        KeyUp?.Invoke (null, key);
-
-        if (key.Handled)
-        {
-            return true;
-        }
-
-
-        // TODO: Add Popover support
-
-        foreach (Toplevel topLevel in TopLevels.ToList ())
-        {
-            if (topLevel.NewKeyUpEvent (key))
-            {
-                return true;
-            }
-
-            if (topLevel.Modal)
-            {
-                break;
-            }
-        }
-
-        return false;
+        add => ApplicationImpl.Instance.Keyboard.KeyDown += value;
+        remove => ApplicationImpl.Instance.Keyboard.KeyDown -= value;
     }
 
-    #region Application-scoped KeyBindings
-
-    static Application ()
-    {
-        AddKeyBindings ();
-    }
+    /// <inheritdoc cref="IKeyboard.RaiseKeyUpEvent"/>
+    public static bool RaiseKeyUpEvent (Key key) => ApplicationImpl.Instance.Keyboard.RaiseKeyUpEvent (key);
 
-    /// <summary>Gets the Application-scoped key bindings.</summary>
-    public static KeyBindings KeyBindings { get; internal set; } = new (null);
-
-    internal static void AddKeyBindings ()
-    {
-        _commandImplementations.Clear ();
-
-        // Things Application knows how to do
-        AddCommand (
-                    Command.Quit,
-                    static () =>
-                    {
-                        RequestStop ();
-
-                        return true;
-                    }
-                   );
-        AddCommand (
-                    Command.Suspend,
-                    static () =>
-                    {
-                        Driver?.Suspend ();
-
-                        return true;
-                    }
-                   );
-        AddCommand (
-                    Command.NextTabStop,
-                    static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));
-
-        AddCommand (
-                    Command.PreviousTabStop,
-                    static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop));
-
-        AddCommand (
-                    Command.NextTabGroup,
-                    static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup));
-
-        AddCommand (
-                    Command.PreviousTabGroup,
-                    static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup));
-
-        AddCommand (
-                    Command.Refresh,
-                    static () =>
-                    {
-                        LayoutAndDraw (true);
-
-                        return true;
-                    }
-                   );
-
-        AddCommand (
-                    Command.Arrange,
-                    static () =>
-                    {
-                        View? viewToArrange = Navigation?.GetFocused ();
-
-                        // Go up the superview hierarchy and find the first that is not ViewArrangement.Fixed
-                        while (viewToArrange is { SuperView: { }, Arrangement: ViewArrangement.Fixed })
-                        {
-                            viewToArrange = viewToArrange.SuperView;
-                        }
-
-                        if (viewToArrange is { })
-                        {
-                            return viewToArrange.Border?.EnterArrangeMode (ViewArrangement.Fixed);
-                        }
-
-                        return false;
-                    });
-
-        //SetKeysToHardCodedDefaults ();
-
-        // Need to clear after setting the above to ensure actually clear
-        // because set_QuitKey etc.. may call Add
-        KeyBindings.Clear ();
-
-        KeyBindings.Add (QuitKey, Command.Quit);
-        KeyBindings.Add (NextTabKey, Command.NextTabStop);
-        KeyBindings.Add (PrevTabKey, Command.PreviousTabStop);
-        KeyBindings.Add (NextTabGroupKey, Command.NextTabGroup);
-        KeyBindings.Add (PrevTabGroupKey, Command.PreviousTabGroup);
-        KeyBindings.Add (ArrangeKey, Command.Arrange);
-
-        KeyBindings.Add (Key.CursorRight, Command.NextTabStop);
-        KeyBindings.Add (Key.CursorDown, Command.NextTabStop);
-        KeyBindings.Add (Key.CursorLeft, Command.PreviousTabStop);
-        KeyBindings.Add (Key.CursorUp, Command.PreviousTabStop);
-
-        // TODO: Refresh Key should be configurable
-        KeyBindings.Add (Key.F5, Command.Refresh);
-
-        // TODO: Suspend Key should be configurable
-        if (Environment.OSVersion.Platform == PlatformID.Unix)
-        {
-            KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
-        }
-    }
-
-    #endregion Application-scoped KeyBindings
-
-    /// <summary>
-    ///     <para>
-    ///         Sets the function that will be invoked for a <see cref="Command"/>.
-    ///     </para>
-    ///     <para>
-    ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
-    ///         replace the old one.
-    ///     </para>
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         This version of AddCommand is for commands that do not require a <see cref="ICommandContext"/>.
-    ///     </para>
-    /// </remarks>
-    /// <param name="command">The command.</param>
-    /// <param name="f">The function.</param>
-    private static void AddCommand (Command command, Func<bool?> f) { _commandImplementations! [command] = ctx => f (); }
-
-    /// <summary>
-    ///     Commands for Application.
-    /// </summary>
-    private static readonly Dictionary<Command, View.CommandImplementation> _commandImplementations = new ();
+    /// <inheritdoc cref="IKeyboard.KeyBindings"/>
+    public static KeyBindings KeyBindings => ApplicationImpl.Instance.Keyboard.KeyBindings;
 }

+ 95 - 0
Terminal.Gui/App/Application.Lifecycle.cs

@@ -0,0 +1,95 @@
+#nullable enable
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using Microsoft.VisualBasic;
+using Terminal.Gui.App;
+using Terminal.Gui.Drivers;
+using Terminal.Gui.Views;
+
+namespace Terminal.Gui.App;
+
+public static partial class Application // Lifecycle (Init/Shutdown)
+{
+
+    /// <summary>Initializes a new instance of a Terminal.Gui Application. <see cref="Shutdown"/> must be called when the application is closing.</summary>
+    /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
+    /// <para>
+    ///     This function loads the right <see cref="IDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
+    ///     assigns it to <see cref="Top"/>
+    /// </para>
+    /// <para>
+    ///     <see cref="Shutdown"/> must be called when the application is closing (typically after
+    ///     <see cref="Run{T}"/> has returned) to ensure resources are cleaned up and
+    ///     terminal settings
+    ///     restored.
+    /// </para>
+    /// <para>
+    ///     The <see cref="Run{T}"/> function combines
+    ///     <see cref="Init(IDriver,string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
+    ///     into a single
+    ///     call. An application can use <see cref="Run{T}"/> without explicitly calling
+    ///     <see cref="Init(IDriver,string)"/>.
+    /// </para>
+    /// <param name="driver">
+    ///     The <see cref="IDriver"/> to use. If neither <paramref name="driver"/> or
+    ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
+    /// </param>
+    /// <param name="driverName">
+    ///     The short name (e.g. "dotnet", "windows", "unix", or "fake") of the
+    ///     <see cref="IDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
+    ///     specified the default driver for the platform will be used.
+    /// </param>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public static void Init (IDriver? driver = null, string? driverName = null)
+    {
+        ApplicationImpl.Instance.Init (driver, driverName ?? ForceDriver);
+    }
+
+    /// <summary>
+    ///     Gets or sets the main thread ID for the application.
+    /// </summary>
+    public static int? MainThreadId
+    {
+        get => ((ApplicationImpl)ApplicationImpl.Instance).MainThreadId;
+        set => ((ApplicationImpl)ApplicationImpl.Instance).MainThreadId = value;
+    }
+
+    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    /// <remarks>
+    ///     Shutdown must be called for every call to <see cref="Init"/> or
+    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
+    ///     up (Disposed)
+    ///     and terminal settings are restored.
+    /// </remarks>
+    public static void Shutdown () => ApplicationImpl.Instance.Shutdown ();
+
+    /// <summary>
+    ///     Gets whether the application has been initialized with <see cref="Init"/> and not yet shutdown with <see cref="Shutdown"/>.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    ///     The <see cref="InitializedChanged"/> event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
+    /// </para>
+    /// </remarks>
+    public static bool Initialized
+    {
+        get => ApplicationImpl.Instance.Initialized;
+        internal set => ApplicationImpl.Instance.Initialized = value;
+    }
+
+    /// <inheritdoc cref="IApplication.InitializedChanged"/>
+    public static event EventHandler<EventArgs<bool>>? InitializedChanged
+    {
+        add => ApplicationImpl.Instance.InitializedChanged += value;
+        remove => ApplicationImpl.Instance.InitializedChanged -= value;
+    }
+
+    // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
+    // Encapsulate all setting of initial state for Application; Having
+    // this in a function like this ensures we don't make mistakes in
+    // guaranteeing that the state of this singleton is deterministic when Init
+    // starts running and after Shutdown returns.
+    internal static void ResetState (bool ignoreDisposed = false) => ApplicationImpl.Instance?.ResetState (ignoreDisposed);
+}

+ 48 - 252
Terminal.Gui/App/Application.Mouse.cs

@@ -5,176 +5,39 @@ namespace Terminal.Gui.App;
 
 public static partial class Application // Mouse handling
 {
-    /// <summary>
-    /// INTERNAL API: Holds the last mouse position.
-    /// </summary>
-    internal static Point? LastMousePosition { get; set; }
-
     /// <summary>
     ///     Gets the most recent position of the mouse.
     /// </summary>
-    public static Point? GetLastMousePosition () { return LastMousePosition; }
+    public static Point? GetLastMousePosition () { return Mouse.GetLastMousePosition (); }
 
     /// <summary>Disable or enable the mouse. The mouse is enabled by default.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
-    public static bool IsMouseDisabled { get; set; }
-
-    /// <summary>
-    /// Static reference to the current <see cref="IApplication"/> <see cref="IMouseGrabHandler"/>.
-    /// </summary>
-    public static IMouseGrabHandler MouseGrabHandler
+    public static bool IsMouseDisabled
     {
-        get => ApplicationImpl.Instance.MouseGrabHandler;
-        set => ApplicationImpl.Instance.MouseGrabHandler = value ??
-                                                           throw new ArgumentNullException(nameof(value));
+        get => Mouse.IsMouseDisabled;
+        set => Mouse.IsMouseDisabled = value;
     }
 
     /// <summary>
-    ///     INTERNAL API: Called when a mouse event is raised by the driver. Determines the view under the mouse and
-    ///     calls the appropriate View mouse event handlers.
+    ///     Gets the <see cref="IMouse"/> instance that manages mouse event handling and state.
     /// </summary>
-    /// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
-    /// <param name="mouseEvent">The mouse event with coordinates relative to the screen.</param>
-    internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
-    {
-        if (Initialized)
-        {
-            // LastMousePosition is a static; only set if the application is initialized.
-            LastMousePosition = mouseEvent.ScreenPosition;
-        }
-
-        if (IsMouseDisabled)
-        {
-            return;
-        }
-
-        // The position of the mouse is the same as the screen position at the application level.
-        //Debug.Assert (mouseEvent.Position == mouseEvent.ScreenPosition);
-        mouseEvent.Position = mouseEvent.ScreenPosition;
-
-        List<View?> currentViewsUnderMouse = View.GetViewsUnderLocation (mouseEvent.ScreenPosition, ViewportSettingsFlags.TransparentMouse);
-
-        View? deepestViewUnderMouse = currentViewsUnderMouse.LastOrDefault ();
-
-        if (deepestViewUnderMouse is { })
-        {
-#if DEBUG_IDISPOSABLE
-            if (View.EnableDebugIDisposableAsserts && deepestViewUnderMouse.WasDisposed)
-            {
-                throw new ObjectDisposedException (deepestViewUnderMouse.GetType ().FullName);
-            }
-#endif
-            mouseEvent.View = deepestViewUnderMouse;
-        }
-
-        MouseEvent?.Invoke (null, mouseEvent);
-
-        if (mouseEvent.Handled)
-        {
-            return;
-        }
-
-        // Dismiss the Popover if the user presses mouse outside of it
-        if (mouseEvent.IsPressed
-            && Popover?.GetActivePopover () as View is { Visible: true } visiblePopover
-            && View.IsInHierarchy (visiblePopover, deepestViewUnderMouse, includeAdornments: true) is false)
-        {
-            ApplicationPopover.HideWithQuitCommand (visiblePopover);
-
-            // Recurse once so the event can be handled below the popover
-            RaiseMouseEvent (mouseEvent);
-
-            return;
-        }
-
-        if (HandleMouseGrab (deepestViewUnderMouse, mouseEvent))
-        {
-            return;
-        }
-
-        // May be null before the prior condition or the condition may set it as null.
-        // So, the checking must be outside the prior condition.
-        if (deepestViewUnderMouse is null)
-        {
-            return;
-        }
-
-        // if the mouse is outside the Application.Top or Application.Popover hierarchy, we don't want to
-        // send the mouse event to the deepest view under the mouse.
-        if (!View.IsInHierarchy (Application.Top, deepestViewUnderMouse, true) && !View.IsInHierarchy (Popover?.GetActivePopover () as View, deepestViewUnderMouse, true))
-        {
-            return;
-        }
-
-        // Create a view-relative mouse event to send to the view that is under the mouse.
-        MouseEventArgs viewMouseEvent;
-
-        if (deepestViewUnderMouse is Adornment adornment)
-        {
-            Point frameLoc = adornment.ScreenToFrame (mouseEvent.ScreenPosition);
-
-            viewMouseEvent = new ()
-            {
-                Position = frameLoc,
-                Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.ScreenPosition,
-                View = deepestViewUnderMouse
-            };
-        }
-        else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.ScreenPosition))
-        {
-            Point viewportLocation = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
-
-            viewMouseEvent = new ()
-            {
-                Position = viewportLocation,
-                Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.ScreenPosition,
-                View = deepestViewUnderMouse
-            };
-        }
-        else
-        {
-            // The mouse was outside any View's Viewport.
-            // Debug.Fail ("This should never happen. If it does please file an Issue!!");
-
-            return;
-        }
-
-        RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse);
-
-        while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabHandler.MouseGrabView is not { })
-        {
-            if (deepestViewUnderMouse is Adornment adornmentView)
-            {
-                deepestViewUnderMouse = adornmentView.Parent?.SuperView;
-            }
-            else
-            {
-                deepestViewUnderMouse = deepestViewUnderMouse.SuperView;
-            }
-
-            if (deepestViewUnderMouse is null)
-            {
-                break;
-            }
-
-            Point boundsPoint = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
-
-            viewMouseEvent = new ()
-            {
-                Position = boundsPoint,
-                Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.ScreenPosition,
-                View = deepestViewUnderMouse
-            };
-        }
-    }
-
+    /// <remarks>
+    ///     <para>
+    ///         This property provides access to mouse-related functionality in a way that supports
+    ///         parallel test execution by avoiding static state.
+    ///     </para>
+    ///     <para>
+    ///         New code should use <c>Application.Mouse</c> instead of the static properties and methods
+    ///         for better testability. Legacy static properties like <see cref="IsMouseDisabled"/> and
+    ///         <see cref="GetLastMousePosition"/> are retained for backward compatibility.
+    ///     </para>
+    /// </remarks>
+    public static IMouse Mouse => ApplicationImpl.Instance.Mouse;
 
 #pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
     /// <summary>
-    /// Raised when a mouse event occurs. Can be cancelled by setting <see cref="HandledEventArgs.Handled"/> to <see langword="true"/>.
+    ///     Raised when a mouse event occurs. Can be cancelled by setting <see cref="HandledEventArgs.Handled"/> to
+    ///     <see langword="true"/>.
     /// </summary>
     /// <remarks>
     ///     <para>
@@ -184,59 +47,34 @@ public static partial class Application // Mouse handling
     ///         <see cref="MouseEventArgs.View"/> will be the deepest view under the mouse.
     ///     </para>
     ///     <para>
-    ///         <see cref="MouseEventArgs.Position"/> coordinates are view-relative. Only valid if <see cref="MouseEventArgs.View"/> is set.
+    ///         <see cref="MouseEventArgs.Position"/> coordinates are view-relative. Only valid if
+    ///         <see cref="MouseEventArgs.View"/> is set.
     ///     </para>
     ///     <para>
     ///         Use this even to handle mouse events at the application level, before View-specific handling.
     ///     </para>
     /// </remarks>
-    public static event EventHandler<MouseEventArgs>? MouseEvent;
-#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
-
-    internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent)
+    public static event EventHandler<MouseEventArgs>? MouseEvent
     {
-        if (MouseGrabHandler.MouseGrabView is { })
-        {
-#if DEBUG_IDISPOSABLE
-            if (View.EnableDebugIDisposableAsserts && MouseGrabHandler.MouseGrabView.WasDisposed)
-            {
-                throw new ObjectDisposedException (MouseGrabHandler.MouseGrabView.GetType ().FullName);
-            }
-#endif
-
-            // If the mouse is grabbed, send the event to the view that grabbed it.
-            // The coordinates are relative to the Bounds of the view that grabbed the mouse.
-            Point frameLoc = MouseGrabHandler.MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
-
-            var viewRelativeMouseEvent = new MouseEventArgs
-            {
-                Position = frameLoc,
-                Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.ScreenPosition,
-                View = deepestViewUnderMouse ?? MouseGrabHandler.MouseGrabView
-            };
-
-            //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
-            if (MouseGrabHandler.MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
-            {
-                return true;
-            }
-
-            // ReSharper disable once ConditionIsAlwaysTrueOrFalse
-            if (MouseGrabHandler.MouseGrabView is null && deepestViewUnderMouse is Adornment)
-            {
-                // The view that grabbed the mouse has been disposed
-                return true;
-            }
-        }
-
-        return false;
+        add => Mouse.MouseEvent += value;
+        remove => Mouse.MouseEvent -= value;
     }
+#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
 
     /// <summary>
-    ///     INTERNAL: Holds the non-<see cref="ViewportSettingsFlags.TransparentMouse"/> views that are currently under the mouse.
+    ///     INTERNAL: Holds the non-<see cref="ViewportSettingsFlags.TransparentMouse"/> views that are currently under the
+    ///     mouse.
     /// </summary>
-    internal static List<View?> CachedViewsUnderMouse { get; } = [];
+    internal static List<View?> CachedViewsUnderMouse => Mouse.CachedViewsUnderMouse;
+
+    /// <summary>
+    ///     INTERNAL API: Holds the last mouse position.
+    /// </summary>
+    internal static Point? LastMousePosition
+    {
+        get => Mouse.LastMousePosition;
+        set => Mouse.LastMousePosition = value;
+    }
 
     /// <summary>
     ///     INTERNAL: Raises the MouseEnter and MouseLeave events for the views that are under the mouse.
@@ -245,59 +83,17 @@ public static partial class Application // Mouse handling
     /// <param name="currentViewsUnderMouse">The most recent result from GetViewsUnderLocation().</param>
     internal static void RaiseMouseEnterLeaveEvents (Point screenPosition, List<View?> currentViewsUnderMouse)
     {
-        // Tell any views that are no longer under the mouse that the mouse has left
-        List<View?> viewsToLeave = CachedViewsUnderMouse.Where (v => v is { } && !currentViewsUnderMouse.Contains (v)).ToList ();
-
-        foreach (View? view in viewsToLeave)
-        {
-            if (view is null)
-            {
-                continue;
-            }
-
-            view.NewMouseLeaveEvent ();
-            CachedViewsUnderMouse.Remove (view);
-        }
-
-        // Tell any views that are now under the mouse that the mouse has entered and add them to the list
-        foreach (View? view in currentViewsUnderMouse)
-        {
-            if (view is null)
-            {
-                continue;
-            }
-
-            if (CachedViewsUnderMouse.Contains (view))
-            {
-                continue;
-            }
-
-            CachedViewsUnderMouse.Add (view);
-            var raise = false;
-
-            if (view is Adornment { Parent: { } } adornmentView)
-            {
-                Point superViewLoc = adornmentView.Parent.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
-                raise = adornmentView.Contains (superViewLoc);
-            }
-            else
-            {
-                Point superViewLoc = view.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
-                raise = view.Contains (superViewLoc);
-            }
-
-            if (!raise)
-            {
-                continue;
-            }
-
-            CancelEventArgs eventArgs = new ();
-            bool? cancelled = view.NewMouseEnterEvent (eventArgs);
+        Mouse.RaiseMouseEnterLeaveEvents (screenPosition, currentViewsUnderMouse);
+    }
 
-            if (cancelled is true || eventArgs.Cancel)
-            {
-                break;
-            }
-        }
+    /// <summary>
+    ///     INTERNAL API: Called when a mouse event is raised by the driver. Determines the view under the mouse and
+    ///     calls the appropriate View mouse event handlers.
+    /// </summary>
+    /// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
+    /// <param name="mouseEvent">The mouse event with coordinates relative to the screen.</param>
+    internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
+    {
+        Mouse.RaiseMouseEvent (mouseEvent);
     }
 }

+ 22 - 47
Terminal.Gui/App/Application.Navigation.cs

@@ -7,44 +7,28 @@ public static partial class Application // Navigation stuff
     /// <summary>
     ///     Gets the <see cref="ApplicationNavigation"/> instance for the current <see cref="Application"/>.
     /// </summary>
-    public static ApplicationNavigation? Navigation { get; internal set; }
-
-    private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrides
-    private static Key _nextTabKey = Key.Tab; // Resources/config.json overrides
-    private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrides
-    private static Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrides
+    public static ApplicationNavigation? Navigation
+    {
+        get => ApplicationImpl.Instance.Navigation;
+        internal set => ApplicationImpl.Instance.Navigation = value;
+    }
 
     /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key NextTabGroupKey
     {
-        get => _nextTabGroupKey;
-        set
-        {
-            //if (_nextTabGroupKey != value)
-            {
-                KeyBindings.Replace (_nextTabGroupKey, value);
-                _nextTabGroupKey = value;
-            }
-        }
+        get => ApplicationImpl.Instance.Keyboard.NextTabGroupKey;
+        set => ApplicationImpl.Instance.Keyboard.NextTabGroupKey = value;
     }
 
-    /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
+    /// <summary>Alternative key to navigate forwards through views. Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key NextTabKey
     {
-        get => _nextTabKey;
-        set
-        {
-            //if (_nextTabKey != value)
-            {
-                KeyBindings.Replace (_nextTabKey, value);
-                _nextTabKey = value;
-            }
-        }
+        get => ApplicationImpl.Instance.Keyboard.NextTabKey;
+        set => ApplicationImpl.Instance.Keyboard.NextTabKey = value;
     }
 
-
     /// <summary>
     ///     Raised when the user releases a key.
     ///     <para>
@@ -53,38 +37,29 @@ public static partial class Application // Navigation stuff
     ///     </para>
     /// </summary>
     /// <remarks>
-    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Unix) do not support firing the
     ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
     ///     <para>Fired after <see cref="KeyDown"/>.</para>
     /// </remarks>
-    public static event EventHandler<Key>? KeyUp;
+    public static event EventHandler<Key>? KeyUp
+    {
+        add => ApplicationImpl.Instance.Keyboard.KeyUp += value;
+        remove => ApplicationImpl.Instance.Keyboard.KeyUp -= value;
+    }
+
     /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key PrevTabGroupKey
     {
-        get => _prevTabGroupKey;
-        set
-        {
-            //if (_prevTabGroupKey != value)
-            {
-                KeyBindings.Replace (_prevTabGroupKey, value);
-                _prevTabGroupKey = value;
-            }
-        }
+        get => ApplicationImpl.Instance.Keyboard.PrevTabGroupKey;
+        set => ApplicationImpl.Instance.Keyboard.PrevTabGroupKey = value;
     }
 
-    /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
+    /// <summary>Alternative key to navigate backwards through views. Shift+Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key PrevTabKey
     {
-        get => _prevTabKey;
-        set
-        {
-            //if (_prevTabKey != value)
-            {
-                KeyBindings.Replace (_prevTabKey, value);
-                _prevTabKey = value;
-            }
-        }
+        get => ApplicationImpl.Instance.Keyboard.PrevTabKey;
+        set => ApplicationImpl.Instance.Keyboard.PrevTabKey = value;
     }
 }

+ 5 - 1
Terminal.Gui/App/Application.Popover.cs

@@ -5,5 +5,9 @@ namespace Terminal.Gui.App;
 public static partial class Application // Popover handling
 {
     /// <summary>Gets the Application <see cref="Popover"/> manager.</summary>
-    public static ApplicationPopover? Popover { get; internal set; }
+    public static ApplicationPopover? Popover
+    {
+        get => ApplicationImpl.Instance.Popover;
+        internal set => ApplicationImpl.Instance.Popover = value;
+    }
 }

+ 49 - 579
Terminal.Gui/App/Application.Run.cs

@@ -1,625 +1,95 @@
 #nullable enable
-using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 
 namespace Terminal.Gui.App;
 
-public static partial class Application // Run (Begin, Run, End, Stop)
+public static partial class Application // Run (Begin -> Run -> Layout/Draw -> End -> Stop)
 {
-    private static Key _quitKey = Key.Esc; // Resources/config.json overrides
-
     /// <summary>Gets or sets the key to quit the application.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key QuitKey
     {
-        get => _quitKey;
-        set
-        {
-            //if (_quitKey != value)
-            {
-                KeyBindings.Replace (_quitKey, value);
-                _quitKey = value;
-            }
-        }
+        get => ApplicationImpl.Instance.Keyboard.QuitKey;
+        set => ApplicationImpl.Instance.Keyboard.QuitKey = value;
     }
 
-    private static Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
-
     /// <summary>Gets or sets the key to activate arranging views using the keyboard.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key ArrangeKey
     {
-        get => _arrangeKey;
-        set
-        {
-            //if (_arrangeKey != value)
-            {
-                KeyBindings.Replace (_arrangeKey, value);
-                _arrangeKey = value;
-            }
-        }
-    }
-
-    // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`.
-    // This variable is set in `End` in this case so that `Begin` correctly sets `Top`.
-    private static Toplevel? _cachedRunStateToplevel;
-
-    /// <summary>
-    ///     Notify that a new <see cref="RunState"/> was created (<see cref="Begin(Toplevel)"/> was called). The token is
-    ///     created in <see cref="Begin(Toplevel)"/> and this event will be fired before that function exits.
-    /// </summary>
-    /// <remarks>
-    ///     If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to <see cref="Begin(Toplevel)"/>
-    ///     must also subscribe to <see cref="NotifyStopRunState"/> and manually dispose of the <see cref="RunState"/> token
-    ///     when the application is done.
-    /// </remarks>
-    public static event EventHandler<RunStateEventArgs>? NotifyNewRunState;
-
-    /// <summary>Notify that an existent <see cref="RunState"/> is stopping (<see cref="End(RunState)"/> was called).</summary>
-    /// <remarks>
-    ///     If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to <see cref="Begin(Toplevel)"/>
-    ///     must also subscribe to <see cref="NotifyStopRunState"/> and manually dispose of the <see cref="RunState"/> token
-    ///     when the application is done.
-    /// </remarks>
-    public static event EventHandler<ToplevelEventArgs>? NotifyStopRunState;
-
-    /// <summary>Building block API: Prepares the provided <see cref="Toplevel"/> for execution.</summary>
-    /// <returns>
-    ///     The <see cref="RunState"/> handle that needs to be passed to the <see cref="End(RunState)"/> method upon
-    ///     completion.
-    /// </returns>
-    /// <param name="toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
-    /// <remarks>
-    ///     This method prepares the provided <see cref="Toplevel"/> for running with the focus, it adds this to the list
-    ///     of <see cref="Toplevel"/>s, lays out the SubViews, focuses the first element, and draws the <see cref="Toplevel"/>
-    ///     in the screen. This is usually followed by executing the <see cref="RunLoop"/> method, and then the
-    ///     <see cref="End(RunState)"/> method upon termination which will undo these changes.
-    /// </remarks>
-    public static RunState Begin (Toplevel toplevel)
-    {
-        ArgumentNullException.ThrowIfNull (toplevel);
-
-        //#if DEBUG_IDISPOSABLE
-        //        Debug.Assert (!toplevel.WasDisposed);
-
-        //        if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel)
-        //        {
-        //            Debug.Assert (_cachedRunStateToplevel.WasDisposed);
-        //        }
-        //#endif
-
-        // Ensure the mouse is ungrabbed.
-        if (MouseGrabHandler.MouseGrabView is { })
-        {
-            MouseGrabHandler.UngrabMouse ();
-        }
-
-        var rs = new RunState (toplevel);
-
-#if DEBUG_IDISPOSABLE
-        if (View.EnableDebugIDisposableAsserts && Top is { } && toplevel != Top && !TopLevels.Contains (Top))
-        {
-            // This assertion confirm if the Top was already disposed
-            Debug.Assert (Top.WasDisposed);
-            Debug.Assert (Top == _cachedRunStateToplevel);
-        }
-#endif
-
-        lock (TopLevels)
-        {
-            if (Top is { } && toplevel != Top && !TopLevels.Contains (Top))
-            {
-                // If Top was already disposed and isn't on the Toplevels Stack,
-                // clean it up here if is the same as _cachedRunStateToplevel
-                if (Top == _cachedRunStateToplevel)
-                {
-                    Top = null;
-                }
-                else
-                {
-                    // Probably this will never hit
-                    throw new ObjectDisposedException (Top.GetType ().FullName);
-                }
-            }
-
-            // BUGBUG: We should not depend on `Id` internally.
-            // BUGBUG: It is super unclear what this code does anyway.
-            if (string.IsNullOrEmpty (toplevel.Id))
-            {
-                var count = 1;
-                var id = (TopLevels.Count + count).ToString ();
-
-                while (TopLevels.Count > 0 && TopLevels.FirstOrDefault (x => x.Id == id) is { })
-                {
-                    count++;
-                    id = (TopLevels.Count + count).ToString ();
-                }
-
-                toplevel.Id = (TopLevels.Count + count).ToString ();
-
-                TopLevels.Push (toplevel);
-            }
-            else
-            {
-                Toplevel? dup = TopLevels.FirstOrDefault (x => x.Id == toplevel.Id);
-
-                if (dup is null)
-                {
-                    TopLevels.Push (toplevel);
-                }
-            }
-
-            //if (TopLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0)
-            //{
-            //    throw new ArgumentException ("There are duplicates Toplevel IDs");
-            //}
-        }
-
-        if (Top is null)
-        {
-            Top = toplevel;
-        }
-
-        if ((Top?.Modal == false && toplevel.Modal)
-            || (Top?.Modal == false && !toplevel.Modal)
-            || (Top?.Modal == true && toplevel.Modal))
-        {
-            if (toplevel.Visible)
-            {
-                if (Top is { HasFocus: true })
-                {
-                    Top.HasFocus = false;
-                }
-
-                // Force leave events for any entered views in the old Top
-                if (GetLastMousePosition () is { })
-                {
-                    RaiseMouseEnterLeaveEvents (GetLastMousePosition ()!.Value, new ());
-                }
-
-                Top?.OnDeactivate (toplevel);
-                Toplevel previousTop = Top!;
-
-                Top = toplevel;
-                Top.OnActivate (previousTop);
-            }
-        }
-
-        // View implements ISupportInitializeNotification which is derived from ISupportInitialize
-        if (!toplevel.IsInitialized)
-        {
-            toplevel.BeginInit ();
-            toplevel.EndInit (); // Calls Layout
-        }
-
-        // Call ConfigurationManager Apply here to ensure all subscribers to ConfigurationManager.Applied
-        // can update their state appropriately.
-        // BUGBUG: DO NOT DO THIS. Leave this commented out until we can figure out how to do this right
-        //Apply ();
-
-        // Try to set initial focus to any TabStop
-        if (!toplevel.HasFocus)
-        {
-            toplevel.SetFocus ();
-        }
-
-        toplevel.OnLoaded ();
-
-        LayoutAndDraw (true);
-
-        if (PositionCursor ())
-        {
-            Driver?.UpdateCursor ();
-        }
-
-        NotifyNewRunState?.Invoke (toplevel, new (rs));
-
-        return rs;
+        get => ApplicationImpl.Instance.Keyboard.ArrangeKey;
+        set => ApplicationImpl.Instance.Keyboard.ArrangeKey = value;
     }
 
-    /// <summary>
-    ///     Calls <see cref="View.PositionCursor"/> on the most focused view.
-    /// </summary>
-    /// <remarks>
-    ///     Does nothing if there is no most focused view.
-    ///     <para>
-    ///         If the most focused view is not visible within it's superview, the cursor will be hidden.
-    ///     </para>
-    /// </remarks>
-    /// <returns><see langword="true"/> if a view positioned the cursor and the position is visible.</returns>
-    internal static bool PositionCursor ()
-    {
-        // Find the most focused view and position the cursor there.
-        View? mostFocused = Navigation?.GetFocused ();
-
-        // If the view is not visible or enabled, don't position the cursor
-        if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
-        {
-            var current = CursorVisibility.Invisible;
-            Driver?.GetCursorVisibility (out current);
-
-            if (current != CursorVisibility.Invisible)
-            {
-                Driver?.SetCursorVisibility (CursorVisibility.Invisible);
-            }
-
-            return false;
-        }
-
-        // If the view is not visible within it's superview, don't position the cursor
-        Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty });
-
-        Rectangle superViewViewport =
-            mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver!.Screen;
-
-        if (!superViewViewport.IntersectsWith (mostFocusedViewport))
-        {
-            return false;
-        }
-
-        Point? cursor = mostFocused.PositionCursor ();
-
-        Driver!.GetCursorVisibility (out CursorVisibility currentCursorVisibility);
-
-        if (cursor is { })
-        {
-            // Convert cursor to screen coords
-            cursor = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = cursor.Value }).Location;
-
-            // If the cursor is not in a visible location in the SuperView, hide it
-            if (!superViewViewport.Contains (cursor.Value))
-            {
-                if (currentCursorVisibility != CursorVisibility.Invisible)
-                {
-                    Driver.SetCursorVisibility (CursorVisibility.Invisible);
-                }
+    /// <inheritdoc cref="IApplication.Begin"/>
+    public static SessionToken Begin (Toplevel toplevel) => ApplicationImpl.Instance.Begin (toplevel);
 
-                return false;
-            }
+    /// <inheritdoc cref="IApplication.PositionCursor"/>
+    public static bool PositionCursor () => ApplicationImpl.Instance.PositionCursor ();
 
-            // Show it
-            if (currentCursorVisibility == CursorVisibility.Invisible)
-            {
-                Driver.SetCursorVisibility (mostFocused.CursorVisibility);
-            }
-
-            return true;
-        }
-
-        if (currentCursorVisibility != CursorVisibility.Invisible)
-        {
-            Driver.SetCursorVisibility (CursorVisibility.Invisible);
-        }
-
-        return false;
-    }
-
-    /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
-    /// </summary>
-    /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
-    ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
-    ///         ensure resources are cleaned up and terminal settings restored.
-    ///     </para>
-    ///     <para>
-    ///         The caller is responsible for disposing the object returned by this method.
-    ///     </para>
-    /// </remarks>
-    /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
+    /// <inheritdoc cref="IApplication.Run(Func{Exception, bool}, string)"/>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public static Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-    {
-        return ApplicationImpl.Instance.Run (errorHandler, driver);
-    }
+    public static Toplevel Run (Func<Exception, bool>? errorHandler = null, string? driver = null) => ApplicationImpl.Instance.Run (errorHandler, driver);
 
-    /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
-    /// </summary>
-    /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
-    ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
-    ///         ensure resources are cleaned up and terminal settings restored.
-    ///     </para>
-    ///     <para>
-    ///         The caller is responsible for disposing the object returned by this method.
-    ///     </para>
-    /// </remarks>
-    /// <param name="errorHandler"></param>
-    /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
-    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
-    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
-    /// </param>
-    /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
+    /// <inheritdoc cref="IApplication.Run{TView}(Func{Exception, bool}, string)"/>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-        where T : Toplevel, new()
-    {
-        return ApplicationImpl.Instance.Run<T> (errorHandler, driver);
-    }
-
-    /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         This method is used to start processing events for the main application, but it is also used to run other
-    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
-    ///     </para>
-    ///     <para>
-    ///         To make a <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> stop execution, call
-    ///         <see cref="Application.RequestStop"/>.
-    ///     </para>
-    ///     <para>
-    ///         Calling <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
-    ///         <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState)"/>, and then calling
-    ///         <see cref="End(RunState)"/>.
-    ///     </para>
-    ///     <para>
-    ///         Alternatively, to have a program control the main loop and process events manually, call
-    ///         <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
-    ///         <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers handlers and then
-    ///         return control immediately.
-    ///     </para>
-    ///     <para>
-    ///         When using <see cref="Run{T}"/> or
-    ///         <see cref="Run(System.Func{System.Exception,bool},IConsoleDriver)"/>
-    ///         <see cref="Init"/> will be called automatically.
-    ///     </para>
-    ///     <para>
-    ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
-    ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
-    ///         returns <see langword="true"/> the <see cref="RunLoop(RunState)"/> will resume; otherwise this method will
-    ///         exit.
-    ///     </para>
-    /// </remarks>
-    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
-    /// <param name="errorHandler">
-    ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
-    ///     rethrows when null).
-    /// </param>
-    public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = null) { ApplicationImpl.Instance.Run (view, errorHandler); }
-
-    /// <summary>Adds a timeout to the application.</summary>
-    /// <remarks>
-    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
-    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
-    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
-    /// </remarks>
-    public static object? AddTimeout (TimeSpan time, Func<bool> callback) { return ApplicationImpl.Instance.AddTimeout (time, callback); }
+    public static TView Run<TView> (Func<Exception, bool>? errorHandler = null, string? driver = null)
+        where TView : Toplevel, new() => ApplicationImpl.Instance.Run<TView> (errorHandler, driver);
 
-    /// <summary>Removes a previously scheduled timeout</summary>
-    /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
-    /// Returns
-    /// <see langword="true"/>
-    /// if the timeout is successfully removed; otherwise,
-    /// <see langword="false"/>
-    /// .
-    /// This method also returns
-    /// <see langword="false"/>
-    /// if the timeout is not found.
-    public static bool RemoveTimeout (object token) { return ApplicationImpl.Instance.RemoveTimeout (token); }
+    /// <inheritdoc cref="IApplication.Run(Toplevel, Func{Exception, bool})"/>
+    public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = null) => ApplicationImpl.Instance.Run (view, errorHandler);
 
-    /// <summary>Runs <paramref name="action"/> on the thread that is processing events</summary>
-    /// <param name="action">the action to be invoked on the main processing thread.</param>
-    public static void Invoke (Action action) { ApplicationImpl.Instance.Invoke (action); }
+    /// <inheritdoc cref="IApplication.AddTimeout"/>
+    public static object? AddTimeout (TimeSpan time, Func<bool> callback) => ApplicationImpl.Instance.AddTimeout (time, callback);
 
-    // TODO: Determine if this is really needed. The only code that calls WakeUp I can find
-    // is ProgressBarStyles, and it's not clear it needs to.
+    /// <inheritdoc cref="IApplication.RemoveTimeout"/>
+    public static bool RemoveTimeout (object token) => ApplicationImpl.Instance.RemoveTimeout (token);
 
-    /// <summary>Wakes up the running application that might be waiting on input.</summary>
-    public static void Wakeup () { MainLoop?.Wakeup (); }
+    /// <inheritdoc cref="IApplication.TimedEvents"/>
+    /// 
+    public static ITimedEvents? TimedEvents => ApplicationImpl.Instance?.TimedEvents;
+    /// <inheritdoc cref="IApplication.Invoke"/>
+    public static void Invoke (Action action) => ApplicationImpl.Instance.Invoke (action);
 
-    /// <summary>
-    ///     Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that
-    ///     need to be laid out (see <see cref="View.NeedsLayout"/>) will be laid out.
-    ///     Only Views that need to be drawn (see <see cref="View.NeedsDraw"/>) will be drawn.
-    /// </summary>
-    /// <param name="forceDraw">
-    ///     If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and
-    ///     should only be overriden for testing.
-    /// </param>
-    public static void LayoutAndDraw (bool forceDraw = false) { ApplicationImpl.Instance.LayoutAndDraw (forceDraw); }
+    /// <inheritdoc cref="IApplication.LayoutAndDraw"/>
+    public static void LayoutAndDraw (bool forceRedraw = false) => ApplicationImpl.Instance.LayoutAndDraw (forceRedraw);
 
-    internal static void LayoutAndDrawImpl (bool forceDraw = false)
+    /// <inheritdoc cref="IApplication.StopAfterFirstIteration"/>
+    public static bool StopAfterFirstIteration
     {
-        List<View> tops = [.. TopLevels];
-
-        if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
-        {
-            visiblePopover.SetNeedsDraw ();
-            visiblePopover.SetNeedsLayout ();
-            tops.Insert (0, visiblePopover);
-        }
-
-        bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Screen.Size);
-
-        if (ClearScreenNextIteration)
-        {
-            forceDraw = true;
-            ClearScreenNextIteration = false;
-        }
-
-        if (forceDraw)
-        {
-            Driver?.ClearContents ();
-        }
-
-        View.SetClipToScreen ();
-        View.Draw (tops, neededLayout || forceDraw);
-        View.SetClipToScreen ();
-        Driver?.Refresh ();
+        get => ApplicationImpl.Instance.StopAfterFirstIteration;
+        set => ApplicationImpl.Instance.StopAfterFirstIteration = value;
     }
 
-    /// <summary>This event is raised on each iteration of the main loop.</summary>
-    /// <remarks>See also <see cref="Timeout"/></remarks>
-    public static event EventHandler<IterationEventArgs>? Iteration;
-    
-    /// <summary>The <see cref="MainLoop"/> driver for the application</summary>
-    /// <value>The main loop.</value>
-    internal static MainLoop? MainLoop { get; set; }
-
-    /// <summary>
-    ///     Set to true to cause <see cref="End"/> to be called after the first iteration. Set to false (the default) to
-    ///     cause the application to continue running until Application.RequestStop () is called.
-    /// </summary>
-    public static bool EndAfterFirstIteration { get; set; }
-
-    /// <summary>Building block API: Runs the main loop for the created <see cref="Toplevel"/>.</summary>
-    /// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
-    public static void RunLoop (RunState state)
-    {
-        ArgumentNullException.ThrowIfNull (state);
-        ObjectDisposedException.ThrowIf (state.Toplevel is null, "state");
-
-        var firstIteration = true;
-
-        for (state.Toplevel.Running = true; state.Toplevel?.Running == true;)
-        {
-            if (MainLoop is { })
-            {
-                MainLoop.Running = true;
-            }
-
-            if (EndAfterFirstIteration && !firstIteration)
-            {
-                return;
-            }
+    /// <inheritdoc cref="IApplication.RequestStop(Toplevel)"/>
+    public static void RequestStop (Toplevel? top = null) => ApplicationImpl.Instance.RequestStop (top);
 
-            firstIteration = RunIteration (ref state, firstIteration);
-        }
+    /// <inheritdoc cref="IApplication.End"/>
+    public static void End (SessionToken sessionToken) => ApplicationImpl.Instance.End (sessionToken);
 
-        if (MainLoop is { })
-        {
-            MainLoop.Running = false;
-        }
+    /// <inheritdoc cref="IApplication.RaiseIteration"/>
+    internal static void RaiseIteration () => ApplicationImpl.Instance.RaiseIteration ();
 
-        // Run one last iteration to consume any outstanding input events from Driver
-        // This is important for remaining OnKeyUp events.
-        RunIteration (ref state, firstIteration);
-    }
-
-    /// <summary>Run one application iteration.</summary>
-    /// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
-    /// <param name="firstIteration">
-    ///     Set to <see langword="true"/> if this is the first run loop iteration.
-    /// </param>
-    /// <returns><see langword="false"/> if at least one iteration happened.</returns>
-    public static bool RunIteration (ref RunState state, bool firstIteration = false)
+    /// <inheritdoc cref="IApplication.Iteration"/>
+    public static event EventHandler<IterationEventArgs>? Iteration
     {
-        // If the driver has events pending do an iteration of the driver MainLoop
-        if (MainLoop is { Running: true } && MainLoop.EventsPending ())
-        {
-            // Notify Toplevel it's ready
-            if (firstIteration)
-            {
-                state.Toplevel.OnReady ();
-            }
-
-            MainLoop.RunIteration ();
-
-            Iteration?.Invoke (null, new ());
-        }
-
-        firstIteration = false;
-
-        if (Top is null)
-        {
-            return firstIteration;
-        }
-
-        LayoutAndDraw (TopLevels.Any (v => v.NeedsLayout || v.NeedsDraw));
-
-        if (PositionCursor ())
-        {
-            Driver?.UpdateCursor ();
-        }
-
-        return firstIteration;
+        add => ApplicationImpl.Instance.Iteration += value;
+        remove => ApplicationImpl.Instance.Iteration -= value;
     }
 
-    /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
-    /// <param name="top">The <see cref="Toplevel"/> to stop.</param>
-    /// <remarks>
-    ///     <para>This will cause <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to return.</para>
-    ///     <para>
-    ///         Calling <see cref="RequestStop(Toplevel)"/> is equivalent to setting the
-    ///         <see cref="Toplevel.Running"/>
-    ///         property on the currently running <see cref="Toplevel"/> to false.
-    ///     </para>
-    /// </remarks>
-    public static void RequestStop (Toplevel? top = null) { ApplicationImpl.Instance.RequestStop (top); }
-
-    internal static void OnNotifyStopRunState (Toplevel top)
+    /// <inheritdoc cref="IApplication.SessionBegun"/>
+    public static event EventHandler<SessionTokenEventArgs>? SessionBegun
     {
-        if (EndAfterFirstIteration)
-        {
-            NotifyStopRunState?.Invoke (top, new (top));
-        }
+        add => ApplicationImpl.Instance.SessionBegun += value;
+        remove => ApplicationImpl.Instance.SessionBegun -= value;
     }
 
-    /// <summary>
-    ///     Building block API: completes the execution of a <see cref="Toplevel"/> that was started with
-    ///     <see cref="Begin(Toplevel)"/> .
-    /// </summary>
-    /// <param name="runState">The <see cref="RunState"/> returned by the <see cref="Begin(Toplevel)"/> method.</param>
-    public static void End (RunState runState)
-    {
-        ArgumentNullException.ThrowIfNull (runState);
-
-        if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
-        {
-            ApplicationPopover.HideWithQuitCommand (visiblePopover);
-        }
-
-        runState.Toplevel.OnUnloaded ();
-
-        // End the RunState.Toplevel
-        // First, take it off the Toplevel Stack
-        if (TopLevels.TryPop (out Toplevel? topOfStack))
-        {
-            if (topOfStack != runState.Toplevel)
-            {
-                // If the top of the stack is not the RunState.Toplevel then
-                // this call to End is not balanced with the call to Begin that started the RunState
-                throw new ArgumentException ("End must be balanced with calls to Begin");
-            }
-        }
-
-        // Notify that it is closing
-        runState.Toplevel?.OnClosed (runState.Toplevel);
-
-        if (TopLevels.TryPeek (out Toplevel? newTop))
-        {
-            Top = newTop;
-            Top?.SetNeedsDraw ();
-        }
-
-        if (runState.Toplevel is { HasFocus: true })
-        {
-            runState.Toplevel.HasFocus = false;
-        }
-
-        if (Top is { HasFocus: false })
-        {
-            Top.SetFocus ();
-        }
-
-        _cachedRunStateToplevel = runState.Toplevel;
-
-        runState.Toplevel = null;
-        runState.Dispose ();
-
-        LayoutAndDraw (true);
-    }
-    internal static void RaiseIteration ()
+    /// <inheritdoc cref="IApplication.SessionEnded"/>
+    public static event EventHandler<ToplevelEventArgs>? SessionEnded
     {
-        Iteration?.Invoke (null, new ());
+        add => ApplicationImpl.Instance.SessionEnded += value;
+        remove => ApplicationImpl.Instance.SessionEnded -= value;
     }
 }

+ 14 - 74
Terminal.Gui/App/Application.Screen.cs

@@ -2,88 +2,28 @@
 
 namespace Terminal.Gui.App;
 
-public static partial class Application // Screen related stuff
+public static partial class Application // Screen related stuff; intended to hide Driver details
 {
-    private static readonly object _lockScreen = new ();
-    private static Rectangle? _screen;
+    /// <inheritdoc cref="IApplication.Screen"/>
 
-    /// <summary>
-    ///     Gets or sets the size of the screen. By default, this is the size of the screen as reported by the <see cref="IConsoleDriver"/>.
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    ///     If the <see cref="IConsoleDriver"/> has not been initialized, this will return a default size of 2048x2048; useful for unit tests.
-    /// </para>
-    /// </remarks>
     public static Rectangle Screen
     {
-        get
-        {
-            lock (_lockScreen)
-            {
-                if (_screen == null)
-                {
-                    _screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
-                }
-
-                return _screen.Value;
-            }
-        }
-        set
-        {
-            if (value is {} && (value.X != 0 || value.Y != 0))
-            {
-                throw new NotImplementedException ($"Screen locations other than 0, 0 are not yet supported");
-            }
-
-            lock (_lockScreen)
-            {
-                _screen = value;
-            }
-        }
+        get => ApplicationImpl.Instance.Screen;
+        set => ApplicationImpl.Instance.Screen = value;
     }
 
-    /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
-    /// <remarks>
-    ///     Event handlers can set <see cref="SizeChangedEventArgs.Cancel"/> to <see langword="true"/> to prevent
-    ///     <see cref="Application"/> from changing it's size to match the new terminal size.
-    /// </remarks>
-    public static event EventHandler<SizeChangedEventArgs>? SizeChanging;
-
-    /// <summary>
-    ///     Called when the application's size changes. Sets the size of all <see cref="Toplevel"/>s and fires the
-    ///     <see cref="SizeChanging"/> event.
-    /// </summary>
-    /// <param name="args">The new size.</param>
-    /// <returns><see lanword="true"/>if the size was changed.</returns>
-    public static bool OnSizeChanging (SizeChangedEventArgs args)
+    /// <inheritdoc cref="IApplication.ScreenChanged"/>
+    public static event EventHandler<EventArgs<Rectangle>>? ScreenChanged
     {
-        SizeChanging?.Invoke (null, args);
-
-        if (args.Cancel || args.Size is null)
-        {
-            return false;
-        }
-
-        Screen = new (Point.Empty, args.Size.Value);
-
-        foreach (Toplevel t in TopLevels)
-        {
-            t.OnSizeChanging (new (args.Size));
-            t.SetNeedsLayout ();
-        }
+        add => ApplicationImpl.Instance.ScreenChanged += value;
+        remove => ApplicationImpl.Instance.ScreenChanged -= value;
+    }
 
-        LayoutAndDraw (true);
+    /// <inheritdoc cref="IApplication.ClearScreenNextIteration"/>
 
-        return true;
+    internal static bool ClearScreenNextIteration
+    {
+        get => ApplicationImpl.Instance.ClearScreenNextIteration;
+        set => ApplicationImpl.Instance.ClearScreenNextIteration = value;
     }
-
-    /// <summary>
-    ///     Gets or sets whether the screen will be cleared, and all Views redrawn, during the next Application iteration.
-    /// </summary>
-    /// <remarks>
-    ///     This is typicall set to true when a View's <see cref="View.Frame"/> changes and that view has no
-    ///     SuperView (e.g. when <see cref="Application.Top"/> is moved or resized.
-    /// </remarks>
-    public static bool ClearScreenNextIteration { get; set; }
 }

+ 7 - 17
Terminal.Gui/App/Application.Toplevel.cs

@@ -5,24 +5,14 @@ namespace Terminal.Gui.App;
 
 public static partial class Application // Toplevel handling
 {
-    // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What
-
-    private static readonly ConcurrentStack<Toplevel> _topLevels = new ();
-    private static readonly object _topLevelsLock = new ();
-
-    /// <summary>Holds the stack of TopLevel views.</summary>
-    internal static ConcurrentStack<Toplevel> TopLevels
-    {
-        get
-        {
-            lock (_topLevelsLock)
-            {
-                return _topLevels;
-            }
-        }
-    }
+    /// <inheritdoc cref="IApplication.TopLevels"/>
+    public static ConcurrentStack<Toplevel> TopLevels => ApplicationImpl.Instance.TopLevels;
 
     /// <summary>The <see cref="Toplevel"/> that is currently active.</summary>
     /// <value>The top.</value>
-    public static Toplevel? Top { get; internal set; }
+    public static Toplevel? Top
+    {
+        get => ApplicationImpl.Instance.Top;
+        internal set => ApplicationImpl.Instance.Top = value;
+    }
 }

+ 4 - 4
Terminal.Gui/App/Application.cd

@@ -36,19 +36,19 @@
       <FileName>App\MainLoopSyncContext.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.App.RunState" Collapsed="true" BaseTypeListCollapsed="true">
+  <Class Name="Terminal.Gui.App.SessionToken" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="15.25" Y="4" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAACACAgAAAAAAAAAAAAAAAAACQAAAAAAAAAA=</HashCode>
-      <FileName>App\RunState.cs</FileName>
+      <FileName>App\SessionToken.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" Collapsed="true" />
   </Class>
-  <Class Name="Terminal.Gui.App.RunStateEventArgs" Collapsed="true">
+  <Class Name="Terminal.Gui.App.SessionTokenEventArgs" Collapsed="true">
     <Position X="17" Y="4" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=</HashCode>
-      <FileName>App\RunStateEventArgs.cs</FileName>
+      <FileName>App\SessionTokenEventArgs.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.App.Timeout" Collapsed="true">

+ 7 - 114
Terminal.Gui/App/Application.cs

@@ -39,18 +39,6 @@ namespace Terminal.Gui.App;
 /// <remarks></remarks>
 public static partial class Application
 {
-    /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
-    public static List<CultureInfo>? SupportedCultures { get; private set; } = GetSupportedCultures ();
-
-
-    /// <summary>
-    /// <para>
-    /// Handles recurring events. These are invoked on the main UI thread - allowing for
-    /// safe updates to <see cref="View"/> instances.
-    /// </para>
-    /// </summary>
-    public static ITimedEvents? TimedEvents => ApplicationImpl.Instance?.TimedEvents;
-
     /// <summary>
     /// Maximum number of iterations of the main loop (and hence draws)
     /// to allow to occur per second. Defaults to <see cref="DefaultMaximumIterationsPerSecond"/>> which is a 40ms sleep
@@ -71,7 +59,7 @@ public static partial class Application
     /// <returns>A string representation of the Application </returns>
     public new static string ToString ()
     {
-        IConsoleDriver? driver = Driver;
+        IDriver? driver = Driver;
 
         if (driver is null)
         {
@@ -82,11 +70,11 @@ public static partial class Application
     }
 
     /// <summary>
-    ///     Gets a string representation of the Application rendered by the provided <see cref="IConsoleDriver"/>.
+    ///     Gets a string representation of the Application rendered by the provided <see cref="IDriver"/>.
     /// </summary>
     /// <param name="driver">The driver to use to render the contents.</param>
     /// <returns>A string representation of the Application </returns>
-    public static string ToString (IConsoleDriver? driver)
+    public static string ToString (IDriver? driver)
     {
         if (driver is null)
         {
@@ -129,6 +117,10 @@ public static partial class Application
         return sb.ToString ();
     }
 
+    /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
+    public static List<CultureInfo>? SupportedCultures { get; private set; } = GetSupportedCultures ();
+
+
     internal static List<CultureInfo> GetAvailableCulturesFromEmbeddedResources ()
     {
         ResourceManager rm = new (typeof (Strings));
@@ -171,103 +163,4 @@ public static partial class Application
         // It's called from a self-contained single-file and get available cultures from the embedded resources strings.
         return GetAvailableCulturesFromEmbeddedResources ();
     }
-
-    // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
-    // Encapsulate all setting of initial state for Application; Having
-    // this in a function like this ensures we don't make mistakes in
-    // guaranteeing that the state of this singleton is deterministic when Init
-    // starts running and after Shutdown returns.
-    internal static void ResetState (bool ignoreDisposed = false)
-    {
-        Navigation = new ();
-
-        // Shutdown is the bookend for Init. As such it needs to clean up all resources
-        // Init created. Apps that do any threading will need to code defensively for this.
-        // e.g. see Issue #537
-        foreach (Toplevel? t in TopLevels)
-        {
-            t!.Running = false;
-        }
-
-        if (Popover?.GetActivePopover () is View popover)
-        {
-            // This forcefully closes the popover; invoking Command.Quit would be more graceful
-            // but since this is shutdown, doing this is ok.
-            popover.Visible = false;
-        }
-
-        Popover?.Dispose ();
-        Popover = null;
-
-        TopLevels.Clear ();
-#if DEBUG_IDISPOSABLE
-
-        // Don't dispose the Top. It's up to caller dispose it
-        if (View.EnableDebugIDisposableAsserts && !ignoreDisposed && Top is { })
-        {
-            Debug.Assert (Top.WasDisposed, $"Title = {Top.Title}, Id = {Top.Id}");
-
-            // If End wasn't called _cachedRunStateToplevel may be null
-            if (_cachedRunStateToplevel is { })
-            {
-                Debug.Assert (_cachedRunStateToplevel.WasDisposed);
-                Debug.Assert (_cachedRunStateToplevel == Top);
-            }
-        }
-#endif
-        Top = null;
-        _cachedRunStateToplevel = null;
-
-        // MainLoop stuff
-        MainLoop?.Dispose ();
-        MainLoop = null;
-        MainThreadId = -1;
-        Iteration = null;
-        EndAfterFirstIteration = false;
-        ClearScreenNextIteration = false;
-
-        // Driver stuff
-        if (Driver is { })
-        {
-            UnsubscribeDriverEvents ();
-            Driver?.End ();
-            Driver = null;
-        }
-
-        _screen = null;
-
-        // Don't reset ForceDriver; it needs to be set before Init is called.
-        //ForceDriver = string.Empty;
-        //Force16Colors = false;
-        _forceFakeConsole = false;
-
-        // Run State stuff
-        NotifyNewRunState = null;
-        NotifyStopRunState = null;
-        MouseGrabHandler = new MouseGrabHandler ();
-        Initialized = false;
-
-        // Mouse
-        // Do not clear _lastMousePosition; Popover's require it to stay set with
-        // last mouse pos.
-        //_lastMousePosition = null;
-        CachedViewsUnderMouse.Clear ();
-        MouseEvent = null;
-
-        // Keyboard
-        KeyDown = null;
-        KeyUp = null;
-        SizeChanging = null;
-
-        Navigation = null;
-
-        KeyBindings.Clear ();
-        AddKeyBindings ();
-
-        // Reset synchronization context to allow the user to run async/await,
-        // as the main loop has been ended, the synchronization context from
-        // gui.cs does no longer process any callbacks. See #1084 for more details:
-        // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
-        SynchronizationContext.SetSynchronizationContext (null);
-    }
 }

+ 176 - 0
Terminal.Gui/App/ApplicationImpl.Driver.cs

@@ -0,0 +1,176 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.App;
+
+public partial class ApplicationImpl
+{
+    /// <inheritdoc/>
+    public IDriver? Driver { get; set; }
+
+    /// <inheritdoc/>
+    public bool Force16Colors { get; set; }
+
+    /// <inheritdoc/>
+    public string ForceDriver { get; set; } = string.Empty;
+
+    /// <inheritdoc/>
+    public List<SixelToRender> Sixel { get; } = new ();
+
+    /// <summary>
+    ///     Creates the appropriate <see cref="IDriver"/> based on platform and driverName.
+    /// </summary>
+    /// <param name="driverName"></param>
+    /// <returns></returns>
+    /// <exception cref="Exception"></exception>
+    /// <exception cref="InvalidOperationException"></exception>
+    private void CreateDriver (string? driverName)
+    {
+        PlatformID p = Environment.OSVersion.Platform;
+
+        // Check component factory type first - this takes precedence over driverName
+        bool factoryIsWindows = _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
+        bool factoryIsDotNet = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
+        bool factoryIsUnix = _componentFactory is IComponentFactory<char>;
+        bool factoryIsFake = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
+
+        // Then check driverName
+        bool nameIsWindows = driverName?.Contains ("win", StringComparison.OrdinalIgnoreCase) ?? false;
+        bool nameIsDotNet = driverName?.Contains ("dotnet", StringComparison.OrdinalIgnoreCase) ?? false;
+        bool nameIsUnix = driverName?.Contains ("unix", StringComparison.OrdinalIgnoreCase) ?? false;
+        bool nameIsFake = driverName?.Contains ("fake", StringComparison.OrdinalIgnoreCase) ?? false;
+
+        // Decide which driver to use - component factory type takes priority
+        if (factoryIsFake || (!factoryIsWindows && !factoryIsDotNet && !factoryIsUnix && nameIsFake))
+        {
+            Coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
+            _driverName = "fake";
+        }
+        else if (factoryIsWindows || (!factoryIsDotNet && !factoryIsUnix && nameIsWindows))
+        {
+            Coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
+            _driverName = "windows";
+        }
+        else if (factoryIsDotNet || (!factoryIsWindows && !factoryIsUnix && nameIsDotNet))
+        {
+            Coordinator = CreateSubcomponents (() => new NetComponentFactory ());
+            _driverName = "dotnet";
+        }
+        else if (factoryIsUnix || (!factoryIsWindows && !factoryIsDotNet && nameIsUnix))
+        {
+            Coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
+            _driverName = "unix";
+        }
+        else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        {
+            Coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
+            _driverName = "windows";
+        }
+        else if (p == PlatformID.Unix)
+        {
+            Coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
+            _driverName = "unix";
+        }
+        else
+        {
+            Logging.Information($"Falling back to dotnet driver.");
+            Coordinator = CreateSubcomponents (() => new NetComponentFactory ());
+            _driverName = "dotnet";
+        }
+
+        Logging.Trace ($"Created Subcomponents: {Coordinator}");
+
+        Coordinator.StartInputTaskAsync ().Wait ();
+
+        if (Driver == null)
+        {
+            throw new ("Driver was null even after booting MainLoopCoordinator");
+        }
+    }
+
+    private readonly IComponentFactory? _componentFactory;
+
+    /// <summary>
+    ///     INTERNAL: Gets or sets the main loop coordinator that orchestrates the application's event processing,
+    ///     input handling, and rendering pipeline.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         The <see cref="IMainLoopCoordinator"/> is the central component responsible for:
+    ///         <list type="bullet">
+    ///             <item>Managing the platform-specific input thread that reads from the console</item>
+    ///             <item>Coordinating the main application loop via <see cref="IMainLoopCoordinator.RunIteration"/></item>
+    ///             <item>Processing queued input events and translating them to Terminal.Gui events</item>
+    ///             <item>Managing the <see cref="ApplicationMainLoop{TInputRecord}"/> that handles rendering</item>
+    ///             <item>Executing scheduled timeouts and callbacks via <see cref="ITimedEvents"/></item>
+    ///         </list>
+    ///     </para>
+    ///     <para>
+    ///         The coordinator is created in <see cref="CreateDriver"/> based on the selected driver
+    ///         (Windows, Unix, .NET, or Fake) and is started by calling
+    ///         <see cref="IMainLoopCoordinator.StartInputTaskAsync"/>.
+    ///     </para>
+    /// </remarks>
+    internal IMainLoopCoordinator? Coordinator { get; private set; }
+
+    /// <summary>
+    ///     INTERNAL: Creates a <see cref="MainLoopCoordinator{TInputRecord}"/> with the appropriate component factory
+    ///     for the specified input record type.
+    /// </summary>
+    /// <typeparam name="TInputRecord">
+    ///     Platform-specific input type: <see cref="ConsoleKeyInfo"/> (.NET/Fake),
+    ///     <see cref="WindowsConsole.InputRecord"/> (Windows), or <see cref="char"/> (Unix).
+    /// </typeparam>
+    /// <param name="fallbackFactory">
+    ///     Factory function to create the component factory if <see cref="_componentFactory"/>
+    ///     is not of type <see cref="IComponentFactory{TInputRecord}"/>.
+    /// </param>
+    /// <returns>
+    ///     A <see cref="MainLoopCoordinator{TInputRecord}"/> configured with the input queue,
+    ///     main loop, timed events, and selected component factory.
+    /// </returns>
+    private IMainLoopCoordinator CreateSubcomponents<TInputRecord> (Func<IComponentFactory<TInputRecord>> fallbackFactory) where TInputRecord : struct
+    {
+        ConcurrentQueue<TInputRecord> inputQueue = new ();
+        ApplicationMainLoop<TInputRecord> loop = new ();
+
+        IComponentFactory<TInputRecord> cf;
+
+        if (_componentFactory is IComponentFactory<TInputRecord> typedFactory)
+        {
+            cf = typedFactory;
+        }
+        else
+        {
+            cf = fallbackFactory ();
+        }
+
+        return new MainLoopCoordinator<TInputRecord> (_timedEvents, inputQueue, loop, cf);
+    }
+
+    internal void SubscribeDriverEvents ()
+    {
+        ArgumentNullException.ThrowIfNull (Driver);
+
+        Driver.SizeChanged += Driver_SizeChanged;
+        Driver.KeyDown += Driver_KeyDown;
+        Driver.KeyUp += Driver_KeyUp;
+        Driver.MouseEvent += Driver_MouseEvent;
+    }
+
+    internal void UnsubscribeDriverEvents ()
+    {
+        ArgumentNullException.ThrowIfNull (Driver);
+
+        Driver.SizeChanged -= Driver_SizeChanged;
+        Driver.KeyDown -= Driver_KeyDown;
+        Driver.KeyUp -= Driver_KeyUp;
+        Driver.MouseEvent -= Driver_MouseEvent;
+    }
+
+    private void Driver_KeyDown (object? sender, Key e) { Keyboard?.RaiseKeyDownEvent (e); }
+
+    private void Driver_KeyUp (object? sender, Key e) { Keyboard?.RaiseKeyUpEvent (e); }
+
+    private void Driver_MouseEvent (object? sender, MouseEventArgs e) { Mouse?.RaiseMouseEvent (e); }
+}

+ 251 - 0
Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

@@ -0,0 +1,251 @@
+#nullable enable
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Terminal.Gui.App;
+
+public partial class ApplicationImpl
+{
+    /// <inheritdoc/>
+    public bool Initialized { get; set; }
+
+    /// <inheritdoc/>
+    public event EventHandler<EventArgs<bool>>? InitializedChanged;
+
+    /// <inheritdoc/>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public void Init (IDriver? driver = null, string? driverName = null)
+    {
+        if (Initialized)
+        {
+            Logging.Error ("Init called multiple times without shutdown, aborting.");
+
+            throw new InvalidOperationException ("Init called multiple times without Shutdown");
+        }
+
+        if (!string.IsNullOrWhiteSpace (driverName))
+        {
+            _driverName = driverName;
+        }
+
+        if (string.IsNullOrWhiteSpace (_driverName))
+        {
+            _driverName = ForceDriver;
+        }
+
+        Debug.Assert (Navigation is null);
+        Navigation = new ();
+
+        Debug.Assert (Popover is null);
+        Popover = new ();
+
+        // Preserve existing keyboard settings if they exist
+        bool hasExistingKeyboard = _keyboard is { };
+        Key existingQuitKey = _keyboard?.QuitKey ?? Key.Esc;
+        Key existingArrangeKey = _keyboard?.ArrangeKey ?? Key.F5.WithCtrl;
+        Key existingNextTabKey = _keyboard?.NextTabKey ?? Key.Tab;
+        Key existingPrevTabKey = _keyboard?.PrevTabKey ?? Key.Tab.WithShift;
+        Key existingNextTabGroupKey = _keyboard?.NextTabGroupKey ?? Key.F6;
+        Key existingPrevTabGroupKey = _keyboard?.PrevTabGroupKey ?? Key.F6.WithShift;
+
+        // Reset keyboard to ensure fresh state with default bindings
+        _keyboard = new KeyboardImpl { Application = this };
+
+        // Restore previously set keys if they existed and were different from defaults
+        if (hasExistingKeyboard)
+        {
+            _keyboard.QuitKey = existingQuitKey;
+            _keyboard.ArrangeKey = existingArrangeKey;
+            _keyboard.NextTabKey = existingNextTabKey;
+            _keyboard.PrevTabKey = existingPrevTabKey;
+            _keyboard.NextTabGroupKey = existingNextTabGroupKey;
+            _keyboard.PrevTabGroupKey = existingPrevTabGroupKey;
+        }
+
+        CreateDriver (driverName ?? _driverName);
+        Screen = Driver!.Screen;
+        Initialized = true;
+
+        RaiseInitializedChanged (this, new (true));
+        SubscribeDriverEvents ();
+
+        SynchronizationContext.SetSynchronizationContext (new ());
+        MainThreadId = Thread.CurrentThread.ManagedThreadId;
+    }
+
+    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    public void Shutdown ()
+    {
+        // Stop the coordinator if running
+        Coordinator?.Stop ();
+
+        // Capture state before cleanup
+        bool wasInitialized = Initialized;
+
+#if DEBUG
+
+        // Check that all Application events have no remaining subscribers BEFORE clearing them
+        // Only check if we were actually initialized
+        if (wasInitialized)
+        {
+            AssertNoEventSubscribers (nameof (Iteration), Iteration);
+            AssertNoEventSubscribers (nameof (SessionBegun), SessionBegun);
+            AssertNoEventSubscribers (nameof (SessionEnded), SessionEnded);
+            AssertNoEventSubscribers (nameof (ScreenChanged), ScreenChanged);
+
+            //AssertNoEventSubscribers (nameof (InitializedChanged), InitializedChanged);
+        }
+#endif
+
+        // Clean up all application state (including sync context)
+        // ResetState handles the case where Initialized is false
+        ResetState ();
+
+        // Configuration manager diagnostics
+        ConfigurationManager.PrintJsonErrors ();
+
+        // Raise the initialized changed event to notify shutdown
+        if (wasInitialized)
+        {
+            bool init = Initialized; // Will be false after ResetState
+            RaiseInitializedChanged (this, new (in init));
+        }
+
+        // Clear the event to prevent memory leaks
+        InitializedChanged = null;
+
+        // Create a new lazy instance for potential future Init
+        _lazyInstance = new (() => new ApplicationImpl ());
+    }
+
+#if DEBUG
+    /// <summary>
+    ///     DEBUG ONLY: Asserts that an event has no remaining subscribers.
+    /// </summary>
+    /// <param name="eventName">The name of the event for diagnostic purposes.</param>
+    /// <param name="eventDelegate">The event delegate to check.</param>
+    private static void AssertNoEventSubscribers (string eventName, Delegate? eventDelegate)
+    {
+        if (eventDelegate is null)
+        {
+            return;
+        }
+
+        Delegate [] subscribers = eventDelegate.GetInvocationList ();
+
+        if (subscribers.Length > 0)
+        {
+            string subscriberInfo = string.Join (
+                                                 ", ",
+                                                 subscribers.Select (d => $"{d.Method.DeclaringType?.Name}.{d.Method.Name}"
+                                                                    )
+                                                );
+
+            Debug.Fail (
+                        $"Application.{eventName} has {subscribers.Length} remaining subscriber(s) after Shutdown: {subscriberInfo}"
+                       );
+        }
+    }
+#endif
+
+    /// <inheritdoc/>
+    public void ResetState (bool ignoreDisposed = false)
+    {
+        // Shutdown is the bookend for Init. As such it needs to clean up all resources
+        // Init created. Apps that do any threading will need to code defensively for this.
+        // e.g. see Issue #537
+
+        // === 1. Stop all running toplevels ===
+        foreach (Toplevel? t in TopLevels)
+        {
+            t!.Running = false;
+        }
+
+        // === 2. Close and dispose popover ===
+        if (Popover?.GetActivePopover () is View popover)
+        {
+            // This forcefully closes the popover; invoking Command.Quit would be more graceful
+            // but since this is shutdown, doing this is ok.
+            popover.Visible = false;
+        }
+
+        Popover?.Dispose ();
+        Popover = null;
+
+        // === 3. Clean up toplevels ===
+        TopLevels.Clear ();
+
+#if DEBUG_IDISPOSABLE
+
+        // Don't dispose the Top. It's up to caller dispose it
+        if (View.EnableDebugIDisposableAsserts && !ignoreDisposed && Top is { })
+        {
+            Debug.Assert (Top.WasDisposed, $"Title = {Top.Title}, Id = {Top.Id}");
+
+            // If End wasn't called _CachedSessionTokenToplevel may be null
+            if (CachedSessionTokenToplevel is { })
+            {
+                Debug.Assert (CachedSessionTokenToplevel.WasDisposed);
+                Debug.Assert (CachedSessionTokenToplevel == Top);
+            }
+        }
+#endif
+
+        Top = null;
+        CachedSessionTokenToplevel = null;
+
+        // === 4. Clean up driver ===
+        if (Driver is { })
+        {
+            UnsubscribeDriverEvents ();
+            Driver?.End ();
+            Driver = null;
+        }
+
+        // Reset screen
+        ResetScreen ();
+        _screen = null;
+
+        // === 5. Clear run state ===
+        Iteration = null;
+        SessionBegun = null;
+        SessionEnded = null;
+        StopAfterFirstIteration = false;
+        ClearScreenNextIteration = false;
+
+        // === 6. Reset input systems ===
+        // Mouse and Keyboard will be lazy-initialized on next access
+        _mouse = null;
+        _keyboard = null;
+        Mouse.ResetState ();
+
+        // === 7. Clear navigation and screen state ===
+        ScreenChanged = null;
+        Navigation = null;
+
+        // === 8. Reset initialization state ===
+        Initialized = false;
+        MainThreadId = null;
+
+        // === 9. Clear graphics ===
+        Sixel.Clear ();
+
+        // === 10. Reset synchronization context ===
+        // IMPORTANT: Always reset sync context, even if not initialized
+        // This ensures cleanup works correctly even if Shutdown is called without Init
+        // Reset synchronization context to allow the user to run async/await,
+        // as the main loop has been ended, the synchronization context from
+        // gui.cs does no longer process any callbacks. See #1084 for more details:
+        // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
+        SynchronizationContext.SetSynchronizationContext (null);
+
+        // Note: ForceDriver and Force16Colors are NOT reset; 
+        // they need to persist across Init/Shutdown cycles
+    }
+
+    /// <summary>
+    ///     Raises the <see cref="InitializedChanged"/> event.
+    /// </summary>
+    internal void RaiseInitializedChanged (object sender, EventArgs<bool> e) { InitializedChanged?.Invoke (sender, e); }
+}

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

@@ -0,0 +1,347 @@
+#nullable enable
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Terminal.Gui.App;
+
+public partial class ApplicationImpl
+{
+    /// <summary>
+    ///     INTERNAL: Gets or sets the managed thread ID of the application's main UI thread, which is set during
+    ///     <see cref="Init"/> and used to determine if code is executing on the main thread.
+    /// </summary>
+    /// <value>
+    ///     The managed thread ID of the main UI thread, or <see langword="null"/> if the application is not initialized.
+    /// </value>
+    internal int? MainThreadId { get; set; }
+
+    #region Begin->Run->Stop->End
+
+    /// <inheritdoc/>
+    public event EventHandler<SessionTokenEventArgs>? SessionBegun;
+
+    /// <inheritdoc/>
+    public event EventHandler<ToplevelEventArgs>? SessionEnded;
+
+    /// <inheritdoc/>
+    public SessionToken Begin (Toplevel toplevel)
+    {
+        ArgumentNullException.ThrowIfNull (toplevel);
+
+        // Ensure the mouse is ungrabbed.
+        if (Mouse.MouseGrabView is { })
+        {
+            Mouse.UngrabMouse ();
+        }
+
+        var rs = new SessionToken (toplevel);
+
+#if DEBUG_IDISPOSABLE
+        if (View.EnableDebugIDisposableAsserts && Top is { } && toplevel != Top && !TopLevels.Contains (Top))
+        {
+            // This assertion confirm if the Top was already disposed
+            Debug.Assert (Top.WasDisposed);
+            Debug.Assert (Top == CachedSessionTokenToplevel);
+        }
+#endif
+
+        lock (TopLevels)
+        {
+            if (Top is { } && toplevel != Top && !TopLevels.Contains (Top))
+            {
+                // If Top was already disposed and isn't on the Toplevels Stack,
+                // clean it up here if is the same as _CachedSessionTokenToplevel
+                if (Top == CachedSessionTokenToplevel)
+                {
+                    Top = null;
+                }
+                else
+                {
+                    // Probably this will never hit
+                    throw new ObjectDisposedException (Top.GetType ().FullName);
+                }
+            }
+
+            // BUGBUG: We should not depend on `Id` internally.
+            // BUGBUG: It is super unclear what this code does anyway.
+            if (string.IsNullOrEmpty (toplevel.Id))
+            {
+                var count = 1;
+                var id = (TopLevels.Count + count).ToString ();
+
+                while (TopLevels.Count > 0 && TopLevels.FirstOrDefault (x => x.Id == id) is { })
+                {
+                    count++;
+                    id = (TopLevels.Count + count).ToString ();
+                }
+
+                toplevel.Id = (TopLevels.Count + count).ToString ();
+
+                TopLevels.Push (toplevel);
+            }
+            else
+            {
+                Toplevel? dup = TopLevels.FirstOrDefault (x => x.Id == toplevel.Id);
+
+                if (dup is null)
+                {
+                    TopLevels.Push (toplevel);
+                }
+            }
+        }
+
+        if (Top is null)
+        {
+            Top = toplevel;
+        }
+
+        if ((Top?.Modal == false && toplevel.Modal)
+            || (Top?.Modal == false && !toplevel.Modal)
+            || (Top?.Modal == true && toplevel.Modal))
+        {
+            if (toplevel.Visible)
+            {
+                if (Top is { HasFocus: true })
+                {
+                    Top.HasFocus = false;
+                }
+
+                // Force leave events for any entered views in the old Top
+                if (Mouse.GetLastMousePosition () is { })
+                {
+                    Mouse.RaiseMouseEnterLeaveEvents (Mouse.GetLastMousePosition ()!.Value, new ());
+                }
+
+                Top?.OnDeactivate (toplevel);
+                Toplevel previousTop = Top!;
+
+                Top = toplevel;
+                Top.OnActivate (previousTop);
+            }
+        }
+
+        // View implements ISupportInitializeNotification which is derived from ISupportInitialize
+        if (!toplevel.IsInitialized)
+        {
+            toplevel.BeginInit ();
+            toplevel.EndInit (); // Calls Layout
+        }
+
+        // Try to set initial focus to any TabStop
+        if (!toplevel.HasFocus)
+        {
+            toplevel.SetFocus ();
+        }
+
+        toplevel.OnLoaded ();
+
+        Instance.LayoutAndDraw (true);
+
+        if (PositionCursor ())
+        {
+            Driver?.UpdateCursor ();
+        }
+
+        SessionBegun?.Invoke (this, new (rs));
+
+        return rs;
+    }
+
+    /// <inheritdoc/>
+    public bool StopAfterFirstIteration { get; set; }
+
+    /// <inheritdoc/>
+    public event EventHandler<IterationEventArgs>? Iteration;
+
+    /// <inheritdoc/>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public Toplevel Run (Func<Exception, bool>? errorHandler = null, string? driver = null) { return Run<Toplevel> (errorHandler, driver); }
+
+    /// <inheritdoc/>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public TView Run<TView> (Func<Exception, bool>? errorHandler = null, string? driver = null)
+        where TView : Toplevel, new ()
+    {
+        if (!Initialized)
+        {
+            // Init() has NOT been called. Auto-initialize as per interface contract.
+            Init (null, driver);
+        }
+
+        TView top = new ();
+        Run (top, errorHandler);
+
+        return top;
+    }
+
+
+    /// <inheritdoc/>
+    public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
+    {
+        Logging.Information ($"Run '{view}'");
+        ArgumentNullException.ThrowIfNull (view);
+
+        if (!Initialized)
+        {
+            throw new NotInitializedException (nameof (Run));
+        }
+
+        if (Driver == null)
+        {
+            throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view");
+        }
+
+        Top = view;
+
+        SessionToken rs = Application.Begin (view);
+
+        Top.Running = true;
+
+        var firstIteration = true;
+
+        while (TopLevels.TryPeek (out Toplevel? found) && found == view && view.Running)
+        {
+            if (Coordinator is null)
+            {
+                throw new ($"{nameof (IMainLoopCoordinator)} inexplicably became null during Run");
+            }
+
+            Coordinator.RunIteration ();
+
+            if (StopAfterFirstIteration && firstIteration)
+            {
+                Logging.Information ("Run - Stopping after first iteration as requested");
+                view.RequestStop ();
+            }
+
+            firstIteration = false;
+        }
+
+        Logging.Information ("Run - Calling End");
+        Application.End (rs);
+    }
+
+    /// <inheritdoc/>
+    public void End (SessionToken sessionToken)
+    {
+        ArgumentNullException.ThrowIfNull (sessionToken);
+
+        if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
+        {
+            ApplicationPopover.HideWithQuitCommand (visiblePopover);
+        }
+
+        sessionToken.Toplevel.OnUnloaded ();
+
+        // End the Session
+        // First, take it off the Toplevel Stack
+        if (TopLevels.TryPop (out Toplevel? topOfStack))
+        {
+            if (topOfStack != sessionToken.Toplevel)
+            {
+                // If the top of the stack is not the SessionToken.Toplevel then
+                // this call to End is not balanced with the call to Begin that started the Session
+                throw new ArgumentException ("End must be balanced with calls to Begin");
+            }
+        }
+
+        // Notify that it is closing
+        sessionToken.Toplevel?.OnClosed (sessionToken.Toplevel);
+
+        if (TopLevels.TryPeek (out Toplevel? newTop))
+        {
+            Top = newTop;
+            Top?.SetNeedsDraw ();
+        }
+
+        if (sessionToken.Toplevel is { HasFocus: true })
+        {
+            sessionToken.Toplevel.HasFocus = false;
+        }
+
+        if (Top is { HasFocus: false })
+        {
+            Top.SetFocus ();
+        }
+
+        CachedSessionTokenToplevel = sessionToken.Toplevel;
+
+        sessionToken.Toplevel = null;
+        sessionToken.Dispose ();
+
+        // BUGBUG: Why layout and draw here? This causes the screen to be cleared!
+        //LayoutAndDraw (true);
+
+        SessionEnded?.Invoke (this, new (CachedSessionTokenToplevel));
+    }
+
+    /// <inheritdoc/>
+    public void RequestStop () { RequestStop (null); }
+
+    /// <inheritdoc/>
+    public void RequestStop (Toplevel? top)
+    {
+        Logging.Trace ($"Top: '{(top is { } ? top : "null")}'");
+
+        top ??= Top;
+
+        if (top == null)
+        {
+            return;
+        }
+
+        ToplevelClosingEventArgs ev = new (top);
+        top.OnClosing (ev);
+
+        if (ev.Cancel)
+        {
+            return;
+        }
+
+        top.Running = false;
+    }
+
+    /// <inheritdoc/>
+    public void RaiseIteration () { Iteration?.Invoke (null, new ()); }
+
+    #endregion Begin->Run->Stop->End
+
+    #region Timeouts and Invoke
+
+    private readonly ITimedEvents _timedEvents = new TimedEvents ();
+
+    /// <inheritdoc/>
+    public ITimedEvents? TimedEvents => _timedEvents;
+
+    /// <inheritdoc/>
+    public object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.Add (time, callback); }
+
+    /// <inheritdoc/>
+    public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); }
+
+    /// <inheritdoc/>
+    public void Invoke (Action action)
+    {
+        // If we are already on the main UI thread
+        if (Top is { Running: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId)
+        {
+            action ();
+
+            return;
+        }
+
+        _timedEvents.Add (
+                          TimeSpan.Zero,
+                          () =>
+                          {
+                              action ();
+
+                              return false;
+                          }
+                         );
+    }
+
+    #endregion Timeouts and Invoke
+}

+ 177 - 0
Terminal.Gui/App/ApplicationImpl.Screen.cs

@@ -0,0 +1,177 @@
+#nullable enable
+
+namespace Terminal.Gui.App;
+
+public partial class ApplicationImpl
+{
+    /// <inheritdoc/>
+    public event EventHandler<EventArgs<Rectangle>>? ScreenChanged;
+
+    private readonly object _lockScreen = new ();
+    private Rectangle? _screen;
+
+    /// <inheritdoc/>
+    public Rectangle Screen
+    {
+        get
+        {
+            lock (_lockScreen)
+            {
+                if (_screen == null)
+                {
+                    _screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
+                }
+
+                return _screen.Value;
+            }
+        }
+        set
+        {
+            if (value is { } && (value.X != 0 || value.Y != 0))
+            {
+                throw new NotImplementedException ("Screen locations other than 0, 0 are not yet supported");
+            }
+
+            lock (_lockScreen)
+            {
+                _screen = value;
+            }
+        }
+    }
+
+    /// <inheritdoc/>
+    public bool ClearScreenNextIteration { get; set; }
+
+    /// <inheritdoc/>
+    public bool PositionCursor ()
+    {
+        // Find the most focused view and position the cursor there.
+        View? mostFocused = Navigation?.GetFocused ();
+
+        // If the view is not visible or enabled, don't position the cursor
+        if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
+        {
+            var current = CursorVisibility.Invisible;
+            Driver?.GetCursorVisibility (out current);
+
+            if (current != CursorVisibility.Invisible)
+            {
+                Driver?.SetCursorVisibility (CursorVisibility.Invisible);
+            }
+
+            return false;
+        }
+
+        // If the view is not visible within it's superview, don't position the cursor
+        Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty });
+
+        Rectangle superViewViewport =
+            mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver!.Screen;
+
+        if (!superViewViewport.IntersectsWith (mostFocusedViewport))
+        {
+            return false;
+        }
+
+        Point? cursor = mostFocused.PositionCursor ();
+
+        Driver!.GetCursorVisibility (out CursorVisibility currentCursorVisibility);
+
+        if (cursor is { })
+        {
+            // Convert cursor to screen coords
+            cursor = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = cursor.Value }).Location;
+
+            // If the cursor is not in a visible location in the SuperView, hide it
+            if (!superViewViewport.Contains (cursor.Value))
+            {
+                if (currentCursorVisibility != CursorVisibility.Invisible)
+                {
+                    Driver.SetCursorVisibility (CursorVisibility.Invisible);
+                }
+
+                return false;
+            }
+
+            // Show it
+            if (currentCursorVisibility == CursorVisibility.Invisible)
+            {
+                Driver.SetCursorVisibility (mostFocused.CursorVisibility);
+            }
+
+            return true;
+        }
+
+        if (currentCursorVisibility != CursorVisibility.Invisible)
+        {
+            Driver.SetCursorVisibility (CursorVisibility.Invisible);
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     INTERNAL: Resets the Screen field to null so it will be recalculated on next access.
+    /// </summary>
+    private void ResetScreen ()
+    {
+        lock (_lockScreen)
+        {
+            _screen = null;
+        }
+    }
+
+    /// <summary>
+    ///     INTERNAL: Called when the application's size has changed. Sets the size of all <see cref="Toplevel"/>s and fires
+    ///     the
+    ///     <see cref="ScreenChanged"/> event.
+    /// </summary>
+    /// <param name="screen">The new screen size and position.</param>
+    private void RaiseScreenChangedEvent (Rectangle screen)
+    {
+        Screen = new (Point.Empty, screen.Size);
+
+        ScreenChanged?.Invoke (this, new (screen));
+
+        foreach (Toplevel t in TopLevels)
+        {
+            t.OnSizeChanging (new (screen.Size));
+            t.SetNeedsLayout ();
+        }
+
+        LayoutAndDraw (true);
+    }
+
+    private void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { RaiseScreenChangedEvent (new (new (0, 0), e.Size!.Value)); }
+
+    /// <inheritdoc/>
+    public void LayoutAndDraw (bool forceRedraw = false)
+    {
+        List<View> tops = [.. TopLevels];
+
+        if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
+        {
+            visiblePopover.SetNeedsDraw ();
+            visiblePopover.SetNeedsLayout ();
+            tops.Insert (0, visiblePopover);
+        }
+
+        bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Screen.Size);
+
+        if (ClearScreenNextIteration)
+        {
+            forceRedraw = true;
+            ClearScreenNextIteration = false;
+        }
+
+        if (forceRedraw)
+        {
+            Driver?.ClearContents ();
+        }
+
+        View.SetClipToScreen ();
+        View.Draw (tops, neededLayout || forceRedraw);
+        View.SetClipToScreen ();
+        Driver?.Refresh ();
+    }
+}

+ 62 - 286
Terminal.Gui/App/ApplicationImpl.cs

@@ -1,334 +1,110 @@
-#nullable enable
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
+#nullable enable
+using System.Collections.Concurrent;
 
 namespace Terminal.Gui.App;
 
 /// <summary>
-/// Original Terminal.Gui implementation of core <see cref="Application"/> methods.
+///     Implementation of core <see cref="Application"/> methods using the modern
+///     main loop architecture with component factories for different platforms.
 /// </summary>
-public class ApplicationImpl : IApplication
+public partial class ApplicationImpl : IApplication
 {
-    // Private static readonly Lazy instance of Application
-    private static Lazy<IApplication> _lazyInstance = new (() => new ApplicationImpl ());
-
     /// <summary>
-    /// Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
-    /// Change to your own implementation by using <see cref="ChangeInstance"/> (before init).
+    ///     Creates a new instance of the Application backend.
     /// </summary>
-    public static IApplication Instance => _lazyInstance.Value;
-
-
-    /// <inheritdoc/>
-    public virtual ITimedEvents? TimedEvents => Application.MainLoop?.TimedEvents;
+    public ApplicationImpl () { }
 
     /// <summary>
-    /// Handles which <see cref="View"/> (if any) has captured the mouse
+    ///     INTERNAL: Creates a new instance of the Application backend.
     /// </summary>
-    public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler ();
+    /// <param name="componentFactory"></param>
+    internal ApplicationImpl (IComponentFactory componentFactory) { _componentFactory = componentFactory; }
 
-    /// <summary>
-    /// Change the singleton implementation, should not be called except before application
-    /// startup. This method lets you provide alternative implementations of core static gateway
-    /// methods of <see cref="Application"/>.
-    /// </summary>
-    /// <param name="newApplication"></param>
-    public static void ChangeInstance (IApplication newApplication)
-    {
-        _lazyInstance = new Lazy<IApplication> (newApplication);
-    }
+    #region Singleton
 
-    /// <inheritdoc/>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    public virtual void Init (IConsoleDriver? driver = null, string? driverName = null)
-    {
-        Application.InternalInit (driver, string.IsNullOrWhiteSpace (driverName) ? Application.ForceDriver : driverName);
-    }
+    // Private static readonly Lazy instance of Application
+    private static Lazy<IApplication> _lazyInstance = new (() => new ApplicationImpl ());
 
     /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    ///     Change the singleton implementation, should not be called except before application
+    ///     startup. This method lets you provide alternative implementations of core static gateway
+    ///     methods of <see cref="Application"/>.
     /// </summary>
-    /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
-    ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
-    ///         ensure resources are cleaned up and terminal settings restored.
-    ///     </para>
-    ///     <para>
-    ///         The caller is responsible for disposing the object returned by this method.
-    ///     </para>
-    /// </remarks>
-    /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    public Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
+    /// <param name="newApplication"></param>
+    public static void ChangeInstance (IApplication? newApplication) { _lazyInstance = new (newApplication!); }
 
     /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    ///     Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
+    ///     Change to your own implementation by using <see cref="ChangeInstance"/> (before init).
     /// </summary>
-    /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
-    ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
-    ///         ensure resources are cleaned up and terminal settings restored.
-    ///     </para>
-    ///     <para>
-    ///         The caller is responsible for disposing the object returned by this method.
-    ///     </para>
-    /// </remarks>
-    /// <param name="errorHandler"></param>
-    /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
-    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
-    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
-    /// </param>
-    /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    public virtual T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-        where T : Toplevel, new()
-    {
-        if (!Application.Initialized)
-        {
-            // Init() has NOT been called.
-            Application.InternalInit (driver, Application.ForceDriver, true);
-        }
-
-        if (Instance is ApplicationV2)
-        {
-            return Instance.Run<T> (errorHandler, driver);
-        }
-
-        var top = new T ();
+    public static IApplication Instance => _lazyInstance.Value;
 
-        Run (top, errorHandler);
+    #endregion Singleton
 
-        return top;
-    }
+    private string? _driverName;
 
-    /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         This method is used to start processing events for the main application, but it is also used to run other
-    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
-    ///     </para>
-    ///     <para>
-    ///         To make a <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> stop execution, call
-    ///         <see cref="Application.RequestStop"/>.
-    ///     </para>
-    ///     <para>
-    ///         Calling <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
-    ///         <see cref="Application.Begin(Toplevel)"/>, followed by <see cref="Application.RunLoop(RunState)"/>, and then calling
-    ///         <see cref="Application.End(RunState)"/>.
-    ///     </para>
-    ///     <para>
-    ///         Alternatively, to have a program control the main loop and process events manually, call
-    ///         <see cref="Application.Begin(Toplevel)"/> to set things up manually and then repeatedly call
-    ///         <see cref="Application.RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="Application.RunLoop(RunState)"/> method will only process any pending events, timers handlers and then
-    ///         return control immediately.
-    ///     </para>
-    ///     <para>When using <see cref="Run{T}"/> or
-    ///         <see cref="Run(System.Func{System.Exception,bool},IConsoleDriver)"/>
-    ///         <see cref="Init"/> will be called automatically.
-    ///     </para>
-    ///     <para>
-    ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
-    ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
-    ///         returns <see langword="true"/> the <see cref="Application.RunLoop(RunState)"/> will resume; otherwise this method will
-    ///         exit.
-    ///     </para>
-    /// </remarks>
-    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
-    /// <param name="errorHandler">
-    ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
-    ///     rethrows when null).
-    /// </param>
-    public virtual void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
-    {
-        ArgumentNullException.ThrowIfNull (view);
 
-        if (Application.Initialized)
-        {
-            if (Application.Driver is null)
-            {
-                // Disposing before throwing
-                view.Dispose ();
-
-                // This code path should be impossible because Init(null, null) will select the platform default driver
-                throw new InvalidOperationException (
-                                                     "Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called."
-                                                    );
-            }
-        }
-        else
-        {
-            // Init() has NOT been called.
-            throw new InvalidOperationException (
-                                                 "Init() has not been called. Only Run() or Run<T>() can be used without calling Init()."
-                                                );
-        }
+    #region Input
 
-        var resume = true;
+    private IMouse? _mouse;
 
-        while (resume)
+    /// <summary>
+    ///     Handles mouse event state and processing.
+    /// </summary>
+    public IMouse Mouse
+    {
+        get
         {
-#if !DEBUG
-            try
+            if (_mouse is null)
             {
-#endif
-                resume = false;
-                RunState runState = Application.Begin (view);
-
-                // If EndAfterFirstIteration is true then the user must dispose of the runToken
-                // by using NotifyStopRunState event.
-                Application.RunLoop (runState);
-
-                if (runState.Toplevel is null)
-                {
-#if DEBUG_IDISPOSABLE
-                if (View.EnableDebugIDisposableAsserts)
-                {
-                    Debug.Assert (Application.TopLevels.Count == 0);
-                }
-#endif
-                    runState.Dispose ();
-
-                    return;
-                }
-
-                if (!Application.EndAfterFirstIteration)
-                {
-                    Application.End (runState);
-                }
-#if !DEBUG
+                _mouse = new MouseImpl { Application = this };
             }
-            catch (Exception error)
-            {
-                Logging.Warning ($"Release Build Exception: {error}");
-                if (errorHandler is null)
-                {
-                    throw;
-                }
 
-                resume = errorHandler (error);
-            }
-#endif
+            return _mouse;
         }
+        set => _mouse = value ?? throw new ArgumentNullException (nameof (value));
     }
 
-    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
-    /// <remarks>
-    ///     Shutdown must be called for every call to <see cref="Init"/> or
-    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
-    ///     up (Disposed)
-    ///     and terminal settings are restored.
-    /// </remarks>
-    public virtual void Shutdown ()
-    {
-        // TODO: Throw an exception if Init hasn't been called.
-
-        bool wasInitialized = Application.Initialized;
-        Application.ResetState ();
-        ConfigurationManager.PrintJsonErrors ();
-
-        if (wasInitialized)
-        {
-            bool init = Application.Initialized;
-
-            Application.OnInitializedChanged (this, new (in init));
-        }
+    private IKeyboard? _keyboard;
+    private bool _stopAfterFirstIteration;
 
-        _lazyInstance = new (() => new ApplicationImpl ());
-    }
-
-    /// <inheritdoc />
-    public virtual void RequestStop (Toplevel? top)
+    /// <summary>
+    ///     Handles keyboard input and key bindings at the Application level
+    /// </summary>
+    public IKeyboard Keyboard
     {
-        top ??= Application.Top;
-
-        if (!top!.Running)
+        get
         {
-            return;
-        }
-
-        var ev = new ToplevelClosingEventArgs (top);
-        top.OnClosing (ev);
+            if (_keyboard is null)
+            {
+                _keyboard = new KeyboardImpl { Application = this };
+            }
 
-        if (ev.Cancel)
-        {
-            return;
+            return _keyboard;
         }
-
-        top.Running = false;
-        Application.OnNotifyStopRunState (top);
+        set => _keyboard = value ?? throw new ArgumentNullException (nameof (value));
     }
 
-    /// <inheritdoc />
-    public virtual void Invoke (Action action)
-    {
+    #endregion Input
 
-        // If we are already on the main UI thread
-        if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId)
-        {
-            action ();
-            WakeupMainLoop ();
+    #region View Management
 
-            return;
-        }
-
-        if (Application.MainLoop == null)
-        {
-            Logging.Warning ("Ignored Invoke because MainLoop is not initialized yet");
-            return;
-        }
-
-
-        Application.AddTimeout (TimeSpan.Zero,
-                           () =>
-                           {
-                               action ();
-
-                               return false;
-                           }
-                          );
+    /// <inheritdoc/>
+    public ApplicationPopover? Popover { get; set; }
 
-        WakeupMainLoop ();
+    /// <inheritdoc/>
+    public ApplicationNavigation? Navigation { get; set; }
 
-        void WakeupMainLoop ()
-        {
-            // Ensure the action is executed in the main loop
-            // Wakeup mainloop if it's waiting for events
-            Application.MainLoop?.Wakeup ();
-        }
-    }
+    /// <inheritdoc/>
+    public Toplevel? Top { get; set; }
 
-    /// <inheritdoc />
-    public bool IsLegacy { get; protected set; } = true;
+    // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What
 
-    /// <inheritdoc />
-    public virtual object AddTimeout (TimeSpan time, Func<bool> callback)
-    {
-        if (Application.MainLoop is null)
-        {
-            throw new NotInitializedException ("Cannot add timeout before main loop is initialized", null);
-        }
-
-        return Application.MainLoop.TimedEvents.Add (time, callback);
-    }
+    /// <inheritdoc/>
+    public ConcurrentStack<Toplevel> TopLevels { get; } = new ();
 
-    /// <inheritdoc />
-    public virtual bool RemoveTimeout (object token)
-    {
-        return Application.MainLoop?.TimedEvents.Remove (token) ?? false;
-    }
+    /// <inheritdoc/>
+    public Toplevel? CachedSessionTokenToplevel { get; set; }
 
-    /// <inheritdoc />
-    public virtual void LayoutAndDraw (bool forceDraw)
-    {
-        Application.LayoutAndDrawImpl (forceDraw);
-    }
+    #endregion View Management
 }

+ 28 - 14
Terminal.Gui/App/CWP/CWPPropertyHelper.cs

@@ -22,10 +22,14 @@ public static class CWPPropertyHelper
     ///     The type of the property value, which may be a nullable reference type (e.g., <see cref="string"/>
     ///     ?).
     /// </typeparam>
-    /// <param name="currentValue">The current property value, which may be null for nullable types.</param>
+    /// <param name="currentValue">
+    ///     Reference to the current property value, which may be null for nullable types. If the change is not cancelled, this
+    ///     will be set to <paramref name="finalValue"/>.
+    /// </param>
     /// <param name="newValue">The proposed new property value, which may be null for nullable types.</param>
     /// <param name="onChanging">The virtual method invoked before the change, returning true to cancel.</param>
     /// <param name="changingEvent">The pre-change event raised to allow modification or cancellation.</param>
+    /// <param name="doWork">The action that performs the actual work of setting the property (e.g., updating backing field, calling related methods).</param>
     /// <param name="onChanged">The virtual method invoked after the change.</param>
     /// <param name="changedEvent">The post-change event raised to notify of the completed change.</param>
     /// <param name="finalValue">
@@ -39,22 +43,23 @@ public static class CWPPropertyHelper
     /// </exception>
     /// <example>
     ///     <code>
-    ///         string? current = null;
+    ///         string? current = _schemeName;
     ///         string? proposed = "Base";
-    ///         Func&lt;ValueChangingEventArgs&lt;string?&gt;, bool&gt; onChanging = args =&gt; false;
-    ///         EventHandler&lt;ValueChangingEventArgs&lt;string?&gt;&gt;? changingEvent = null;
-    ///         Action&lt;ValueChangedEventArgs&lt;string?&gt;&gt;? onChanged = args =&gt;
-    ///             Console.WriteLine($"SchemeName changed to {args.NewValue ?? "none"}.");
-    ///         EventHandler&lt;ValueChangedEventArgs&lt;string?&gt;&gt;? changedEvent = null;
+    ///         Func&lt;ValueChangingEventArgs&lt;string?&gt;, bool&gt; onChanging = OnSchemeNameChanging;
+    ///         EventHandler&lt;ValueChangingEventArgs&lt;string?&gt;&gt;? changingEvent = SchemeNameChanging;
+    ///         Action&lt;string?&gt; doWork = value => _schemeName = value;
+    ///         Action&lt;ValueChangedEventArgs&lt;string?&gt;&gt;? onChanged = OnSchemeNameChanged;
+    ///         EventHandler&lt;ValueChangedEventArgs&lt;string?&gt;&gt;? changedEvent = SchemeNameChanged;
     ///         bool changed = CWPPropertyHelper.ChangeProperty(
-    ///             current, proposed, onChanging, changingEvent, onChanged, changedEvent, out string? final);
+    ///             current, proposed, onChanging, changingEvent, doWork, onChanged, changedEvent, out string? final);
     ///     </code>
     /// </example>
     public static bool ChangeProperty<T> (
-        T currentValue,
+        ref T currentValue,
         T newValue,
-        Func<ValueChangingEventArgs<T>, bool> onChanging,
+        Func<ValueChangingEventArgs<T>, bool>? onChanging,
         EventHandler<ValueChangingEventArgs<T>>? changingEvent,
+        Action<T> doWork,
         Action<ValueChangedEventArgs<T>>? onChanged,
         EventHandler<ValueChangedEventArgs<T>>? changedEvent,
         out T finalValue
@@ -68,13 +73,17 @@ public static class CWPPropertyHelper
         }
 
         ValueChangingEventArgs<T> args = new (currentValue, newValue);
-        bool cancelled = onChanging (args) || args.Handled;
 
-        if (cancelled)
+        if (onChanging is { })
         {
-            finalValue = currentValue;
+            bool cancelled = onChanging (args) || args.Handled;
 
-            return false;
+            if (cancelled)
+            {
+                finalValue = currentValue;
+
+                return false;
+            }
         }
 
         changingEvent?.Invoke (null, args);
@@ -93,7 +102,12 @@ public static class CWPPropertyHelper
         }
 
         finalValue = args.NewValue;
+        
+        // Do the work (set backing field, update related properties, etc.) BEFORE raising Changed events
+        doWork (finalValue);
+        
         ValueChangedEventArgs<T> changedArgs = new (currentValue, finalValue);
+        currentValue = finalValue;
         onChanged?.Invoke (changedArgs);
         changedEvent?.Invoke (null, changedArgs);
 

+ 2 - 2
Terminal.Gui/App/Clipboard/ClipboardBase.cs

@@ -103,7 +103,7 @@ public abstract class ClipboardBase : IClipboard
     }
 
     /// <summary>
-    ///     Returns the contents of the OS clipboard if possible. Implemented by <see cref="IConsoleDriver"/>-specific
+    ///     Returns the contents of the OS clipboard if possible. Implemented by <see cref="IDriver"/>-specific
     ///     subclasses.
     /// </summary>
     /// <returns>The contents of the OS clipboard if successful.</returns>
@@ -111,7 +111,7 @@ public abstract class ClipboardBase : IClipboard
     protected abstract string GetClipboardDataImpl ();
 
     /// <summary>
-    ///     Pastes the <paramref name="text"/> to the OS clipboard if possible. Implemented by <see cref="IConsoleDriver"/>
+    ///     Pastes the <paramref name="text"/> to the OS clipboard if possible. Implemented by <see cref="IDriver"/>
     ///     -specific subclasses.
     /// </summary>
     /// <param name="text">The text to paste to the OS clipboard.</param>

+ 1 - 1
Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs

@@ -5,7 +5,7 @@ namespace Terminal.Gui.App;
 
 /// <summary>
 ///     Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
-///     CursesDriver, but also used in Unit tests which is why it is in IConsoleDriver.cs.
+///     UnixDriver, but also used in Unit tests which is why it is in IDriver.cs.
 /// </summary>
 internal static class ClipboardProcessRunner
 {

+ 449 - 104
Terminal.Gui/App/IApplication.cs

@@ -1,197 +1,542 @@
 #nullable enable
+using System.Collections.Concurrent;
 using System.Diagnostics.CodeAnalysis;
 
 namespace Terminal.Gui.App;
 
 /// <summary>
-/// Interface for instances that provide backing functionality to static
-/// gateway class <see cref="Application"/>.
+///     Interface for instances that provide backing functionality to static
+///     gateway class <see cref="Application"/>.
 /// </summary>
 public interface IApplication
 {
+    #region Keyboard
+
     /// <summary>
-    /// Handles recurring events. These are invoked on the main UI thread - allowing for
-    /// safe updates to <see cref="View"/> instances.
+    ///     Handles keyboard input and key bindings at the Application level.
     /// </summary>
-    ITimedEvents? TimedEvents { get; }
+    /// <remarks>
+    ///     <para>
+    ///         Provides access to keyboard state, key bindings, and keyboard event handling. Set during <see cref="Init"/>.
+    ///     </para>
+    /// </remarks>
+    IKeyboard Keyboard { get; set; }
+
+    #endregion Keyboard
+
+    #region Mouse
 
     /// <summary>
-    /// Handles grabbing the mouse (only a single <see cref="View"/> can grab the mouse at once).
+    ///     Handles mouse event state and processing.
     /// </summary>
-    IMouseGrabHandler MouseGrabHandler { get; set; }
+    /// <remarks>
+    ///     <para>
+    ///         Provides access to mouse state, mouse grabbing, and mouse event handling. Set during <see cref="Init"/>.
+    ///     </para>
+    /// </remarks>
+    IMouse Mouse { get; set; }
+
+    #endregion Mouse
+
+    #region Initialization and Shutdown
 
     /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
-    /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
-    /// <para>
-    ///     This function loads the right <see cref="IConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
-    ///     assigns it to <see cref="Application.Top"/>
-    /// </para>
-    /// <para>
-    ///     <see cref="Shutdown"/> must be called when the application is closing (typically after
-    ///     <see cref="Run{T}"/> has returned) to ensure resources are cleaned up and
-    ///     terminal settings
-    ///     restored.
-    /// </para>
-    /// <para>
-    ///     The <see cref="Run{T}"/> function combines
-    ///     <see cref="Init(IConsoleDriver,string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
-    ///     into a single
-    ///     call. An application cam use <see cref="Run{T}"/> without explicitly calling
-    ///     <see cref="Init(IConsoleDriver,string)"/>.
-    /// </para>
     /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or
+    ///     The <see cref="IDriver"/> to use. If neither <paramref name="driver"/> or
     ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
     /// </param>
     /// <param name="driverName">
-    ///     The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the
-    ///     <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
+    ///     The short name (e.g. "dotnet", "windows", "unix", or "fake") of the
+    ///     <see cref="IDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
     ///     specified the default driver for the platform will be used.
     /// </param>
+    /// <remarks>
+    ///     <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
+    ///     <para>
+    ///         This function loads the right <see cref="IDriver"/> for the platform, creates a main loop coordinator,
+    ///         initializes keyboard and mouse handlers, and subscribes to driver events.
+    ///     </para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after
+    ///         <see cref="Run{T}"/> has returned) to ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         The <see cref="Run{T}"/> function combines <see cref="Init(IDriver,string)"/> and
+    ///         <see cref="Run(Toplevel, Func{Exception, bool})"/> into a single call. An application can use
+    ///         <see cref="Run{T}"/> without explicitly calling <see cref="Init(IDriver,string)"/>.
+    ///     </para>
+    /// </remarks>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public void Init (IConsoleDriver? driver = null, string? driverName = null);
+    public void Init (IDriver? driver = null, string? driverName = null);
+
+    /// <summary>
+    ///     This event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
+    /// </summary>
+    /// <remarks>
+    ///     Intended to support unit tests that need to know when the application has been initialized.
+    /// </remarks>
+    public event EventHandler<EventArgs<bool>>? InitializedChanged;
+
+    /// <summary>Gets or sets whether the application has been initialized.</summary>
+    bool Initialized { get; set; }
+
+    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    /// <remarks>
+    ///     Shutdown must be called for every call to <see cref="Init"/> or
+    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
+    ///     up (Disposed) and terminal settings are restored.
+    /// </remarks>
+    public void Shutdown ();
+
+    /// <summary>
+    ///     Resets the state of this instance.
+    /// </summary>
+    /// <param name="ignoreDisposed">If true, ignores disposed state checks during reset.</param>
+    /// <remarks>
+    ///     <para>
+    ///         Encapsulates all setting of initial state for Application; having this in a function like this ensures we
+    ///         don't make mistakes in guaranteeing that the state of this singleton is deterministic when <see cref="Init"/>
+    ///         starts running and after <see cref="Shutdown"/> returns.
+    ///     </para>
+    ///     <para>
+    ///         IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
+    ///     </para>
+    /// </remarks>
+    public void ResetState (bool ignoreDisposed = false);
+
+    #endregion Initialization and Shutdown
 
+    #region Begin->Run->Iteration->Stop->End
 
     /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    ///     Building block API: Creates a <see cref="SessionToken"/> and prepares the provided <see cref="Toplevel"/> for
+    ///     execution. Not usually called directly by applications. Use <see cref="Run(Toplevel, Func{Exception, bool})"/>
+    ///     instead.
     /// </summary>
+    /// <returns>
+    ///     The <see cref="SessionToken"/> that needs to be passed to the <see cref="End(SessionToken)"/> method upon
+    ///     completion.
+    /// </returns>
+    /// <param name="toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
+    /// <remarks>
+    ///     <para>
+    ///         This method prepares the provided <see cref="Toplevel"/> for running. It adds this to the
+    ///         list of <see cref="Toplevel"/>s, lays out the SubViews, focuses the first element, and draws the
+    ///         <see cref="Toplevel"/> on the screen. This is usually followed by starting the main loop, and then the
+    ///         <see cref="End(SessionToken)"/> method upon termination which will undo these changes.
+    ///     </para>
+    ///     <para>
+    ///         Raises the <see cref="SessionBegun"/> event before returning.
+    ///     </para>
+    /// </remarks>
+    public SessionToken Begin (Toplevel toplevel);
+
+    /// <summary>
+    ///     Runs a new Session creating a <see cref="Toplevel"/> and calling <see cref="Begin(Toplevel)"/>. When the session is
+    ///     stopped, <see cref="End(SessionToken)"/> will be called.
+    /// </summary>
+    /// <param name="errorHandler">Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
+    /// <param name="driver">
+    ///     The driver name. If not specified the default driver for the platform will be used. Must be
+    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
+    /// </param>
+    /// <returns>The created <see cref="Toplevel"/>. The caller is responsible for disposing this object.</returns>
     /// <remarks>
     ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
     ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run has returned) to
     ///         ensure resources are cleaned up and terminal settings restored.
     ///     </para>
     ///     <para>
     ///         The caller is responsible for disposing the object returned by this method.
     ///     </para>
     /// </remarks>
-    /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null);
+    public Toplevel Run (Func<Exception, bool>? errorHandler = null, string? driver = null);
 
     /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    ///     Runs a new Session creating a <see cref="Toplevel"/>-derived object of type <typeparamref name="TView"/>
+    ///     and calling <see cref="Run(Toplevel, Func{Exception, bool})"/>. When the session is stopped,
+    ///     <see cref="End(SessionToken)"/> will be called.
     /// </summary>
+    /// <typeparam name="TView">The type of <see cref="Toplevel"/> to create and run.</typeparam>
+    /// <param name="errorHandler">Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
+    /// <param name="driver">
+    ///     The driver name. If not specified the default driver for the platform will be used. Must be
+    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
+    /// </param>
+    /// <returns>The created <typeparamref name="TView"/> object. The caller is responsible for disposing this object.</returns>
     /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
     ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
+    ///         This method is used to start processing events for the main application, but it is also used to run other
+    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
+    ///     </para>
+    ///     <para>
+    ///         To make <see cref="Run(Toplevel, Func{Exception, bool})"/> stop execution, call
+    ///         <see cref="RequestStop()"/> or <see cref="RequestStop(Toplevel)"/>.
+    ///     </para>
+    ///     <para>
+    ///         Calling <see cref="Run(Toplevel, Func{Exception, bool})"/> is equivalent to calling
+    ///         <see cref="Begin(Toplevel)"/>, followed by starting the main loop, and then calling
+    ///         <see cref="End(SessionToken)"/>.
+    ///     </para>
+    ///     <para>
+    ///         When using <see cref="Run{T}"/> or <see cref="Run(Func{Exception, bool}, string)"/>,
+    ///         <see cref="Init"/> will be called automatically.
+    ///     </para>
+    ///     <para>
+    ///         In RELEASE builds: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
+    ///         rethrown. Otherwise, <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
+    ///         returns <see langword="true"/> the main loop will resume; otherwise this method will exit.
+    ///     </para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run has returned) to
     ///         ensure resources are cleaned up and terminal settings restored.
     ///     </para>
     ///     <para>
+    ///         In RELEASE builds: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
+    ///         rethrown. Otherwise, <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
+    ///         returns <see langword="true"/> the main loop will resume; otherwise this method will exit.
+    ///     </para>
+    ///     <para>
     ///         The caller is responsible for disposing the object returned by this method.
     ///     </para>
     /// </remarks>
-    /// <param name="errorHandler"></param>
-    /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
-    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
-    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
-    /// </param>
-    /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-        where T : Toplevel, new ();
+    public TView Run<TView> (Func<Exception, bool>? errorHandler = null, string? driver = null)
+        where TView : Toplevel, new ();
 
-    /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
+    /// <summary>
+    ///     Runs a new Session using the provided <see cref="Toplevel"/> view and calling
+    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    ///     When the session is stopped, <see cref="End(SessionToken)"/> will be called..
+    /// </summary>
+    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
+    /// <param name="errorHandler">Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
     /// <remarks>
     ///     <para>
     ///         This method is used to start processing events for the main application, but it is also used to run other
     ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
     ///     </para>
     ///     <para>
-    ///         To make a <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> stop execution, call
-    ///         <see cref="Application.RequestStop"/>.
+    ///         To make <see cref="Run(Toplevel, Func{Exception, bool})"/> stop execution, call
+    ///         <see cref="RequestStop()"/> or <see cref="RequestStop(Toplevel)"/>.
     ///     </para>
     ///     <para>
-    ///         Calling <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
-    ///         <see cref="Application.Begin(Toplevel)"/>, followed by <see cref="Application.RunLoop(RunState)"/>, and then calling
-    ///         <see cref="Application.End(RunState)"/>.
+    ///         Calling <see cref="Run(Toplevel, Func{Exception, bool})"/> is equivalent to calling
+    ///         <see cref="Begin(Toplevel)"/>, followed by starting the main loop, and then calling
+    ///         <see cref="End(SessionToken)"/>.
     ///     </para>
     ///     <para>
-    ///         Alternatively, to have a program control the main loop and process events manually, call
-    ///         <see cref="Application.Begin(Toplevel)"/> to set things up manually and then repeatedly call
-    ///         <see cref="Application.RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="Application.RunLoop(RunState)"/> method will only process any pending events, timers handlers and then
-    ///         return control immediately.
-    ///     </para>
-    ///     <para>When using <see cref="Run{T}"/> or
-    ///         <see cref="Run(System.Func{System.Exception,bool},IConsoleDriver)"/>
+    ///         When using <see cref="Run{T}"/> or <see cref="Run(Func{Exception, bool}, string)"/>,
     ///         <see cref="Init"/> will be called automatically.
     ///     </para>
     ///     <para>
-    ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
-    ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
-    ///         returns <see langword="true"/> the <see cref="Application.RunLoop(RunState)"/> will resume; otherwise this method will
-    ///         exit.
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         In RELEASE builds: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
+    ///         rethrown. Otherwise, <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
+    ///         returns <see langword="true"/> the main loop will resume; otherwise this method will exit.
+    ///     </para>
+    ///     <para>
+    ///         The caller is responsible for disposing the object returned by this method.
     ///     </para>
     /// </remarks>
-    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
-    /// <param name="errorHandler">
-    ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
-    ///     rethrows when null).
-    /// </param>
     public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null);
 
-    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    /// <summary>
+    ///     Raises the <see cref="Iteration"/> event.
+    /// </summary>
     /// <remarks>
-    ///     Shutdown must be called for every call to <see cref="Init"/> or
-    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
-    ///     up (Disposed)
-    ///     and terminal settings are restored.
+    ///     This is called once per main loop iteration, before processing input, timeouts, or rendering.
     /// </remarks>
-    public void Shutdown ();
+    public void RaiseIteration ();
+
+    /// <summary>This event is raised on each iteration of the main loop.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This event is raised before input processing, timeout callbacks, and rendering occur each iteration.
+    ///     </para>
+    ///     <para>See also <see cref="AddTimeout"/> and <see cref="TimedEvents"/>.</para>
+    /// </remarks>
+    public event EventHandler<IterationEventArgs>? Iteration;
+
+    /// <summary>Runs <paramref name="action"/> on the main UI loop thread.</summary>
+    /// <param name="action">The action to be invoked on the main processing thread.</param>
+    /// <remarks>
+    ///     <para>
+    ///         If called from the main thread, the action is executed immediately. Otherwise, it is queued via
+    ///         <see cref="AddTimeout"/> with <see cref="TimeSpan.Zero"/> and will be executed on the next main loop
+    ///         iteration.
+    ///     </para>
+    /// </remarks>
+    void Invoke (Action action);
+
+    /// <summary>
+    ///     Building block API: Ends a Session and completes the execution of a <see cref="Toplevel"/> that was started with
+    ///     <see cref="Begin(Toplevel)"/>. Not usually called directly by applications.
+    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>
+    ///     will automatically call this method when the session is stopped.
+    /// </summary>
+    /// <param name="sessionToken">The <see cref="SessionToken"/> returned by the <see cref="Begin(Toplevel)"/> method.</param>
+    /// <remarks>
+    ///     <para>
+    ///         This method removes the <see cref="Toplevel"/> from the stack, raises the <see cref="SessionEnded"/>
+    ///         event, and disposes the <paramref name="sessionToken"/>.
+    ///     </para>
+    /// </remarks>
+    public void End (SessionToken sessionToken);
 
-    /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
-    /// <param name="top">The <see cref="Toplevel"/> to stop.</param>
+    /// <summary>Requests that the currently running Session stop. The Session will stop after the current iteration completes.</summary>
     /// <remarks>
-    ///     <para>This will cause <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to return.</para>
+    ///     <para>This will cause <see cref="Run(Toplevel, Func{Exception, bool})"/> to return.</para>
+    ///     <para>
+    ///         This is equivalent to calling <see cref="RequestStop(Toplevel)"/> with <see cref="Top"/> as the parameter.
+    ///     </para>
+    /// </remarks>
+    void RequestStop ();
+
+    /// <summary>Requests that the currently running Session stop. The Session will stop after the current iteration completes.</summary>
+    /// <param name="top">
+    ///     The <see cref="Toplevel"/> to stop. If <see langword="null"/>, stops the currently running <see cref="Top"/>.
+    /// </param>
+    /// <remarks>
+    ///     <para>This will cause <see cref="Run(Toplevel, Func{Exception, bool})"/> to return.</para>
     ///     <para>
     ///         Calling <see cref="RequestStop(Toplevel)"/> is equivalent to setting the <see cref="Toplevel.Running"/>
-    ///         property on the currently running <see cref="Toplevel"/> to false.
+    ///         property on the specified <see cref="Toplevel"/> to <see langword="false"/>.
     ///     </para>
     /// </remarks>
     void RequestStop (Toplevel? top);
 
-    /// <summary>Runs <paramref name="action"/> on the main UI loop thread</summary>
-    /// <param name="action">the action to be invoked on the main processing thread.</param>
-    void Invoke (Action action);
+    /// <summary>
+    ///     Set to <see langword="true"/> to cause the session to stop running after first iteration.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Used primarily for unit testing. When <see langword="true"/>, <see cref="End"/> will be called
+    ///         automatically after the first main loop iteration.
+    ///     </para>
+    /// </remarks>
+    bool StopAfterFirstIteration { get; set; }
+
+    /// <summary>
+    ///     Raised when <see cref="Begin(Toplevel)"/> has been called and has created a new <see cref="SessionToken"/>.
+    /// </summary>
+    /// <remarks>
+    ///     If <see cref="StopAfterFirstIteration"/> is <see langword="true"/>, callers to <see cref="Begin(Toplevel)"/>
+    ///     must also subscribe to <see cref="SessionEnded"/> and manually dispose of the <see cref="SessionToken"/> token
+    ///     when the application is done.
+    /// </remarks>
+    public event EventHandler<SessionTokenEventArgs>? SessionBegun;
+
+    /// <summary>
+    ///     Raised when <see cref="End(SessionToken)"/> was called and the session is stopping. The event args contain a
+    ///     reference to the <see cref="Toplevel"/>
+    ///     that was active during the session. This can be used to ensure the Toplevel is disposed of properly.
+    /// </summary>
+    /// <remarks>
+    ///     If <see cref="StopAfterFirstIteration"/> is <see langword="true"/>, callers to <see cref="Begin(Toplevel)"/>
+    ///     must also subscribe to <see cref="SessionEnded"/> and manually dispose of the <see cref="SessionToken"/> token
+    ///     when the application is done.
+    /// </remarks>
+    public event EventHandler<ToplevelEventArgs>? SessionEnded;
+
+    #endregion Begin->Run->Iteration->Stop->End
+
+    #region Toplevel Management
+
+    /// <summary>Gets or sets the current Toplevel.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This is set by <see cref="Begin(Toplevel)"/> and cleared by <see cref="End(SessionToken)"/>.
+    ///     </para>
+    /// </remarks>
+    Toplevel? Top { get; set; }
+
+    /// <summary>Gets the stack of all Toplevels.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Toplevels are added to this stack by <see cref="Begin(Toplevel)"/> and removed by
+    ///         <see cref="End(SessionToken)"/>.
+    ///     </para>
+    /// </remarks>
+    ConcurrentStack<Toplevel> TopLevels { get; }
+
+    /// <summary>
+    ///     Caches the Toplevel associated with the current Session.
+    /// </summary>
+    /// <remarks>
+    ///     Used internally to optimize Toplevel state transitions.
+    /// </remarks>
+    Toplevel? CachedSessionTokenToplevel { get; set; }
+
+    #endregion Toplevel Management
+
+    #region Screen and Driver
+
+    /// <summary>Gets or sets the console driver being used.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Set by <see cref="Init"/> based on the driver parameter or platform default.
+    ///     </para>
+    /// </remarks>
+    IDriver? Driver { get; set; }
+
+    /// <summary>
+    ///     Gets or sets whether <see cref="Driver"/> will be forced to output only the 16 colors defined in
+    ///     <see cref="ColorName16"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be
+    ///     output as long as the selected <see cref="IDriver"/> supports TrueColor.
+    /// </summary>
+    bool Force16Colors { get; set; }
+
+    /// <summary>
+    ///     Forces the use of the specified driver (one of "fake", "dotnet", "windows", or "unix"). If not
+    ///     specified, the driver is selected based on the platform.
+    /// </summary>
+    string ForceDriver { get; set; }
+
+    /// <summary>
+    ///     Gets or sets the size of the screen. By default, this is the size of the screen as reported by the
+    ///     <see cref="IDriver"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If the <see cref="IDriver"/> has not been initialized, this will return a default size of 2048x2048; useful
+    ///         for unit tests.
+    ///     </para>
+    /// </remarks>
+    Rectangle Screen { get; set; }
+
+    /// <summary>Raised when the terminal's size changed. The new size of the terminal is provided.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This event is raised when the driver detects a screen size change. The event provides the new screen
+    ///         rectangle.
+    ///     </para>
+    /// </remarks>
+    public event EventHandler<EventArgs<Rectangle>>? ScreenChanged;
+
+    /// <summary>
+    ///     Gets or sets whether the screen will be cleared, and all Views redrawn, during the next Application iteration.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This is typically set to <see langword="true"/> when a View's <see cref="View.Frame"/> changes and that view
+    ///         has no SuperView (e.g. when <see cref="Top"/> is moved or resized).
+    ///     </para>
+    ///     <para>
+    ///         Automatically reset to <see langword="false"/> after <see cref="LayoutAndDraw"/> processes it.
+    ///     </para>
+    /// </remarks>
+    bool ClearScreenNextIteration { get; set; }
+
+    /// <summary>
+    ///     Collection of sixel images to write out to screen when updating.
+    ///     Only add to this collection if you are sure terminal supports sixel format.
+    /// </summary>
+    List<SixelToRender> Sixel { get; }
+
+    #endregion Screen and Driver
+
+    #region Layout and Drawing
+
+    /// <summary>
+    ///     Causes any Toplevels that need layout to be laid out, then draws any Toplevels that need display. Only Views
+    ///     that need to be laid out (see <see cref="View.NeedsLayout"/>) will be laid out. Only Views that need to be drawn
+    ///     (see <see cref="View.NeedsDraw"/>) will be drawn.
+    /// </summary>
+    /// <param name="forceRedraw">
+    ///     If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and
+    ///     should only be overridden for testing.
+    /// </param>
+    /// <remarks>
+    ///     <para>
+    ///         This method is called automatically each main loop iteration when any views need layout or drawing.
+    ///     </para>
+    ///     <para>
+    ///         If <see cref="ClearScreenNextIteration"/> is <see langword="true"/>, the screen will be cleared before
+    ///         drawing and the flag will be reset to <see langword="false"/>.
+    ///     </para>
+    /// </remarks>
+    public void LayoutAndDraw (bool forceRedraw = false);
 
     /// <summary>
-    /// <see langword="true"/> if implementation is 'old'. <see langword="false"/> if implementation
-    /// is cutting edge.
+    ///     Calls <see cref="View.PositionCursor"/> on the most focused view.
     /// </summary>
-    bool IsLegacy { get; }
-    
+    /// <remarks>
+    ///     <para>Does nothing if there is no most focused view.</para>
+    ///     <para>
+    ///         If the most focused view is not visible within its superview, the cursor will be hidden.
+    ///     </para>
+    /// </remarks>
+    /// <returns><see langword="true"/> if a view positioned the cursor and the position is visible.</returns>
+    public bool PositionCursor ();
+
+    #endregion Layout and Drawing
+
+    #region Navigation and Popover
+
+    /// <summary>Gets or sets the popover manager.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Manages application-level popover views. Initialized during <see cref="Init"/>.
+    ///     </para>
+    /// </remarks>
+    ApplicationPopover? Popover { get; set; }
+
+    /// <summary>Gets or sets the navigation manager.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Manages focus navigation and tracking of the most focused view. Initialized during <see cref="Init"/>.
+    ///     </para>
+    /// </remarks>
+    ApplicationNavigation? Navigation { get; set; }
+
+    #endregion Navigation and Popover
+
+    #region Timeouts
+
     /// <summary>Adds a timeout to the application.</summary>
+    /// <param name="time">The time span to wait before invoking the callback.</param>
+    /// <param name="callback">
+    ///     The callback to invoke. If it returns <see langword="true"/>, the timeout will be reset and repeat. If it
+    ///     returns <see langword="false"/>, the timeout will stop and be removed.
+    /// </param>
+    /// <returns>
+    ///     A token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
+    /// </returns>
     /// <remarks>
-    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
-    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
-    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
+    ///     <para>
+    ///         When the time specified passes, the callback will be invoked on the main UI thread.
+    ///     </para>
     /// </remarks>
     object AddTimeout (TimeSpan time, Func<bool> callback);
 
-    /// <summary>Removes a previously scheduled timeout</summary>
-    /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
+    /// <summary>Removes a previously scheduled timeout.</summary>
+    /// <param name="token">The token returned by <see cref="AddTimeout"/>.</param>
     /// <returns>
-    /// <see langword="true"/>
-    /// if the timeout is successfully removed; otherwise,
-    /// <see langword="false"/>
-    /// .
-    /// This method also returns
-    /// <see langword="false"/>
-    /// if the timeout is not found.</returns>
+    ///     <see langword="true"/> if the timeout is successfully removed; otherwise, <see langword="false"/>.
+    ///     This method also returns <see langword="false"/> if the timeout is not found.
+    /// </returns>
     bool RemoveTimeout (object token);
 
     /// <summary>
-    /// Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that need to be laid out (see <see cref="View.NeedsLayout"/>) will be laid out.
-    /// Only Views that need to be drawn (see <see cref="View.NeedsDraw"/>) will be drawn.
+    ///     Handles recurring events. These are invoked on the main UI thread - allowing for
+    ///     safe updates to <see cref="View"/> instances.
     /// </summary>
-    /// <param name="forceDraw">If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and should only be overriden for testing.</param>
-    void LayoutAndDraw (bool forceDraw);
-}
+    /// <remarks>
+    ///     <para>
+    ///         Provides low-level access to the timeout management system. Most applications should use
+    ///         <see cref="AddTimeout"/> and <see cref="RemoveTimeout"/> instead.
+    ///     </para>
+    /// </remarks>
+    ITimedEvents? TimedEvents { get; }
+
+    #endregion Timeouts
+}

+ 1 - 1
Terminal.Gui/App/IterationEventArgs.cs

@@ -1,5 +1,5 @@
 namespace Terminal.Gui.App;
 
-/// <summary>Event arguments for the <see cref="Application.Iteration"/> event.</summary>
+/// <summary>Event arguments for the <see cref="IApplication.Iteration"/> event.</summary>
 public class IterationEventArgs : EventArgs
 { }

+ 113 - 0
Terminal.Gui/App/Keyboard/IKeyboard.cs

@@ -0,0 +1,113 @@
+#nullable enable
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     Defines a contract for managing keyboard input and key bindings at the Application level.
+///     <para>
+///         This interface decouples keyboard handling state from the static <see cref="Application"/> class,
+///         enabling parallelizable unit tests and better testability.
+///     </para>
+/// </summary>
+public interface IKeyboard
+{
+    /// <summary>
+    /// Sets the application instance that this keyboard handler is associated with.
+    /// This provides access to application state without coupling to static Application class.
+    /// </summary>
+    IApplication? Application { get; set; }
+
+    /// <summary>
+    ///     Called when the user presses a key (by the <see cref="IDriver"/>). Raises the cancelable
+    ///     <see cref="KeyDown"/> event, then calls <see cref="View.NewKeyDownEvent"/> on all top level views, and finally
+    ///     if the key was not handled, invokes any Application-scoped <see cref="KeyBindings"/>.
+    /// </summary>
+    /// <remarks>Can be used to simulate key press events.</remarks>
+    /// <param name="key"></param>
+    /// <returns><see langword="true"/> if the key was handled.</returns>
+    bool RaiseKeyDownEvent (Key key);
+
+    /// <summary>
+    ///     Called when the user releases a key (by the <see cref="IDriver"/>). Raises the cancelable
+    ///     <see cref="KeyUp"/>
+    ///     event
+    ///     then calls <see cref="View.NewKeyUpEvent"/> on all top level views. Called after <see cref="RaiseKeyDownEvent"/>.
+    /// </summary>
+    /// <remarks>Can be used to simulate key release events.</remarks>
+    /// <param name="key"></param>
+    /// <returns><see langword="true"/> if the key was handled.</returns>
+    bool RaiseKeyUpEvent (Key key);
+
+    /// <summary>
+    ///     Invokes any commands bound at the Application-level to <paramref name="key"/>.
+    /// </summary>
+    /// <param name="key"></param>
+    /// <returns>
+    ///     <see langword="null"/> if no command was found; input processing should continue.
+    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input processing should continue.
+    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
+    /// </returns>
+    bool? InvokeCommandsBoundToKey (Key key);
+
+    /// <summary>
+    ///     Invokes an Application-bound command.
+    /// </summary>
+    /// <param name="command">The Command to invoke</param>
+    /// <param name="key">The Application-bound Key that was pressed.</param>
+    /// <param name="binding">Describes the binding.</param>
+    /// <returns>
+    ///     <see langword="null"/> if no command was found; input processing should continue.
+    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input processing should continue.
+    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
+    /// </returns>
+    /// <exception cref="NotSupportedException"></exception>
+    bool? InvokeCommand (Command command, Key key, KeyBinding binding);
+
+    /// <summary>
+    ///     Raised when the user presses a key.
+    ///     <para>
+    ///         Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
+    ///         additional processing.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Unix) do not support firing the
+    ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+    ///     <para>Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.</para>
+    /// </remarks>
+    event EventHandler<Key>? KeyDown;
+
+    /// <summary>
+    ///     Raised when the user releases a key.
+    ///     <para>
+    ///         Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
+    ///         additional processing.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Unix) do not support firing the
+    ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+    ///     <para>Fired after <see cref="KeyDown"/>.</para>
+    /// </remarks>
+    event EventHandler<Key>? KeyUp;
+
+    /// <summary>Gets the Application-scoped key bindings.</summary>
+    KeyBindings KeyBindings { get; }
+
+    /// <summary>Gets or sets the key to quit the application.</summary>
+    Key QuitKey { get; set; }
+
+    /// <summary>Gets or sets the key to activate arranging views using the keyboard.</summary>
+    Key ArrangeKey { get; set; }
+
+    /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
+    Key NextTabGroupKey { get; set; }
+
+    /// <summary>Alternative key to navigate forwards through views. Tab is the primary key.</summary>
+    Key NextTabKey { get; set; }
+
+    /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
+    Key PrevTabGroupKey { get; set; }
+
+    /// <summary>Alternative key to navigate backwards through views. Shift+Tab is the primary key.</summary>
+    Key PrevTabKey { get; set; }
+}

+ 384 - 0
Terminal.Gui/App/Keyboard/KeyboardImpl.cs

@@ -0,0 +1,384 @@
+#nullable enable
+using System.Diagnostics;
+
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     INTERNAL: Implements <see cref="IKeyboard"/> to manage keyboard input and key bindings at the Application level.
+///     <para>
+///         This implementation decouples keyboard handling state from the static <see cref="Application"/> class,
+///         enabling parallelizable unit tests and better testability.
+///     </para>
+///     <para>
+///         See <see cref="IKeyboard"/> for usage details.
+///     </para>
+/// </summary>
+internal class KeyboardImpl : IKeyboard
+{
+    private Key _quitKey = Key.Esc; // Resources/config.json overrides
+    private Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
+    private Key _nextTabGroupKey = Key.F6; // Resources/config.json overrides
+    private Key _nextTabKey = Key.Tab; // Resources/config.json overrides
+    private Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrides
+    private Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrides
+
+    /// <summary>
+    ///     Commands for Application.
+    /// </summary>
+    private readonly Dictionary<Command, View.CommandImplementation> _commandImplementations = new ();
+
+    /// <inheritdoc/>
+    public IApplication? Application { get; set; }
+
+    /// <inheritdoc/>
+    public KeyBindings KeyBindings { get; internal set; } = new (null);
+
+    /// <inheritdoc/>
+    public Key QuitKey
+    {
+        get => _quitKey;
+        set
+        {
+            KeyBindings.Replace (_quitKey, value);
+            _quitKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public Key ArrangeKey
+    {
+        get => _arrangeKey;
+        set
+        {
+            KeyBindings.Replace (_arrangeKey, value);
+            _arrangeKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public Key NextTabGroupKey
+    {
+        get => _nextTabGroupKey;
+        set
+        {
+            KeyBindings.Replace (_nextTabGroupKey, value);
+            _nextTabGroupKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public Key NextTabKey
+    {
+        get => _nextTabKey;
+        set
+        {
+            KeyBindings.Replace (_nextTabKey, value);
+            _nextTabKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public Key PrevTabGroupKey
+    {
+        get => _prevTabGroupKey;
+        set
+        {
+            KeyBindings.Replace (_prevTabGroupKey, value);
+            _prevTabGroupKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public Key PrevTabKey
+    {
+        get => _prevTabKey;
+        set
+        {
+            KeyBindings.Replace (_prevTabKey, value);
+            _prevTabKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public event EventHandler<Key>? KeyDown;
+
+    /// <inheritdoc/>
+    public event EventHandler<Key>? KeyUp;
+
+    /// <summary>
+    ///     Initializes keyboard bindings.
+    /// </summary>
+    public KeyboardImpl ()
+    {
+        AddKeyBindings ();
+    }
+
+    /// <inheritdoc/>
+    public bool RaiseKeyDownEvent (Key key)
+    {
+        //ebug.Assert (App.Application.MainThreadId == Thread.CurrentThread.ManagedThreadId);
+        //Logging.Debug ($"{key}");
+
+        // TODO: Add a way to ignore certain keys, esp for debugging.
+        //#if DEBUG
+        //        if (key == Key.Empty.WithAlt || key == Key.Empty.WithCtrl)
+        //        {
+        //            Logging.Debug ($"Ignoring {key}");
+        //            return false;
+        //        }
+        //#endif
+
+        // TODO: This should match standard event patterns
+        KeyDown?.Invoke (null, key);
+
+        if (key.Handled)
+        {
+            return true;
+        }
+
+        if (Application?.Popover?.DispatchKeyDown (key) is true)
+        {
+            return true;
+        }
+
+        if (Application?.Top is null)
+        {
+            if (Application?.TopLevels is { })
+            {
+                foreach (Toplevel topLevel in Application.TopLevels.ToList ())
+                {
+                    if (topLevel.NewKeyDownEvent (key))
+                    {
+                        return true;
+                    }
+
+                    if (topLevel.Modal)
+                    {
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            if (Application.Top.NewKeyDownEvent (key))
+            {
+                return true;
+            }
+        }
+
+        bool? commandHandled = InvokeCommandsBoundToKey (key);
+        if(commandHandled is true)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <inheritdoc/>
+    public bool RaiseKeyUpEvent (Key key)
+    {
+        if (Application?.Initialized != true)
+        {
+            return true;
+        }
+
+        KeyUp?.Invoke (null, key);
+
+        if (key.Handled)
+        {
+            return true;
+        }
+
+
+        // TODO: Add Popover support
+
+        if (Application?.TopLevels is { })
+        {
+            foreach (Toplevel topLevel in Application.TopLevels.ToList ())
+            {
+                if (topLevel.NewKeyUpEvent (key))
+                {
+                    return true;
+                }
+
+                if (topLevel.Modal)
+                {
+                    break;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /// <inheritdoc/>
+    public bool? InvokeCommandsBoundToKey (Key key)
+    {
+        bool? handled = null;
+        // Invoke any Application-scoped KeyBindings.
+        // The first view that handles the key will stop the loop.
+        // foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
+        if (KeyBindings.TryGet (key, out KeyBinding binding))
+        {
+            if (binding.Target is { })
+            {
+                if (!binding.Target.Enabled)
+                {
+                    return null;
+                }
+
+                handled = binding.Target?.InvokeCommands (binding.Commands, binding);
+            }
+            else
+            {
+                bool? toReturn = null;
+
+                foreach (Command command in binding.Commands)
+                {
+                    toReturn = InvokeCommand (command, key, binding);
+                }
+
+                handled = toReturn ?? true;
+            }
+        }
+
+        return handled;
+    }
+
+    /// <inheritdoc/>
+    public bool? InvokeCommand (Command command, Key key, KeyBinding binding)
+    {
+        if (!_commandImplementations.ContainsKey (command))
+        {
+            throw new NotSupportedException (
+                                             @$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by Application."
+                                            );
+        }
+
+        if (_commandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
+        {
+            CommandContext<KeyBinding> context = new (command, null, binding); // Create the context here
+
+            return implementation (context);
+        }
+
+        return null;
+    }
+
+    /// <summary>
+    ///     <para>
+    ///         Sets the function that will be invoked for a <see cref="Command"/>.
+    ///     </para>
+    ///     <para>
+    ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
+    ///         replace the old one.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This version of AddCommand is for commands that do not require a <see cref="ICommandContext"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="command">The command.</param>
+    /// <param name="f">The function.</param>
+    private void AddCommand (Command command, Func<bool?> f) { _commandImplementations [command] = ctx => f (); }
+
+    internal void AddKeyBindings ()
+    {
+        _commandImplementations.Clear ();
+
+        // Things Application knows how to do
+        AddCommand (
+                    Command.Quit,
+                    () =>
+                    {
+                        Application?.RequestStop ();
+
+                        return true;
+                    }
+                   );
+        AddCommand (
+                    Command.Suspend,
+                    () =>
+                    {
+                        Application?.Driver?.Suspend ();
+
+                        return true;
+                    }
+                   );
+        AddCommand (
+                    Command.NextTabStop,
+                    () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));
+
+        AddCommand (
+                    Command.PreviousTabStop,
+                    () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop));
+
+        AddCommand (
+                    Command.NextTabGroup,
+                    () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup));
+
+        AddCommand (
+                    Command.PreviousTabGroup,
+                    () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup));
+
+        AddCommand (
+                    Command.Refresh,
+                    () =>
+                    {
+                        Application?.LayoutAndDraw (true);
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Arrange,
+                    () =>
+                    {
+                        View? viewToArrange = Application?.Navigation?.GetFocused ();
+
+                        // Go up the superview hierarchy and find the first that is not ViewArrangement.Fixed
+                        while (viewToArrange is { SuperView: { }, Arrangement: ViewArrangement.Fixed })
+                        {
+                            viewToArrange = viewToArrange.SuperView;
+                        }
+
+                        if (viewToArrange is { })
+                        {
+                            return viewToArrange.Border?.EnterArrangeMode (ViewArrangement.Fixed);
+                        }
+
+                        return false;
+                    });
+
+        //SetKeysToHardCodedDefaults ();
+
+        // Need to clear after setting the above to ensure actually clear
+        // because set_QuitKey etc.. may call Add
+        KeyBindings.Clear ();
+
+        KeyBindings.Add (QuitKey, Command.Quit);
+        KeyBindings.Add (NextTabKey, Command.NextTabStop);
+        KeyBindings.Add (PrevTabKey, Command.PreviousTabStop);
+        KeyBindings.Add (NextTabGroupKey, Command.NextTabGroup);
+        KeyBindings.Add (PrevTabGroupKey, Command.PreviousTabGroup);
+        KeyBindings.Add (ArrangeKey, Command.Arrange);
+
+        KeyBindings.Add (Key.CursorRight, Command.NextTabStop);
+        KeyBindings.Add (Key.CursorDown, Command.NextTabStop);
+        KeyBindings.Add (Key.CursorLeft, Command.PreviousTabStop);
+        KeyBindings.Add (Key.CursorUp, Command.PreviousTabStop);
+
+        // TODO: Refresh Key should be configurable
+        KeyBindings.Add (Key.F5, Command.Refresh);
+
+        // TODO: Suspend Key should be configurable
+        if (Environment.OSVersion.Platform == PlatformID.Unix)
+        {
+            KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
+        }
+    }
+}

Some files were not shown because too many files changed in this diff