Browse Source

Merge branch 'develop' into main

Tig 2 years ago
parent
commit
094a9b57de
100 changed files with 7418 additions and 3380 deletions
  1. 24 20
      .devcontainer/devcontainer.json
  2. 2 2
      .github/workflows/publish.yml
  3. 19 5
      CONTRIBUTING.md
  4. 1 0
      README.md
  5. 3 3
      ReactiveExample/ReactiveExample.csproj
  6. 149 455
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  7. 3 27
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
  8. 14 21
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs
  9. 0 5
      Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs
  10. 2 2
      Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs
  11. 284 289
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
  12. 59 43
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  13. 258 828
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  14. 117 74
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  15. 49 25
      Terminal.Gui/Core/Application.cs
  16. 45 22
      Terminal.Gui/Core/Autocomplete/Autocomplete.cs
  17. 2 1
      Terminal.Gui/Core/Autocomplete/IAutocomplete.cs
  18. 52 10
      Terminal.Gui/Core/Border.cs
  19. 320 255
      Terminal.Gui/Core/ConsoleDriver.cs
  20. 19 0
      Terminal.Gui/Core/ConsoleKeyMapping.cs
  21. 109 0
      Terminal.Gui/Core/EscSeqUtils/EscSeqReq.cs
  22. 907 0
      Terminal.Gui/Core/EscSeqUtils/EscSeqUtils.cs
  23. 1 1
      Terminal.Gui/Core/Event.cs
  24. 605 0
      Terminal.Gui/Core/Graphs/LineCanvas.cs
  25. 9 7
      Terminal.Gui/Core/TextFormatter.cs
  26. 26 5
      Terminal.Gui/Core/Toplevel.cs
  27. 99 31
      Terminal.Gui/Core/View.cs
  28. 7 12
      Terminal.Gui/Core/Window.cs
  29. 5 3
      Terminal.Gui/Terminal.Gui.csproj
  30. 9 11
      Terminal.Gui/Views/FrameView.cs
  31. 8 5
      Terminal.Gui/Views/ListView.cs
  32. 64 1
      Terminal.Gui/Views/TabView.cs
  33. 6 5
      Terminal.Gui/Views/TextField.cs
  34. 23 15
      Terminal.Gui/Views/TextView.cs
  35. 6 10
      UICatalog/Properties/launchSettings.json
  36. 313 0
      UICatalog/Scenarios/ASCIICustomButton.cs
  37. 234 0
      UICatalog/Scenarios/Animation.cs
  38. 3 2
      UICatalog/Scenarios/BasicColors.cs
  39. 3 2
      UICatalog/Scenarios/BordersComparisons.cs
  40. 212 0
      UICatalog/Scenarios/LineDrawing.cs
  41. 40 2
      UICatalog/Scenarios/Notepad.cs
  42. 353 0
      UICatalog/Scenarios/Snake.cs
  43. BIN
      UICatalog/Scenarios/Spinning_globe_dark_small.gif
  44. 3 0
      UICatalog/Scenarios/spinning-globe-attribution.txt
  45. 20 17
      UICatalog/UICatalog.cs
  46. 5 1
      UICatalog/UICatalog.csproj
  47. 7 7
      UnitTests/Application/ApplicationTests.cs
  48. 23 45
      UnitTests/Application/MainLoopTests.cs
  49. 4 5
      UnitTests/Application/RunStateTests.cs
  50. 1 1
      UnitTests/Application/StackExtensionsTests.cs
  51. 1 1
      UnitTests/Application/SynchronizatonContextTests.cs
  52. 0 842
      UnitTests/ConsoleDriverTests.cs
  53. 79 17
      UnitTests/Core/BorderTests.cs
  54. 78 0
      UnitTests/Core/EscSeqReqTests.cs
  55. 870 0
      UnitTests/Core/EscSeqUtilsTests.cs
  56. 346 0
      UnitTests/Core/LineCanvasTests.cs
  57. 1 6
      UnitTests/Core/ResponderTests.cs
  58. 70 6
      UnitTests/Drivers/AttributeTests.cs
  59. 11 23
      UnitTests/Drivers/ClipboardTests.cs
  60. 77 0
      UnitTests/Drivers/ColorTests.cs
  61. 616 0
      UnitTests/Drivers/ConsoleDriverTests.cs
  62. 2 1
      UnitTests/Drivers/KeyTests.cs
  63. 2 2
      UnitTests/Menus/ContextMenuTests.cs
  64. 2 2
      UnitTests/Menus/MenuTests.cs
  65. 6 6
      UnitTests/TestHelpers.cs
  66. 1 1
      UnitTests/Text/CollectionNavigatorTests.cs
  67. 63 34
      UnitTests/Text/TextFormatterTests.cs
  68. 34 34
      UnitTests/TopLevels/DialogTests.cs
  69. 16 37
      UnitTests/TopLevels/MdiTests.cs
  70. 3 2
      UnitTests/TopLevels/MessageBoxTests.cs
  71. 77 13
      UnitTests/TopLevels/ToplevelTests.cs
  72. 8 7
      UnitTests/TopLevels/WindowTests.cs
  73. 21 21
      UnitTests/TopLevels/WizardTests.cs
  74. 4 9
      UnitTests/Types/DimTests.cs
  75. 1 1
      UnitTests/Types/PointTests.cs
  76. 1 2
      UnitTests/Types/PosTests.cs
  77. 1 1
      UnitTests/Types/RectTests.cs
  78. 1 1
      UnitTests/Types/SizeTests.cs
  79. 6 1
      UnitTests/UICatalog/ScenarioTests.cs
  80. 7 7
      UnitTests/UnitTests.csproj
  81. 1 1
      UnitTests/Views/AllViewsTests.cs
  82. 87 1
      UnitTests/Views/AutocompleteTests.cs
  83. 1 1
      UnitTests/Views/ButtonTests.cs
  84. 1 1
      UnitTests/Views/CheckboxTests.cs
  85. 1 1
      UnitTests/Views/ColorPickerTests.cs
  86. 1 1
      UnitTests/Views/ComboBoxTests.cs
  87. 1 1
      UnitTests/Views/DateFieldTests.cs
  88. 1 1
      UnitTests/Views/FrameViewTests.cs
  89. 1 1
      UnitTests/Views/GraphViewTests.cs
  90. 1 1
      UnitTests/Views/HexViewTests.cs
  91. 1 1
      UnitTests/Views/LineViewTests.cs
  92. 64 2
      UnitTests/Views/ListViewTests.cs
  93. 1 1
      UnitTests/Views/PanelViewTests.cs
  94. 1 1
      UnitTests/Views/ProgressBarTests.cs
  95. 1 1
      UnitTests/Views/RadioGroupTests.cs
  96. 1 1
      UnitTests/Views/ScrollBarViewTests.cs
  97. 224 6
      UnitTests/Views/ScrollViewTests.cs
  98. 1 1
      UnitTests/Views/StatusBarTests.cs
  99. 95 3
      UnitTests/Views/TabViewTests.cs
  100. 1 1
      UnitTests/Views/TableViewTests.cs

+ 24 - 20
.devcontainer/devcontainer.json

@@ -1,25 +1,29 @@
 {
 {
 	"name": "Terminal.Gui Codespace",
 	"name": "Terminal.Gui Codespace",
-	"image": "mcr.microsoft.com/vscode/devcontainers/dotnet:6.0",
-	"settings": {
-		"terminal.integrated.defaultProfile.linux": "pwsh"
-	},
-	"extensions": [
-		"eamodio.gitlens",
-		"ms-dotnettools.csharp",
-		"VisualStudioExptTeam.vscodeintellicode",
-		"ms-vscode.powershell",
-		"cschleiden.vscode-github-actions",
-		"redhat.vscode-yaml",
-		"bierner.markdown-preview-github-styles",
-		"ban.spellright",
-		"jmrog.vscode-nuget-package-manager",
-		"coenraads.bracket-pair-colorizer",
-		"vscode-icons-team.vscode-icons",
-		"editorconfig.editorconfig",
-		"formulahendry.dotnet-test-explorer"
-	],
-	"postCreateCommand": "dotnet restore && dotnet clean && dotnet build --configuration Release --no-restore && dotnet test --configuration Debug --no-restore --verbosity normal --collect:'XPlat Code Coverage' --settings UnitTests/coverlet.runsettings"
+	"image": "mcr.microsoft.com/vscode/devcontainers/dotnet:7.0",
+	"customizations": {
+		"vscode": {
+			"settings": {
+				"terminal.integrated.defaultProfile.linux": "pwsh"
+			},
+			"extensions": [
+				"eamodio.gitlens",
+				"ms-dotnettools.csharp",
+				"VisualStudioExptTeam.vscodeintellicode",
+				"ms-vscode.powershell",
+				"cschleiden.vscode-github-actions",
+				"redhat.vscode-yaml",
+				"bierner.markdown-preview-github-styles",
+				"ban.spellright",
+				"jmrog.vscode-nuget-package-manager",
+				"coenraads.bracket-pair-colorizer",
+				"vscode-icons-team.vscode-icons",
+				"editorconfig.editorconfig",
+				"formulahendry.dotnet-test-explorer"
+			],
+			"postCreateCommand": "dotnet restore && dotnet clean && dotnet build --configuration Release --no-restore && dotnet test --configuration Debug --no-restore --verbosity normal --collect:'XPlat Code Coverage' --settings UnitTests/coverlet.runsettings"		
+		}
+	}
 }
 }
 
 
 // Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
 // Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)

+ 2 - 2
.github/workflows/publish.yml

@@ -16,12 +16,12 @@ jobs:
         fetch-depth: 0 #fetch-depth is needed for GitVersion
         fetch-depth: 0 #fetch-depth is needed for GitVersion
 
 
     - name: Install and calculate the new version with GitVersion 
     - name: Install and calculate the new version with GitVersion 
-      uses: gittools/actions/gitversion/setup@v0.9.15
+      uses: gittools/actions/gitversion/setup@v0.10.2
       with:
       with:
         versionSpec: 5.x
         versionSpec: 5.x
 
 
     - name: Determine Version
     - name: Determine Version
-      uses: gittools/actions/gitversion/execute@v0.9.15
+      uses: gittools/actions/gitversion/execute@v0.10.2
       id: gitversion # step id used as reference for output values
       id: gitversion # step id used as reference for output values
 
 
     - name: Display GitVersion outputs
     - name: Display GitVersion outputs

+ 19 - 5
CONTRIBUTING.md

@@ -8,10 +8,13 @@ We welcome contributions from the community. See [Issues](https://github.com/gui
 
 
 ## Forking and Submitting Changes
 ## Forking and Submitting Changes
 
 
+*IMPORTANT*: v1.x of Terminal.Gui is now in maintenance mode. All new development is happening on the `v2_develop` branch. See the V2 discussion [here](https://github.com/gui-cs/Terminal.Gui/discussions/1940).
+
 Terminal.Gui uses the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. 
 Terminal.Gui uses the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. 
 
 
 * The `main` branch is always stable, and always matches the most recently released Nuget package.
 * The `main` branch is always stable, and always matches the most recently released Nuget package.
-* The `develop` branch is where new development and bug-fixes happen. It is the default branch.
+* The `develop` branch is where bug-fixes to v1.x happens. It is the default branch.
+* The `v2_develop` branch is where development on v2.x happens. 
 
 
 ### Forking Terminal.Gui
 ### Forking Terminal.Gui
 
 
@@ -33,16 +36,23 @@ You now have your own fork and a local repo that references it as `origin`. Your
 
 
 ### Starting to Make a Change
 ### Starting to Make a Change
 
 
-Ensure your local `develop` branch is up-to-date with `upstream` (`github.com/gui-cs/Terminal.Gui`):
+Figure out if your change will target v1 or v2. If you're not sure, ask in the [v2 disucssions here](https://github.com/gui-cs/Terminal.Gui/discussions/1940).
+
+For v1.x, use the `develop` branch. For v2.x, use the `v2_develop` branch.
+
+Prefix any local branches you create with `v1_` or `v2_` to indicate which version you're working on.
+
+Ensure your local branch is up-to-date with `upstream` (`github.com/gui-cs/Terminal.Gui`):
+
 ```powershell
 ```powershell
 cd ./Terminal.Gui
 cd ./Terminal.Gui
-git checkout develop
-git pull upstream develop
+git checkout <develop/v2_develop>
+git pull upstream <develop/v2_develop>
 ```
 ```
 
 
 Create a new local branch:
 Create a new local branch:
 ```powershell
 ```powershell
-git checkout -b my_new_branch
+git checkout -b <v1/v2>_my_new_branch
 ```
 ```
 
 
 #### Making Changes
 #### Making Changes
@@ -90,6 +100,10 @@ remote:
 
 
 Follow the template instructions found on Github.
 Follow the template instructions found on Github.
 
 
+If you are targetting v2, set the project to the [v2 Project](https://github.com/orgs/gui-cs/projects/1).
+
+4. If your change is a bug fix, consider whether you should submit a separate PR to the v1 (`develop`) or v2 (`v2_develop`) branch as well.
+
 ## Terminal.Gui Coding Style
 ## Terminal.Gui Coding Style
 
 
 **Terminal.Gui** follows the [Mono Coding Guidelines](https://www.mono-project.com/community/contributing/coding-guidelines/). [`/.editorconfig`](https://github.com/gui-cs/Terminal.Gui/blob/b0a43ba338adf5ec069066e5a7dff8fea39b41db/.editorconfig) enforces this style in Visual Studio. Use `Ctrl-K-D` in Visual Studio to have it reformat code.
 **Terminal.Gui** follows the [Mono Coding Guidelines](https://www.mono-project.com/community/contributing/coding-guidelines/). [`/.editorconfig`](https://github.com/gui-cs/Terminal.Gui/blob/b0a43ba338adf5ec069066e5a7dff8fea39b41db/.editorconfig) enforces this style in Visual Studio. Use `Ctrl-K-D` in Visual Studio to have it reformat code.

+ 1 - 0
README.md

@@ -6,6 +6,7 @@
 [![License](https://img.shields.io/github/license/gui-cs/gui.cs.svg)](LICENSE)
 [![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)
 ![Bugs](https://img.shields.io/github/issues/gui-cs/gui.cs/bug)
 
 
+*IMPORTANT*: v1.x of Terminal.Gui is now in maintenance mode; we will accept PRs for v1.x (the `develop` branch) only for bugs that impact existing functionality. All new development happens on the `v2_develop` branch. See the V2 discussion [here](https://github.com/gui-cs/Terminal.Gui/discussions/1940).
 # Terminal.Gui - Cross Platform Terminal UI toolkit for .NET
 # Terminal.Gui - Cross Platform Terminal UI toolkit for .NET
 
 
 A toolkit for building rich console apps for .NET, .NET Core, and Mono that works on Windows, the Mac, and Linux/Unix.
 A toolkit for building rich console apps for .NET, .NET Core, and Mono that works on Windows, the Mac, and Linux/Unix.

+ 3 - 3
ReactiveExample/ReactiveExample.csproj

@@ -3,7 +3,7 @@
     <OutputType>Exe</OutputType>
     <OutputType>Exe</OutputType>
     <TargetFramework>net6.0</TargetFramework>
     <TargetFramework>net6.0</TargetFramework>
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
-    <!-- In the source tree the version will always be 1.0 for all projects. -->
+    <!-- In the source tree the version will always be 2.0 for all projects. -->
     <!-- Do not modify these. -->
     <!-- Do not modify these. -->
     <AssemblyVersion>1.0</AssemblyVersion>
     <AssemblyVersion>1.0</AssemblyVersion>
     <FileVersion>1.0</FileVersion>
     <FileVersion>1.0</FileVersion>
@@ -11,8 +11,8 @@
     <InformationalVersion>1.0</InformationalVersion>
     <InformationalVersion>1.0</InformationalVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="ReactiveUI.Fody" Version="18.4.1" />
-    <PackageReference Include="ReactiveUI" Version="18.4.1" />
+    <PackageReference Include="ReactiveUI.Fody" Version="18.4.26" />
+    <PackageReference Include="ReactiveUI" Version="18.4.26" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.2.3" PrivateAssets="all" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.2.3" PrivateAssets="all" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>

+ 149 - 455
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -1,9 +1,6 @@
 //
 //
 // Driver.cs: Curses-based Driver
 // Driver.cs: Curses-based Driver
 //
 //
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
@@ -23,7 +20,12 @@ namespace Terminal.Gui {
 		public override int Rows => Curses.Lines;
 		public override int Rows => Curses.Lines;
 		public override int Left => 0;
 		public override int Left => 0;
 		public override int Top => 0;
 		public override int Top => 0;
-		public override bool HeightAsBuffer { get; set; }
+		public override bool EnableConsoleScrolling { get; set; }
+		[Obsolete ("This API is deprecated; use EnableConsoleScrolling instead.", false)]
+		public override bool HeightAsBuffer {
+			get => EnableConsoleScrolling;
+			set => EnableConsoleScrolling = value;
+		}
 		public override IClipboard Clipboard { get => clipboard; }
 		public override IClipboard Clipboard { get => clipboard; }
 
 
 		CursorVisibility? initialCursorVisibility = null;
 		CursorVisibility? initialCursorVisibility = null;
@@ -62,50 +64,72 @@ namespace Terminal.Gui {
 					Curses.move (crow, ccol);
 					Curses.move (crow, ccol);
 					needMove = false;
 					needMove = false;
 				}
 				}
-				if (runeWidth < 2 && ccol > 0
-					&& Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) {
-
-					var curAtttib = currentAttribute;
-					Curses.attrset (contents [crow, ccol - 1, 1]);
-					Curses.mvaddch (crow, ccol - 1, (int)(uint)' ');
-					contents [crow, ccol - 1, 0] = (int)(uint)' ';
-					Curses.move (crow, ccol);
-					Curses.attrset (curAtttib);
-
-				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
-					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
-
-					var curAtttib = currentAttribute;
-					Curses.attrset (contents [crow, ccol + 1, 1]);
-					Curses.mvaddch (crow, ccol + 1, (int)(uint)' ');
-					contents [crow, ccol + 1, 0] = (int)(uint)' ';
-					Curses.move (crow, ccol);
-					Curses.attrset (curAtttib);
+				if (runeWidth == 0 && ccol > 0) {
+					var r = contents [crow, ccol - 1, 0];
+					var s = new string (new char [] { (char)r, (char)rune });
+					string sn;
+					if (!s.IsNormalized ()) {
+						sn = s.Normalize ();
+					} else {
+						sn = s;
+					}
+					var c = sn [0];
+					Curses.mvaddch (crow, ccol - 1, (int)(uint)c);
+					contents [crow, ccol - 1, 0] = c;
+					contents [crow, ccol - 1, 1] = CurrentAttribute;
+					contents [crow, ccol - 1, 2] = 1;
 
 
-				}
-				if (runeWidth > 1 && ccol == Clip.Right - 1) {
-					Curses.addch ((int)(uint)' ');
-					contents [crow, ccol, 0] = (int)(uint)' ';
 				} else {
 				} else {
-					Curses.addch ((int)(uint)rune);
-					contents [crow, ccol, 0] = (int)(uint)rune;
+					if (runeWidth < 2 && ccol > 0
+						&& Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) {
+
+						var curAtttib = CurrentAttribute;
+						Curses.attrset (contents [crow, ccol - 1, 1]);
+						Curses.mvaddch (crow, ccol - 1, (int)(uint)' ');
+						contents [crow, ccol - 1, 0] = (int)(uint)' ';
+						Curses.move (crow, ccol);
+						Curses.attrset (curAtttib);
+
+					} else if (runeWidth < 2 && ccol <= Clip.Right - 1
+						&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+
+						var curAtttib = CurrentAttribute;
+						Curses.attrset (contents [crow, ccol + 1, 1]);
+						Curses.mvaddch (crow, ccol + 1, (int)(uint)' ');
+						contents [crow, ccol + 1, 0] = (int)(uint)' ';
+						Curses.move (crow, ccol);
+						Curses.attrset (curAtttib);
+
+					}
+					if (runeWidth > 1 && ccol == Clip.Right - 1) {
+						Curses.addch ((int)(uint)' ');
+						contents [crow, ccol, 0] = (int)(uint)' ';
+					} else {
+						Curses.addch ((int)(uint)rune);
+						contents [crow, ccol, 0] = (int)(uint)rune;
+					}
+					contents [crow, ccol, 1] = CurrentAttribute;
+					contents [crow, ccol, 2] = 1;
 				}
 				}
-				contents [crow, ccol, 1] = currentAttribute;
-				contents [crow, ccol, 2] = 1;
-			} else
+			} else {
 				needMove = true;
 				needMove = true;
+			}
+
+			if (runeWidth < 0 || runeWidth > 0) {
+				ccol++;
+			}
 
 
-			ccol++;
 			if (runeWidth > 1) {
 			if (runeWidth > 1) {
 				if (validClip && ccol < Clip.Right) {
 				if (validClip && ccol < Clip.Right) {
-					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 1] = CurrentAttribute;
 					contents [crow, ccol, 2] = 0;
 					contents [crow, ccol, 2] = 0;
 				}
 				}
 				ccol++;
 				ccol++;
 			}
 			}
 
 
-			if (sync)
+			if (sync) {
 				UpdateScreen ();
 				UpdateScreen ();
+			}
 		}
 		}
 
 
 		public override void AddStr (ustring str)
 		public override void AddStr (ustring str)
@@ -136,36 +160,18 @@ namespace Terminal.Gui {
 
 
 		public override void End ()
 		public override void End ()
 		{
 		{
-			if (reportableMouseEvents.HasFlag (Curses.Event.ReportMousePosition)) {
-				StopReportingMouseMoves ();
-			}
-
+			StopReportingMouseMoves ();
 			SetCursorVisibility (CursorVisibility.Default);
 			SetCursorVisibility (CursorVisibility.Default);
 
 
 			Curses.endwin ();
 			Curses.endwin ();
-
-			// I'm commenting this because was used in a trying to fix the Linux hanging and forgot to exclude it.
-			// Clear and reset entire screen.
-			//Console.Out.Write ("\x1b[2J");
-			//Console.Out.Flush ();
-
-			// Set top and bottom lines of a window.
-			//Console.Out.Write ("\x1b[1;25r");
-			//Console.Out.Flush ();
-
-			//Set cursor key to cursor.
-			//Console.Out.Write ("\x1b[?1l");
-			//Console.Out.Flush ();
 		}
 		}
 
 
 		public override void UpdateScreen () => window.redrawwin ();
 		public override void UpdateScreen () => window.redrawwin ();
 
 
-		Attribute currentAttribute;
-
 		public override void SetAttribute (Attribute c)
 		public override void SetAttribute (Attribute c)
 		{
 		{
-			currentAttribute = c;
-			Curses.attrset (currentAttribute);
+			base.SetAttribute (c);
+			Curses.attrset (CurrentAttribute);
 		}
 		}
 
 
 		public Curses.Window window;
 		public Curses.Window window;
@@ -201,6 +207,7 @@ namespace Terminal.Gui {
 
 
 		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
 		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
 		{
 		{
+			// BUGBUG: This code is never called ?? See Issue #2300
 			int f = (short)foreground;
 			int f = (short)foreground;
 			int b = (short)background;
 			int b = (short)background;
 			var v = colorPairs [f, b];
 			var v = colorPairs [f, b];
@@ -218,6 +225,7 @@ namespace Terminal.Gui {
 		Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
 		Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
 		public override void SetColors (short foreColorId, short backgroundColorId)
 		public override void SetColors (short foreColorId, short backgroundColorId)
 		{
 		{
+			// BUGBUG: This code is never called ?? See Issue #2300
 			int key = ((ushort)foreColorId << 16) | (ushort)backgroundColorId;
 			int key = ((ushort)foreColorId << 16) | (ushort)backgroundColorId;
 			if (!rawPairs.TryGetValue (key, out var v)) {
 			if (!rawPairs.TryGetValue (key, out var v)) {
 				v = MakeColor (foreColorId, backgroundColorId);
 				v = MakeColor (foreColorId, backgroundColorId);
@@ -302,305 +310,6 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-		Curses.Event? lastMouseButtonPressed;
-		bool isButtonPressed;
-		bool cancelButtonClicked;
-		bool isReportMousePosition;
-		Point point;
-		int buttonPressedCount;
-
-		MouseEvent ToDriverMouse (Curses.MouseEvent cev)
-		{
-			MouseFlags mouseFlag = MouseFlags.AllEvents;
-
-			if (lastMouseButtonPressed != null && cev.ButtonState != Curses.Event.ReportMousePosition) {
-				lastMouseButtonPressed = null;
-				isButtonPressed = false;
-			}
-
-			if (cev.ButtonState == Curses.Event.Button1Pressed
-				|| cev.ButtonState == Curses.Event.Button2Pressed
-				|| cev.ButtonState == Curses.Event.Button3Pressed) {
-
-				isButtonPressed = true;
-				buttonPressedCount++;
-			} else {
-				buttonPressedCount = 0;
-			}
-			//System.Diagnostics.Debug.WriteLine ($"buttonPressedCount: {buttonPressedCount}");
-
-			if (buttonPressedCount == 2
-				&& (cev.ButtonState == Curses.Event.Button1Pressed
-				|| cev.ButtonState == Curses.Event.Button2Pressed
-				|| cev.ButtonState == Curses.Event.Button3Pressed)) {
-
-				switch (cev.ButtonState) {
-				case Curses.Event.Button1Pressed:
-					mouseFlag = MouseFlags.Button1DoubleClicked;
-					break;
-
-				case Curses.Event.Button2Pressed:
-					mouseFlag = MouseFlags.Button2DoubleClicked;
-					break;
-
-				case Curses.Event.Button3Pressed:
-					mouseFlag = MouseFlags.Button3DoubleClicked;
-					break;
-				}
-				cancelButtonClicked = true;
-
-			} else if (buttonPressedCount == 3
-			       && (cev.ButtonState == Curses.Event.Button1Pressed
-			       || cev.ButtonState == Curses.Event.Button2Pressed
-			       || cev.ButtonState == Curses.Event.Button3Pressed)) {
-
-				switch (cev.ButtonState) {
-				case Curses.Event.Button1Pressed:
-					mouseFlag = MouseFlags.Button1TripleClicked;
-					break;
-
-				case Curses.Event.Button2Pressed:
-					mouseFlag = MouseFlags.Button2TripleClicked;
-					break;
-
-				case Curses.Event.Button3Pressed:
-					mouseFlag = MouseFlags.Button3TripleClicked;
-					break;
-				}
-				buttonPressedCount = 0;
-
-			} else if ((cev.ButtonState == Curses.Event.Button1Clicked || cev.ButtonState == Curses.Event.Button2Clicked ||
-			       cev.ButtonState == Curses.Event.Button3Clicked) &&
-			       lastMouseButtonPressed == null) {
-
-				isButtonPressed = false;
-				mouseFlag = ProcessButtonClickedEvent (cev);
-
-			} else if (((cev.ButtonState == Curses.Event.Button1Pressed || cev.ButtonState == Curses.Event.Button2Pressed ||
-				cev.ButtonState == Curses.Event.Button3Pressed) && lastMouseButtonPressed == null) ||
-				isButtonPressed && lastMouseButtonPressed != null && cev.ButtonState == Curses.Event.ReportMousePosition) {
-
-				mouseFlag = MapCursesButton (cev.ButtonState);
-				if (cev.ButtonState != Curses.Event.ReportMousePosition)
-					lastMouseButtonPressed = cev.ButtonState;
-				isButtonPressed = true;
-				isReportMousePosition = false;
-
-				if (cev.ButtonState == Curses.Event.ReportMousePosition) {
-					mouseFlag = MapCursesButton ((Curses.Event)lastMouseButtonPressed) | MouseFlags.ReportMousePosition;
-					cancelButtonClicked = true;
-				}
-				point = new Point () {
-					X = cev.X,
-					Y = cev.Y
-				};
-
-				if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) {
-					Application.MainLoop.AddIdle (() => {
-						Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag));
-						return false;
-					});
-				}
-
-
-			} else if ((cev.ButtonState == Curses.Event.Button1Released || cev.ButtonState == Curses.Event.Button2Released ||
-				cev.ButtonState == Curses.Event.Button3Released)) {
-
-				mouseFlag = ProcessButtonReleasedEvent (cev);
-				isButtonPressed = false;
-
-			} else if (cev.ButtonState == Curses.Event.ButtonWheeledUp) {
-
-				mouseFlag = MouseFlags.WheeledUp;
-
-			} else if (cev.ButtonState == Curses.Event.ButtonWheeledDown) {
-
-				mouseFlag = MouseFlags.WheeledDown;
-
-			} else if ((cev.ButtonState & (Curses.Event.ButtonWheeledUp & Curses.Event.ButtonShift)) != 0) {
-
-				mouseFlag = MouseFlags.WheeledLeft;
-
-			} else if ((cev.ButtonState & (Curses.Event.ButtonWheeledDown & Curses.Event.ButtonShift)) != 0) {
-
-				mouseFlag = MouseFlags.WheeledRight;
-
-			} else if (cev.ButtonState == Curses.Event.ReportMousePosition) {
-				if (cev.X != point.X || cev.Y != point.Y) {
-					mouseFlag = MouseFlags.ReportMousePosition;
-					isReportMousePosition = true;
-					point = new Point ();
-				} else {
-					mouseFlag = 0;
-				}
-
-			} else {
-				mouseFlag = 0;
-				var eFlags = cev.ButtonState;
-				foreach (Enum value in Enum.GetValues (eFlags.GetType ())) {
-					if (eFlags.HasFlag (value)) {
-						mouseFlag |= MapCursesButton ((Curses.Event)value);
-					}
-				}
-			}
-
-			mouseFlag = SetControlKeyStates (cev, mouseFlag);
-
-			return new MouseEvent () {
-				X = cev.X,
-				Y = cev.Y,
-				//Flags = MapCursesButton (cev.ButtonState)
-				Flags = mouseFlag
-			};
-		}
-
-		MouseFlags ProcessButtonClickedEvent (Curses.MouseEvent cev)
-		{
-			lastMouseButtonPressed = cev.ButtonState;
-			var mf = GetButtonState (cev, true);
-			mouseHandler (ProcessButtonState (cev, mf));
-			if (lastMouseButtonPressed != null && lastMouseButtonPressed == cev.ButtonState) {
-				mf = GetButtonState (cev, false);
-				mouseHandler (ProcessButtonState (cev, mf));
-				if (lastMouseButtonPressed != null && lastMouseButtonPressed == cev.ButtonState) {
-					mf = MapCursesButton (cev.ButtonState);
-				}
-			}
-			lastMouseButtonPressed = null;
-			isButtonPressed = false;
-			return mf;
-		}
-
-		MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev)
-		{
-			var mf = MapCursesButton (cev.ButtonState);
-			if (!cancelButtonClicked && lastMouseButtonPressed == null && !isReportMousePosition) {
-				mouseHandler (ProcessButtonState (cev, mf));
-				mf = GetButtonState (cev);
-			} else if (isReportMousePosition) {
-				mf = MouseFlags.ReportMousePosition;
-			}
-			cancelButtonClicked = false;
-			return mf;
-		}
-
-		async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
-		{
-			while (isButtonPressed) {
-				await Task.Delay (100);
-				var me = new MouseEvent () {
-					X = point.X,
-					Y = point.Y,
-					Flags = mouseFlag
-				};
-
-				var view = Application.WantContinuousButtonPressedView;
-				if (view == null)
-					break;
-				if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
-					Application.MainLoop.Invoke (() => mouseHandler (me));
-				}
-			}
-		}
-
-		MouseFlags GetButtonState (Curses.MouseEvent cev, bool pressed = false)
-		{
-			MouseFlags mf = default;
-			switch (cev.ButtonState) {
-			case Curses.Event.Button1Clicked:
-				if (pressed)
-					mf = MouseFlags.Button1Pressed;
-				else
-					mf = MouseFlags.Button1Released;
-				break;
-
-			case Curses.Event.Button2Clicked:
-				if (pressed)
-					mf = MouseFlags.Button2Pressed;
-				else
-					mf = MouseFlags.Button2Released;
-				break;
-
-			case Curses.Event.Button3Clicked:
-				if (pressed)
-					mf = MouseFlags.Button3Pressed;
-				else
-					mf = MouseFlags.Button3Released;
-				break;
-
-			case Curses.Event.Button1Released:
-				mf = MouseFlags.Button1Clicked;
-				break;
-
-			case Curses.Event.Button2Released:
-				mf = MouseFlags.Button2Clicked;
-				break;
-
-			case Curses.Event.Button3Released:
-				mf = MouseFlags.Button3Clicked;
-				break;
-
-			}
-			return mf;
-		}
-
-		MouseEvent ProcessButtonState (Curses.MouseEvent cev, MouseFlags mf)
-		{
-			return new MouseEvent () {
-				X = cev.X,
-				Y = cev.Y,
-				Flags = mf
-			};
-		}
-
-		MouseFlags MapCursesButton (Curses.Event cursesButton)
-		{
-			switch (cursesButton) {
-			case Curses.Event.Button1Pressed: return MouseFlags.Button1Pressed;
-			case Curses.Event.Button1Released: return MouseFlags.Button1Released;
-			case Curses.Event.Button1Clicked: return MouseFlags.Button1Clicked;
-			case Curses.Event.Button1DoubleClicked: return MouseFlags.Button1DoubleClicked;
-			case Curses.Event.Button1TripleClicked: return MouseFlags.Button1TripleClicked;
-			case Curses.Event.Button2Pressed: return MouseFlags.Button2Pressed;
-			case Curses.Event.Button2Released: return MouseFlags.Button2Released;
-			case Curses.Event.Button2Clicked: return MouseFlags.Button2Clicked;
-			case Curses.Event.Button2DoubleClicked: return MouseFlags.Button2DoubleClicked;
-			case Curses.Event.Button2TrippleClicked: return MouseFlags.Button2TripleClicked;
-			case Curses.Event.Button3Pressed: return MouseFlags.Button3Pressed;
-			case Curses.Event.Button3Released: return MouseFlags.Button3Released;
-			case Curses.Event.Button3Clicked: return MouseFlags.Button3Clicked;
-			case Curses.Event.Button3DoubleClicked: return MouseFlags.Button3DoubleClicked;
-			case Curses.Event.Button3TripleClicked: return MouseFlags.Button3TripleClicked;
-			case Curses.Event.ButtonWheeledUp: return MouseFlags.WheeledUp;
-			case Curses.Event.ButtonWheeledDown: return MouseFlags.WheeledDown;
-			case Curses.Event.Button4Pressed: return MouseFlags.Button4Pressed;
-			case Curses.Event.Button4Released: return MouseFlags.Button4Released;
-			case Curses.Event.Button4Clicked: return MouseFlags.Button4Clicked;
-			case Curses.Event.Button4DoubleClicked: return MouseFlags.Button4DoubleClicked;
-			case Curses.Event.Button4TripleClicked: return MouseFlags.Button4TripleClicked;
-			case Curses.Event.ButtonShift: return MouseFlags.ButtonShift;
-			case Curses.Event.ButtonCtrl: return MouseFlags.ButtonCtrl;
-			case Curses.Event.ButtonAlt: return MouseFlags.ButtonAlt;
-			case Curses.Event.ReportMousePosition: return MouseFlags.ReportMousePosition;
-			case Curses.Event.AllEvents: return MouseFlags.AllEvents;
-			default: return 0;
-			}
-		}
-
-		static MouseFlags SetControlKeyStates (Curses.MouseEvent cev, MouseFlags mouseFlag)
-		{
-			if ((cev.ButtonState & Curses.Event.ButtonCtrl) != 0 && (mouseFlag & MouseFlags.ButtonCtrl) == 0)
-				mouseFlag |= MouseFlags.ButtonCtrl;
-
-			if ((cev.ButtonState & Curses.Event.ButtonShift) != 0 && (mouseFlag & MouseFlags.ButtonShift) == 0)
-				mouseFlag |= MouseFlags.ButtonShift;
-
-			if ((cev.ButtonState & Curses.Event.ButtonAlt) != 0 && (mouseFlag & MouseFlags.ButtonAlt) == 0)
-				mouseFlag |= MouseFlags.ButtonAlt;
-			return mouseFlag;
-		}
-
-
 		KeyModifiers keyModifiers;
 		KeyModifiers keyModifiers;
 
 
 		KeyModifiers MapKeyModifiers (Key key)
 		KeyModifiers MapKeyModifiers (Key key)
@@ -634,9 +343,18 @@ namespace Terminal.Gui {
 					ProcessWinChange ();
 					ProcessWinChange ();
 				}
 				}
 				if (wch == Curses.KeyMouse) {
 				if (wch == Curses.KeyMouse) {
-					Curses.getmouse (out Curses.MouseEvent ev);
-					//System.Diagnostics.Debug.WriteLine ($"ButtonState: {ev.ButtonState}; ID: {ev.ID}; X: {ev.X}; Y: {ev.Y}; Z: {ev.Z}");
-					mouseHandler (ToDriverMouse (ev));
+					int wch2 = wch;
+
+					while (wch2 == Curses.KeyMouse) {
+						KeyEvent key = null;
+						ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
+							new ConsoleKeyInfo ((char)Key.Esc, 0, false, false, false),
+							new ConsoleKeyInfo ('[', 0, false, false, false),
+							new ConsoleKeyInfo ('<', 0, false, false, false)
+						};
+						code = 0;
+						GetEscSeq (ref code, ref k, ref wch2, ref key, ref cki);
+					}
 					return;
 					return;
 				}
 				}
 				k = MapCursesKey (wch);
 				k = MapCursesKey (wch);
@@ -672,7 +390,7 @@ namespace Terminal.Gui {
 					k = Key.AltMask | MapCursesKey (wch);
 					k = Key.AltMask | MapCursesKey (wch);
 				}
 				}
 				if (code == 0) {
 				if (code == 0) {
-					KeyEvent key;
+					KeyEvent key = null;
 
 
 					// The ESC-number handling, debatable.
 					// The ESC-number handling, debatable.
 					// Simulates the AltMask itself by pressing Alt + Space.
 					// Simulates the AltMask itself by pressing Alt + Space.
@@ -684,55 +402,13 @@ namespace Terminal.Gui {
 						k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + (wch2 + 64));
 						k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + (wch2 + 64));
 					} else if (wch2 >= (uint)Key.D0 && wch2 <= (uint)Key.D9) {
 					} else if (wch2 >= (uint)Key.D0 && wch2 <= (uint)Key.D9) {
 						k = (Key)((uint)Key.AltMask + (uint)Key.D0 + (wch2 - (uint)Key.D0));
 						k = (Key)((uint)Key.AltMask + (uint)Key.D0 + (wch2 - (uint)Key.D0));
-					} else if (wch2 == 27) {
-						k = (Key)wch2;
-					} else if (wch2 == Curses.KEY_CODE_SEQ) {
-						int [] c = null;
-						while (code == 0) {
-							code = Curses.get_wch (out wch2);
-							if (wch2 > 0) {
-								Array.Resize (ref c, c == null ? 1 : c.Length + 1);
-								c [c.Length - 1] = wch2;
-							}
-						}
-						if (c [0] == 49 && c [1] == 59 && c [2] == 55 && c [3] >= 80 && c [3] <= 83) { // Ctrl+Alt+(F1 - F4)
-							wch2 = c [3] + 185;
-							k = Key.CtrlMask | Key.AltMask | MapCursesKey (wch2);
-						} else if (c [0] == 49 && c [2] == 59 && c [3] == 55 && c [4] == 126 && c [1] >= 53 && c [1] <= 57) { // Ctrl+Alt+(F5 - F8)
-							wch2 = c [1] == 53 ? c [1] + 216 : c [1] + 215;
-							k = Key.CtrlMask | Key.AltMask | MapCursesKey (wch2);
-						} else if (c [0] == 50 && c [2] == 59 && c [3] == 55 && c [4] == 126 && c [1] >= 48 && c [1] <= 52) { // Ctrl+Alt+(F9 - F12)
-							wch2 = c [1] < 51 ? c [1] + 225 : c [1] + 224;
-							k = Key.CtrlMask | Key.AltMask | MapCursesKey (wch2);
-						} else if (c [0] == 49 && c [1] == 59 && c [2] == 56 && c [3] >= 80 && c [3] <= 83) { // Ctrl+Shift+Alt+(F1 - F4)
-							wch2 = c [3] + 185;
-							k = Key.CtrlMask | Key.ShiftMask | Key.AltMask | MapCursesKey (wch2);
-						} else if (c [0] == 49 && c [2] == 59 && c [3] == 56 && c [4] == 126 && c [1] >= 53 && c [1] <= 57) { // Ctrl+Shift+Alt+(F5 - F8)
-							wch2 = c [1] == 53 ? c [1] + 216 : c [1] + 215;
-							k = Key.CtrlMask | Key.ShiftMask | Key.AltMask | MapCursesKey (wch2);
-						} else if (c [0] == 50 && c [2] == 59 && c [3] == 56 && c [4] == 126 && c [1] >= 48 && c [1] <= 52) {  // Ctrl+Shift+Alt+(F9 - F12)
-							wch2 = c [1] < 51 ? c [1] + 225 : c [1] + 224;
-							k = Key.CtrlMask | Key.ShiftMask | Key.AltMask | MapCursesKey (wch2);
-						} else if (c [0] == 49 && c [1] == 59 && c [2] == 52 && c [3] == 83) {  // Shift+Alt+(F4)
-							wch2 = 268;
-							k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch2);
-						} else if (c [0] == 49 && c [2] == 59 && c [3] == 52 && c [4] == 126 && c [1] >= 53 && c [1] <= 57) {  // Shift+Alt+(F5 - F8)
-							wch2 = c [1] < 55 ? c [1] + 216 : c [1] + 215;
-							k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch2);
-						} else if (c [0] == 50 && c [2] == 59 && c [3] == 52 && c [4] == 126 && c [1] >= 48 && c [1] <= 52) {  // Shift+Alt+(F9 - F12)
-							wch2 = c [1] < 51 ? c [1] + 225 : c [1] + 224;
-							k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch2);
-						} else if (c [0] == 54 && c [1] == 59 && c [2] == 56 && c [3] == 126) {  // Shift+Ctrl+Alt+KeyNPage
-							k = Key.ShiftMask | Key.CtrlMask | Key.AltMask | Key.PageDown;
-						} else if (c [0] == 53 && c [1] == 59 && c [2] == 56 && c [3] == 126) {  // Shift+Ctrl+Alt+KeyPPage
-							k = Key.ShiftMask | Key.CtrlMask | Key.AltMask | Key.PageUp;
-						} else if (c [0] == 49 && c [1] == 59 && c [2] == 56 && c [3] == 72) {  // Shift+Ctrl+Alt+KeyHome
-							k = Key.ShiftMask | Key.CtrlMask | Key.AltMask | Key.Home;
-						} else if (c [0] == 49 && c [1] == 59 && c [2] == 56 && c [3] == 70) {  // Shift+Ctrl+Alt+KeyEnd
-							k = Key.ShiftMask | Key.CtrlMask | Key.AltMask | Key.End;
-						} else {
-							k = MapCursesKey (wch2);
-						}
+					} else if (wch2 == Curses.KeyCSI) {
+						ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
+							new ConsoleKeyInfo ((char)Key.Esc, 0, false, false, false),
+							new ConsoleKeyInfo ('[', 0, false, false, false)
+						};
+						GetEscSeq (ref code, ref k, ref wch2, ref key, ref cki);
+						return;
 					} else {
 					} else {
 						// Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
 						// Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
 						if (((Key)wch2 & Key.CtrlMask) != 0) {
 						if (((Key)wch2 & Key.CtrlMask) != 0) {
@@ -787,6 +463,52 @@ namespace Terminal.Gui {
 			//}
 			//}
 		}
 		}
 
 
+		void GetEscSeq (ref int code, ref Key k, ref int wch2, ref KeyEvent key, ref ConsoleKeyInfo [] cki)
+		{
+			ConsoleKey ck = 0;
+			ConsoleModifiers mod = 0;
+			while (code == 0) {
+				code = Curses.get_wch (out wch2);
+				var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false);
+				if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) {
+					EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out List<MouseFlags> mouseFlags, out Point pos, out _, ProcessContinuousButtonPressed);
+					if (isKeyMouse) {
+						foreach (var mf in mouseFlags) {
+							ProcessMouseEvent (mf, pos);
+						}
+						cki = null;
+						if (wch2 == 27) {
+							cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0,
+								false, false, false), cki);
+						}
+					} else {
+						k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _);
+						k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k);
+						key = new KeyEvent (k, MapKeyModifiers (k));
+						keyDownHandler (key);
+						keyHandler (key);
+					}
+				} else {
+					cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
+				}
+			}
+		}
+
+		void ProcessMouseEvent (MouseFlags mouseFlag, Point pos)
+		{
+			var me = new MouseEvent () {
+				Flags = mouseFlag,
+				X = pos.X,
+				Y = pos.Y
+			};
+			mouseHandler (me);
+		}
+
+		void ProcessContinuousButtonPressed (MouseFlags mouseFlag, Point pos)
+		{
+			ProcessMouseEvent (mouseFlag, pos);
+		}
+
 		Action<KeyEvent> keyHandler;
 		Action<KeyEvent> keyHandler;
 		Action<KeyEvent> keyDownHandler;
 		Action<KeyEvent> keyDownHandler;
 		Action<KeyEvent> keyUpHandler;
 		Action<KeyEvent> keyUpHandler;
@@ -813,17 +535,12 @@ namespace Terminal.Gui {
 			};
 			};
 		}
 		}
 
 
-		Curses.Event oldMouseEvents, reportableMouseEvents;
 		public override void Init (Action terminalResized)
 		public override void Init (Action terminalResized)
 		{
 		{
 			if (window != null)
 			if (window != null)
 				return;
 				return;
 
 
 			try {
 			try {
-				//Set cursor key to application.
-				//Console.Out.Write ("\x1b[?1h");
-				//Console.Out.Flush ();
-
 				window = Curses.initscr ();
 				window = Curses.initscr ();
 				Curses.set_escdelay (10);
 				Curses.set_escdelay (10);
 			} catch (Exception e) {
 			} catch (Exception e) {
@@ -870,39 +587,21 @@ namespace Terminal.Gui {
 			Curses.noecho ();
 			Curses.noecho ();
 
 
 			Curses.Window.Standard.keypad (true);
 			Curses.Window.Standard.keypad (true);
-			reportableMouseEvents = Curses.mousemask (Curses.Event.AllEvents | Curses.Event.ReportMousePosition, out oldMouseEvents);
 			TerminalResized = terminalResized;
 			TerminalResized = terminalResized;
-			if (reportableMouseEvents.HasFlag (Curses.Event.ReportMousePosition))
-				StartReportingMouseMoves ();
+			StartReportingMouseMoves ();
 
 
-			ResizeScreen ();
-			UpdateOffScreen ();
-
-			//HLine = Curses.ACS_HLINE;
-			//VLine = Curses.ACS_VLINE;
-			//Stipple = Curses.ACS_CKBOARD;
-			//Diamond = Curses.ACS_DIAMOND;
-			//ULCorner = Curses.ACS_ULCORNER;
-			//LLCorner = Curses.ACS_LLCORNER;
-			//URCorner = Curses.ACS_URCORNER;
-			//LRCorner = Curses.ACS_LRCORNER;
-			//LeftTee = Curses.ACS_LTEE;
-			//RightTee = Curses.ACS_RTEE;
-			//TopTee = Curses.ACS_TTEE;
-			//BottomTee = Curses.ACS_BTEE;
-			//RightArrow = Curses.ACS_RARROW;
-			//LeftArrow = Curses.ACS_LARROW;
-			//UpArrow = Curses.ACS_UARROW;
-			//DownArrow = Curses.ACS_DARROW;
+			CurrentAttribute = MakeColor (Color.White, Color.Black);
 
 
 			if (Curses.HasColors) {
 			if (Curses.HasColors) {
 				Curses.StartColor ();
 				Curses.StartColor ();
 				Curses.UseDefaultColors ();
 				Curses.UseDefaultColors ();
 
 
-				CreateColors ();
+				InitalizeColorSchemes ();
 			} else {
 			} else {
-				CreateColors (false);
+				InitalizeColorSchemes (false);
 
 
+				// BUGBUG: This is a hack to make the colors work on the Mac?
+				// The new Theme support overwrites these colors, so this is not needed?
 				Colors.TopLevel.Normal = Curses.COLOR_GREEN;
 				Colors.TopLevel.Normal = Curses.COLOR_GREEN;
 				Colors.TopLevel.Focus = Curses.COLOR_WHITE;
 				Colors.TopLevel.Focus = Curses.COLOR_WHITE;
 				Colors.TopLevel.HotNormal = Curses.COLOR_YELLOW;
 				Colors.TopLevel.HotNormal = Curses.COLOR_YELLOW;
@@ -929,13 +628,16 @@ namespace Terminal.Gui {
 				Colors.Error.HotFocus = Curses.A_REVERSE;
 				Colors.Error.HotFocus = Curses.A_REVERSE;
 				Colors.Error.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
 				Colors.Error.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
 			}
 			}
+
+			ResizeScreen ();
+			UpdateOffScreen ();
+
 		}
 		}
 
 
 		public override void ResizeScreen ()
 		public override void ResizeScreen ()
 		{
 		{
 			Clip = new Rect (0, 0, Cols, Rows);
 			Clip = new Rect (0, 0, Cols, Rows);
-			Console.Out.Write ("\x1b[3J");
-			Console.Out.Flush ();
+			Curses.refresh ();
 		}
 		}
 
 
 		public override void UpdateOffScreen ()
 		public override void UpdateOffScreen ()
@@ -1055,25 +757,21 @@ namespace Terminal.Gui {
 
 
 		public override void Suspend ()
 		public override void Suspend ()
 		{
 		{
-			if (reportableMouseEvents.HasFlag (Curses.Event.ReportMousePosition))
-				StopReportingMouseMoves ();
+			StopReportingMouseMoves ();
 			Platform.Suspend ();
 			Platform.Suspend ();
 			Curses.Window.Standard.redrawwin ();
 			Curses.Window.Standard.redrawwin ();
 			Curses.refresh ();
 			Curses.refresh ();
-			if (reportableMouseEvents.HasFlag (Curses.Event.ReportMousePosition))
-				StartReportingMouseMoves ();
+			StartReportingMouseMoves ();
 		}
 		}
 
 
 		public override void StartReportingMouseMoves ()
 		public override void StartReportingMouseMoves ()
 		{
 		{
-			Console.Out.Write ("\x1b[?1003h");
-			Console.Out.Flush ();
+			Console.Out.Write (EscSeqUtils.EnableMouseEvents);
 		}
 		}
 
 
 		public override void StopReportingMouseMoves ()
 		public override void StopReportingMouseMoves ()
 		{
 		{
-			Console.Out.Write ("\x1b[?1003l");
-			Console.Out.Flush ();
+			Console.Out.Write (EscSeqUtils.DisableMouseEvents);
 		}
 		}
 
 
 		//int lastMouseInterval;
 		//int lastMouseInterval;
@@ -1093,11 +791,6 @@ namespace Terminal.Gui {
 			//Curses.mouseinterval (lastMouseInterval);
 			//Curses.mouseinterval (lastMouseInterval);
 		}
 		}
 
 
-		public override Attribute GetAttribute ()
-		{
-			return currentAttribute;
-		}
-
 		/// <inheritdoc/>
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{
 		{
@@ -1121,7 +814,6 @@ namespace Terminal.Gui {
 
 
 			if (visibility != CursorVisibility.Invisible) {
 			if (visibility != CursorVisibility.Invisible) {
 				Console.Out.Write ("\x1b[{0} q", ((int)visibility >> 24) & 0xFF);
 				Console.Out.Write ("\x1b[{0} q", ((int)visibility >> 24) & 0xFF);
-				Console.Out.Flush ();
 			}
 			}
 
 
 			currentCursorVisibility = visibility;
 			currentCursorVisibility = visibility;
@@ -1186,8 +878,8 @@ namespace Terminal.Gui {
 			background = default;
 			background = default;
 			int back = -1;
 			int back = -1;
 			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
 			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-			      .OfType<ConsoleColor> ()
-			      .Select (s => (int)s);
+				.OfType<ConsoleColor> ()
+				.Select (s => (int)s);
 			if (values.Contains ((value >> 12) & 0xffff)) {
 			if (values.Contains ((value >> 12) & 0xffff)) {
 				hasColor = true;
 				hasColor = true;
 				back = (value >> 12) & 0xffff;
 				back = (value >> 12) & 0xffff;
@@ -1280,6 +972,7 @@ namespace Terminal.Gui {
 
 
 		bool CheckSupport ()
 		bool CheckSupport ()
 		{
 		{
+#pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception.
 			try {
 			try {
 				var (exitCode, result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true);
 				var (exitCode, result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true);
 				if (exitCode == 0 && result.FileExists ()) {
 				if (exitCode == 0 && result.FileExists ()) {
@@ -1289,6 +982,7 @@ namespace Terminal.Gui {
 			} catch (Exception) {
 			} catch (Exception) {
 				// Permissions issue.
 				// Permissions issue.
 			}
 			}
+#pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception.
 			return false;
 			return false;
 		}
 		}
 
 

+ 3 - 27
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -1,30 +1,6 @@
 //
 //
 // mainloop.cs: Simple managed mainloop implementation.
 // mainloop.cs: Simple managed mainloop implementation.
 //
 //
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-// Copyright (C) 2011 Novell (http://www.novell.com)
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
@@ -52,7 +28,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		///   Condition on which to wake up from file descriptor activity.  These match the Linux/BSD poll definitions.
+		///	Condition on which to wake up from file descriptor activity.  These match the Linux/BSD poll definitions.
 		/// </summary>
 		/// </summary>
 		[Flags]
 		[Flags]
 		public enum Condition : short {
 		public enum Condition : short {
@@ -127,10 +103,10 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		///   Removes an active watch from the mainloop.
+		///	Removes an active watch from the mainloop.
 		/// </summary>
 		/// </summary>
 		/// <remarks>
 		/// <remarks>
-		///   The token parameter is the value returned from AddWatch
+		///	The token parameter is the value returned from AddWatch
 		/// </remarks>
 		/// </remarks>
 		public void RemoveWatch (object token)
 		public void RemoveWatch (object token)
 		{
 		{

+ 14 - 21
Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs

@@ -6,7 +6,7 @@
 // you may not use this file except in compliance with the License.
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 // You may obtain a copy of the License at
 //
 //
-//     http://www.apache.org/licenses/LICENSE-2.0
+//	 http://www.apache.org/licenses/LICENSE-2.0
 //
 //
 // Unless required by applicable law or agreed to in writing, software
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -21,8 +21,6 @@ using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading;
 
 
-
-
 namespace Unix.Terminal {
 namespace Unix.Terminal {
 	/// <summary>
 	/// <summary>
 	/// Represents a dynamically loaded unmanaged library in a (partially) platform independent manner.
 	/// Represents a dynamically loaded unmanaged library in a (partially) platform independent manner.
@@ -45,7 +43,7 @@ namespace Unix.Terminal {
 		static bool IsNetCore;
 		static bool IsNetCore;
 
 
 		public static bool IsMacOSPlatform => IsMacOS;
 		public static bool IsMacOSPlatform => IsMacOS;
-		
+
 		[DllImport ("libc")]
 		[DllImport ("libc")]
 		static extern int uname (IntPtr buf);
 		static extern int uname (IntPtr buf);
 
 
@@ -105,11 +103,11 @@ namespace Unix.Terminal {
 		//
 		//
 		public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
 		public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
 		{
 		{
-			if (isFullPath){
+			if (isFullPath) {
 				this.libraryPath = FirstValidLibraryPath (libraryPathAlternatives);
 				this.libraryPath = FirstValidLibraryPath (libraryPathAlternatives);
 				this.handle = PlatformSpecificLoadLibrary (this.libraryPath);
 				this.handle = PlatformSpecificLoadLibrary (this.libraryPath);
 			} else {
 			} else {
-				foreach (var lib in libraryPathAlternatives){
+				foreach (var lib in libraryPathAlternatives) {
 					this.handle = PlatformSpecificLoadLibrary (lib);
 					this.handle = PlatformSpecificLoadLibrary (lib);
 					if (this.handle != IntPtr.Zero)
 					if (this.handle != IntPtr.Zero)
 						break;
 						break;
@@ -164,13 +162,13 @@ namespace Unix.Terminal {
 		}
 		}
 
 
 		public T GetNativeMethodDelegate<T> (string methodName)
 		public T GetNativeMethodDelegate<T> (string methodName)
-		    where T : class
+			where T : class
 		{
 		{
 			var ptr = LoadSymbol (methodName);
 			var ptr = LoadSymbol (methodName);
 			if (ptr == IntPtr.Zero) {
 			if (ptr == IntPtr.Zero) {
 				throw new MissingMethodException (string.Format ("The native method \"{0}\" does not exist", methodName));
 				throw new MissingMethodException (string.Format ("The native method \"{0}\" does not exist", methodName));
 			}
 			}
-			return Marshal.GetDelegateForFunctionPointer<T>(ptr);  // non-generic version is obsolete
+			return Marshal.GetDelegateForFunctionPointer<T> (ptr);  // non-generic version is obsolete
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -209,12 +207,11 @@ namespace Unix.Terminal {
 				}
 				}
 			}
 			}
 			throw new FileNotFoundException (
 			throw new FileNotFoundException (
-			    String.Format ("Error loading native library. Not found in any of the possible locations: {0}",
+				String.Format ("Error loading native library. Not found in any of the possible locations: {0}",
 				string.Join (",", libraryPathAlternatives)));
 				string.Join (",", libraryPathAlternatives)));
 		}
 		}
 
 
-		static class Windows
-		{
+		static class Windows {
 			[DllImport ("kernel32.dll")]
 			[DllImport ("kernel32.dll")]
 			internal static extern IntPtr LoadLibrary (string filename);
 			internal static extern IntPtr LoadLibrary (string filename);
 
 
@@ -222,8 +219,7 @@ namespace Unix.Terminal {
 			internal static extern IntPtr GetProcAddress (IntPtr hModule, string procName);
 			internal static extern IntPtr GetProcAddress (IntPtr hModule, string procName);
 		}
 		}
 
 
-		static class Linux
-		{
+		static class Linux {
 			[DllImport ("libdl.so")]
 			[DllImport ("libdl.so")]
 			internal static extern IntPtr dlopen (string filename, int flags);
 			internal static extern IntPtr dlopen (string filename, int flags);
 
 
@@ -231,8 +227,7 @@ namespace Unix.Terminal {
 			internal static extern IntPtr dlsym (IntPtr handle, string symbol);
 			internal static extern IntPtr dlsym (IntPtr handle, string symbol);
 		}
 		}
 
 
-		static class MacOSX
-		{
+		static class MacOSX {
 			[DllImport ("libSystem.dylib")]
 			[DllImport ("libSystem.dylib")]
 			internal static extern IntPtr dlopen (string filename, int flags);
 			internal static extern IntPtr dlopen (string filename, int flags);
 
 
@@ -247,8 +242,7 @@ namespace Unix.Terminal {
 		/// dlopen and dlsym from the current process as on Linux
 		/// dlopen and dlsym from the current process as on Linux
 		/// Mono sure is linked against these symbols.
 		/// Mono sure is linked against these symbols.
 		/// </summary>
 		/// </summary>
-		static class Mono
-		{
+		static class Mono {
 			[DllImport ("__Internal")]
 			[DllImport ("__Internal")]
 			internal static extern IntPtr dlopen (string filename, int flags);
 			internal static extern IntPtr dlopen (string filename, int flags);
 
 
@@ -261,13 +255,12 @@ namespace Unix.Terminal {
 		/// dlopen and dlsym from the "libcoreclr.so",
 		/// dlopen and dlsym from the "libcoreclr.so",
 		/// to avoid the dependency on libc-dev Linux.
 		/// to avoid the dependency on libc-dev Linux.
 		/// </summary>
 		/// </summary>
-		static class CoreCLR
-		{
+		static class CoreCLR {
 #if NET6_0
 #if NET6_0
 			// Custom resolver to support true single-file apps
 			// Custom resolver to support true single-file apps
 			// (those which run directly from bundle; in-memory).
 			// (those which run directly from bundle; in-memory).
-			//     -1 on Unix means self-referencing binary (libcoreclr.so)
-			//     0 means fallback to CoreCLR's internal resolution
+			//	 -1 on Unix means self-referencing binary (libcoreclr.so)
+			//	 0 means fallback to CoreCLR's internal resolution
 			// Note: meaning of -1 stay the same even for non-single-file form factors.
 			// Note: meaning of -1 stay the same even for non-single-file form factors.
 			static CoreCLR() =>  NativeLibrary.SetDllImportResolver(typeof(CoreCLR).Assembly,
 			static CoreCLR() =>  NativeLibrary.SetDllImportResolver(typeof(CoreCLR).Assembly,
 				(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) =>
 				(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) =>

+ 0 - 5
Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs

@@ -145,11 +145,6 @@ namespace Unix.Terminal {
 			if (l == 1 || l != lines || c != cols) {
 			if (l == 1 || l != lines || c != cols) {
 				lines = l;
 				lines = l;
 				cols = c;
 				cols = c;
-				//if (l <= 0 || c <= 0) {
-				//	Console.Out.Write ($"\x1b[8;50;{c}t");
-				//	Console.Out.Flush ();
-				//	return false;
-				//}
 				return true;
 				return true;
 			}
 			}
 			return false;
 			return false;

+ 2 - 2
Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs

@@ -53,7 +53,6 @@ namespace Unix.Terminal {
 		public const int COLOR_WHITE = unchecked((int)0x7);
 		public const int COLOR_WHITE = unchecked((int)0x7);
 		public const int COLOR_GRAY = unchecked((int)0x8);
 		public const int COLOR_GRAY = unchecked((int)0x8);
 		public const int KEY_CODE_YES = unchecked((int)0x100);
 		public const int KEY_CODE_YES = unchecked((int)0x100);
-		public const int KEY_CODE_SEQ = unchecked((int)0x5b);
 		public const int ERR = unchecked((int)0xffffffff);
 		public const int ERR = unchecked((int)0xffffffff);
 		public const int TIOCGWINSZ  = unchecked((int)0x5413);
 		public const int TIOCGWINSZ  = unchecked((int)0x5413);
 		public const int TIOCGWINSZ_MAC  = unchecked((int)0x40087468);
 		public const int TIOCGWINSZ_MAC  = unchecked((int)0x40087468);
@@ -69,7 +68,7 @@ namespace Unix.Terminal {
 			Button2Released = unchecked((int)0x20),
 			Button2Released = unchecked((int)0x20),
 			Button2Clicked = unchecked((int)0x80),
 			Button2Clicked = unchecked((int)0x80),
 			Button2DoubleClicked = unchecked((int)0x100),
 			Button2DoubleClicked = unchecked((int)0x100),
-			Button2TrippleClicked = unchecked((int)0x200),
+			Button2TripleClicked = unchecked((int)0x200),
 			Button3Pressed = unchecked((int)0x800),
 			Button3Pressed = unchecked((int)0x800),
 			Button3Released = unchecked((int)0x400),
 			Button3Released = unchecked((int)0x400),
 			Button3Clicked = unchecked((int)0x1000),
 			Button3Clicked = unchecked((int)0x1000),
@@ -106,6 +105,7 @@ namespace Unix.Terminal {
 		public const int KeyPPage = unchecked((int)0x153);
 		public const int KeyPPage = unchecked((int)0x153);
 		public const int KeyHome = unchecked((int)0x106);
 		public const int KeyHome = unchecked((int)0x106);
 		public const int KeyMouse = unchecked((int)0x199);
 		public const int KeyMouse = unchecked((int)0x199);
+		public const int KeyCSI = unchecked((int)0x5b);
 		public const int KeyEnd = unchecked((int)0x168);
 		public const int KeyEnd = unchecked((int)0x168);
 		public const int KeyDeleteChar = unchecked((int)0x14a);
 		public const int KeyDeleteChar = unchecked((int)0x14a);
 		public const int KeyInsertChar = unchecked((int)0x14b);
 		public const int KeyInsertChar = unchecked((int)0x14b);

File diff suppressed because it is too large
+ 284 - 289
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs


+ 59 - 43
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -1,9 +1,6 @@
 //
 //
 // FakeDriver.cs: A fake ConsoleDriver for unit tests. 
 // FakeDriver.cs: A fake ConsoleDriver for unit tests. 
 //
 //
-// Authors:
-//   Charlie Kindel (github.com/tig)
-//
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
@@ -33,7 +30,7 @@ namespace Terminal.Gui {
 				UseFakeClipboard = useFakeClipboard;
 				UseFakeClipboard = useFakeClipboard;
 				FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
 				FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
 				FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
 				FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
-				
+
 				// double check usage is correct
 				// double check usage is correct
 				Debug.Assert (useFakeClipboard == false && fakeClipboardAlwaysThrowsNotSupportedException == false);
 				Debug.Assert (useFakeClipboard == false && fakeClipboardAlwaysThrowsNotSupportedException == false);
 				Debug.Assert (useFakeClipboard == false && fakeClipboardIsSupportedAlwaysTrue == false);
 				Debug.Assert (useFakeClipboard == false && fakeClipboardIsSupportedAlwaysTrue == false);
@@ -48,7 +45,12 @@ namespace Terminal.Gui {
 		// Only handling left here because not all terminals has a horizontal scroll bar.
 		// Only handling left here because not all terminals has a horizontal scroll bar.
 		public override int Left => 0;
 		public override int Left => 0;
 		public override int Top => 0;
 		public override int Top => 0;
-		public override bool HeightAsBuffer { get; set; }
+		public override bool EnableConsoleScrolling { get; set; }
+		[Obsolete ("This API is deprecated; use EnableConsoleScrolling instead.", false)]
+		public override bool HeightAsBuffer {
+			get => EnableConsoleScrolling;
+			set => EnableConsoleScrolling = value;
+		}
 		private IClipboard clipboard = null;
 		private IClipboard clipboard = null;
 		public override IClipboard Clipboard => clipboard;
 		public override IClipboard Clipboard => clipboard;
 
 
@@ -131,34 +133,54 @@ namespace Terminal.Gui {
 					//MockConsole.CursorTop = crow;
 					//MockConsole.CursorTop = crow;
 					needMove = false;
 					needMove = false;
 				}
 				}
-				if (runeWidth < 2 && ccol > 0
+				if (runeWidth == 0 && ccol > 0) {
+					var r = contents [crow, ccol - 1, 0];
+					var s = new string (new char [] { (char)r, (char)rune });
+					string sn;
+					if (!s.IsNormalized ()) {
+						sn = s.Normalize ();
+					} else {
+						sn = s;
+					}
+					var c = sn [0];
+					contents [crow, ccol - 1, 0] = c;
+					contents [crow, ccol - 1, 1] = CurrentAttribute;
+					contents [crow, ccol - 1, 2] = 1;
+
+				} else {
+					if (runeWidth < 2 && ccol > 0
 					&& Rune.ColumnWidth ((Rune)contents [crow, ccol - 1, 0]) > 1) {
 					&& Rune.ColumnWidth ((Rune)contents [crow, ccol - 1, 0]) > 1) {
 
 
-					contents [crow, ccol - 1, 0] = (int)(uint)' ';
+						contents [crow, ccol - 1, 0] = (int)(uint)' ';
 
 
-				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
-					&& Rune.ColumnWidth ((Rune)contents [crow, ccol, 0]) > 1) {
+					} else if (runeWidth < 2 && ccol <= Clip.Right - 1
+						&& Rune.ColumnWidth ((Rune)contents [crow, ccol, 0]) > 1) {
 
 
-					contents [crow, ccol + 1, 0] = (int)(uint)' ';
-					contents [crow, ccol + 1, 2] = 1;
+						contents [crow, ccol + 1, 0] = (int)(uint)' ';
+						contents [crow, ccol + 1, 2] = 1;
 
 
-				}
-				if (runeWidth > 1 && ccol == Clip.Right - 1) {
-					contents [crow, ccol, 0] = (int)(uint)' ';
-				} else {
-					contents [crow, ccol, 0] = (int)(uint)rune;
-				}
-				contents [crow, ccol, 1] = currentAttribute;
-				contents [crow, ccol, 2] = 1;
+					}
+					if (runeWidth > 1 && ccol == Clip.Right - 1) {
+						contents [crow, ccol, 0] = (int)(uint)' ';
+					} else {
+						contents [crow, ccol, 0] = (int)(uint)rune;
+					}
+					contents [crow, ccol, 1] = CurrentAttribute;
+					contents [crow, ccol, 2] = 1;
 
 
-				dirtyLine [crow] = true;
-			} else
+					dirtyLine [crow] = true;
+				}
+			} else {
 				needMove = true;
 				needMove = true;
+			}
+
+			if (runeWidth < 0 || runeWidth > 0) {
+				ccol++;
+			}
 
 
-			ccol++;
 			if (runeWidth > 1) {
 			if (runeWidth > 1) {
 				if (validClip && ccol < Clip.Right) {
 				if (validClip && ccol < Clip.Right) {
-					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 1] = CurrentAttribute;
 					contents [crow, ccol, 2] = 0;
 					contents [crow, ccol, 2] = 0;
 				}
 				}
 				ccol++;
 				ccol++;
@@ -169,8 +191,9 @@ namespace Terminal.Gui {
 			//	if (crow + 1 < Rows)
 			//	if (crow + 1 < Rows)
 			//		crow++;
 			//		crow++;
 			//}
 			//}
-			if (sync)
+			if (sync) {
 				UpdateScreen ();
 				UpdateScreen ();
+			}
 		}
 		}
 
 
 		public override void AddStr (ustring str)
 		public override void AddStr (ustring str)
@@ -208,11 +231,10 @@ namespace Terminal.Gui {
 			rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
 			rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
 			FakeConsole.Clear ();
 			FakeConsole.Clear ();
 			ResizeScreen ();
 			ResizeScreen ();
+			// Call InitalizeColorSchemes before UpdateOffScreen as it references Colors
+			CurrentAttribute = MakeColor (Color.White, Color.Black);
+			InitalizeColorSchemes ();
 			UpdateOffScreen ();
 			UpdateOffScreen ();
-
-			CreateColors ();
-
-			//MockConsole.Clear ();
 		}
 		}
 
 
 		public override Attribute MakeAttribute (Color fore, Color back)
 		public override Attribute MakeAttribute (Color fore, Color back)
@@ -225,8 +247,8 @@ namespace Terminal.Gui {
 		{
 		{
 			redrawColor = color;
 			redrawColor = color;
 			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
 			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-			      .OfType<ConsoleColor> ()
-			      .Select (s => (int)s);
+				.OfType<ConsoleColor> ()
+				.Select (s => (int)s);
 			if (values.Contains (color & 0xffff)) {
 			if (values.Contains (color & 0xffff)) {
 				FakeConsole.BackgroundColor = (ConsoleColor)(color & 0xffff);
 				FakeConsole.BackgroundColor = (ConsoleColor)(color & 0xffff);
 			}
 			}
@@ -283,10 +305,9 @@ namespace Terminal.Gui {
 			UpdateCursor ();
 			UpdateCursor ();
 		}
 		}
 
 
-		Attribute currentAttribute;
 		public override void SetAttribute (Attribute c)
 		public override void SetAttribute (Attribute c)
 		{
 		{
-			currentAttribute = c;
+			base.SetAttribute (c);
 		}
 		}
 
 
 		public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
 		public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
@@ -475,11 +496,6 @@ namespace Terminal.Gui {
 			keyUpHandler (new KeyEvent (map, keyModifiers));
 			keyUpHandler (new KeyEvent (map, keyModifiers));
 		}
 		}
 
 
-		public override Attribute GetAttribute ()
-		{
-			return currentAttribute;
-		}
-
 		/// <inheritdoc/>
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{
 		{
@@ -521,7 +537,7 @@ namespace Terminal.Gui {
 			FakeConsole.SetBufferSize (width, height);
 			FakeConsole.SetBufferSize (width, height);
 			cols = width;
 			cols = width;
 			rows = height;
 			rows = height;
-			if (!HeightAsBuffer) {
+			if (!EnableConsoleScrolling) {
 				SetWindowSize (width, height);
 				SetWindowSize (width, height);
 			}
 			}
 			ProcessResize ();
 			ProcessResize ();
@@ -530,7 +546,7 @@ namespace Terminal.Gui {
 		public void SetWindowSize (int width, int height)
 		public void SetWindowSize (int width, int height)
 		{
 		{
 			FakeConsole.SetWindowSize (width, height);
 			FakeConsole.SetWindowSize (width, height);
-			if (!HeightAsBuffer) {
+			if (!EnableConsoleScrolling) {
 				if (width != cols || height != rows) {
 				if (width != cols || height != rows) {
 					SetBufferSize (width, height);
 					SetBufferSize (width, height);
 					cols = width;
 					cols = width;
@@ -542,7 +558,7 @@ namespace Terminal.Gui {
 
 
 		public void SetWindowPosition (int left, int top)
 		public void SetWindowPosition (int left, int top)
 		{
 		{
-			if (HeightAsBuffer) {
+			if (EnableConsoleScrolling) {
 				this.left = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0);
 				this.left = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0);
 				this.top = Math.Max (Math.Min (top, Rows - FakeConsole.WindowHeight), 0);
 				this.top = Math.Max (Math.Min (top, Rows - FakeConsole.WindowHeight), 0);
 			} else if (this.left > 0 || this.top > 0) {
 			} else if (this.left > 0 || this.top > 0) {
@@ -561,7 +577,7 @@ namespace Terminal.Gui {
 
 
 		public override void ResizeScreen ()
 		public override void ResizeScreen ()
 		{
 		{
-			if (!HeightAsBuffer) {
+			if (!EnableConsoleScrolling) {
 				if (FakeConsole.WindowHeight > 0) {
 				if (FakeConsole.WindowHeight > 0) {
 					// Can raise an exception while is still resizing.
 					// Can raise an exception while is still resizing.
 					try {
 					try {
@@ -615,8 +631,8 @@ namespace Terminal.Gui {
 			foreground = default;
 			foreground = default;
 			background = default;
 			background = default;
 			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
 			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-			      .OfType<ConsoleColor> ()
-			      .Select (s => (int)s);
+				.OfType<ConsoleColor> ()
+				.Select (s => (int)s);
 			if (values.Contains (value & 0xffff)) {
 			if (values.Contains (value & 0xffff)) {
 				hasColor = true;
 				hasColor = true;
 				background = (Color)(ConsoleColor)(value & 0xffff);
 				background = (Color)(ConsoleColor)(value & 0xffff);

File diff suppressed because it is too large
+ 258 - 828
Terminal.Gui/ConsoleDrivers/NetDriver.cs


+ 117 - 74
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1,30 +1,6 @@
 //
 //
 // WindowsDriver.cs: Windows specific driver
 // WindowsDriver.cs: Windows specific driver
 //
 //
-// Authors:
-//   Miguel de Icaza ([email protected])
-//   Nick Van Dyck ([email protected])
-//
-// Copyright (c) 2018
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-//
 using NStack;
 using NStack;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -116,6 +92,10 @@ namespace Terminal.Gui {
 
 
 		public bool GetCursorVisibility (out CursorVisibility visibility)
 		public bool GetCursorVisibility (out CursorVisibility visibility)
 		{
 		{
+			if (ScreenBuffer == IntPtr.Zero) {
+				visibility = CursorVisibility.Invisible;
+				return false;
+			}
 			if (!GetConsoleCursorInfo (ScreenBuffer, out ConsoleCursorInfo info)) {
 			if (!GetConsoleCursorInfo (ScreenBuffer, out ConsoleCursorInfo info)) {
 				var err = Marshal.GetLastWin32Error ();
 				var err = Marshal.GetLastWin32Error ();
 				if (err != 0) {
 				if (err != 0) {
@@ -251,7 +231,7 @@ namespace Terminal.Gui {
 				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 			}
 			}
 			var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
 			var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
-			if (!SetConsoleWindowInfo (ScreenBuffer, true, ref winRect)) {
+			if (!SetConsoleWindowInfo (OutputHandle, true, ref winRect)) {
 				//throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 				//throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 				return new Size (cols, rows);
 				return new Size (cols, rows);
 			}
 			}
@@ -261,7 +241,7 @@ namespace Terminal.Gui {
 
 
 		void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi)
 		void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi)
 		{
 		{
-			if (ScreenBuffer != IntPtr.Zero && !SetConsoleScreenBufferInfoEx (OutputHandle, ref csbi)) {
+			if (ScreenBuffer != IntPtr.Zero && !SetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) {
 				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 			}
 			}
 		}
 		}
@@ -283,6 +263,9 @@ namespace Terminal.Gui {
 			position = new Point (csbi.srWindow.Left, csbi.srWindow.Top);
 			position = new Point (csbi.srWindow.Left, csbi.srWindow.Top);
 			SetConsoleOutputWindow (csbi);
 			SetConsoleOutputWindow (csbi);
 			var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0));
 			var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0));
+			if (!SetConsoleScreenBufferInfoEx (OutputHandle, ref csbi)) {
+				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
+			}
 			if (!SetConsoleWindowInfo (OutputHandle, true, ref winRect)) {
 			if (!SetConsoleWindowInfo (OutputHandle, true, ref winRect)) {
 				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 			}
 			}
@@ -639,7 +622,7 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-#if false      // Not needed on the constructor. Perhaps could be used on resizing. To study.
+#if false      // Not needed on the constructor. Perhaps could be used on resizing. To study.                                                                                     
 		[DllImport ("kernel32.dll", ExactSpelling = true)]
 		[DllImport ("kernel32.dll", ExactSpelling = true)]
 		static extern IntPtr GetConsoleWindow ();
 		static extern IntPtr GetConsoleWindow ();
 
 
@@ -732,7 +715,12 @@ namespace Terminal.Gui {
 		public override int Rows => rows;
 		public override int Rows => rows;
 		public override int Left => left;
 		public override int Left => left;
 		public override int Top => top;
 		public override int Top => top;
-		public override bool HeightAsBuffer { get; set; }
+		public override bool EnableConsoleScrolling { get; set; }
+		[Obsolete ("This API is deprecated; use EnableConsoleScrolling instead.", false)]
+		public override bool HeightAsBuffer {
+			get => EnableConsoleScrolling;
+			set => EnableConsoleScrolling = value;
+		}
 		public override IClipboard Clipboard => clipboard;
 		public override IClipboard Clipboard => clipboard;
 		public override int [,,] Contents => contents;
 		public override int [,,] Contents => contents;
 
 
@@ -767,13 +755,13 @@ namespace Terminal.Gui {
 
 
 		private void ChangeWin (Size e)
 		private void ChangeWin (Size e)
 		{
 		{
-			if (!HeightAsBuffer) {
+			if (!EnableConsoleScrolling) {
 				var w = e.Width;
 				var w = e.Width;
 				if (w == cols - 3 && e.Height < rows) {
 				if (w == cols - 3 && e.Height < rows) {
 					w += 3;
 					w += 3;
 				}
 				}
 				var newSize = WinConsole.SetConsoleWindow (
 				var newSize = WinConsole.SetConsoleWindow (
-					(short)Math.Max (w, 16), (short)Math.Max (e.Height, 1));
+					(short)Math.Max (w, 16), (short)Math.Max (e.Height, 0));
 				left = 0;
 				left = 0;
 				top = 0;
 				top = 0;
 				cols = newSize.Width;
 				cols = newSize.Width;
@@ -908,8 +896,12 @@ namespace Terminal.Gui {
 				left = pos.X;
 				left = pos.X;
 				top = pos.Y;
 				top = pos.Y;
 				cols = inputEvent.WindowBufferSizeEvent.size.X;
 				cols = inputEvent.WindowBufferSizeEvent.size.X;
-				rows = inputEvent.WindowBufferSizeEvent.size.Y;
-				//System.Diagnostics.Debug.WriteLine ($"{HeightAsBuffer},{cols},{rows}");
+				if (EnableConsoleScrolling) {
+					rows = Math.Max (inputEvent.WindowBufferSizeEvent.size.Y, rows);
+				} else {
+					rows = inputEvent.WindowBufferSizeEvent.size.Y;
+				}
+				//System.Diagnostics.Debug.WriteLine ($"{EnableConsoleScrolling},{cols},{rows}");
 				ResizeScreen ();
 				ResizeScreen ();
 				UpdateOffScreen ();
 				UpdateOffScreen ();
 				TerminalResized?.Invoke ();
 				TerminalResized?.Invoke ();
@@ -1452,16 +1444,32 @@ namespace Terminal.Gui {
 			TerminalResized = terminalResized;
 			TerminalResized = terminalResized;
 
 
 			try {
 			try {
+				// Needed for Windows Terminal
+				// ESC [ ? 1047 h  Activate xterm alternative buffer (no backscroll)
+				// ESC [ ? 1047 l  Restore xterm working buffer (with backscroll)
+				// ESC [ ? 1048 h  Save cursor position
+				// ESC [ ? 1048 l  Restore cursor position
+				// ESC [ ? 1049 h  Save cursor position and activate xterm alternative buffer (no backscroll)
+				// ESC [ ? 1049 l  Restore cursor position and restore xterm working buffer (with backscroll)
+				// Per Issue #2264 using the alterantive screen buffer is required for Windows Terminal to not 
+				// wipe out the backscroll buffer when the application exits.
+				Console.Out.Write ("\x1b[?1047h");
+
+				// Console.Out.Flush () is not needed. See https://stackoverflow.com/a/20450486/297526
+
 				var winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
 				var winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
 				cols = winSize.Width;
 				cols = winSize.Width;
 				rows = winSize.Height;
 				rows = winSize.Height;
-
 				WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
 				WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
 
 
+				CurrentAttribute = MakeColor (Color.White, Color.Black);
+				InitalizeColorSchemes ();
+
+				CurrentAttribute = MakeColor (Color.White, Color.Black);
+				InitalizeColorSchemes ();
+
 				ResizeScreen ();
 				ResizeScreen ();
 				UpdateOffScreen ();
 				UpdateOffScreen ();
-
-				CreateColors ();
 			} catch (Win32Exception e) {
 			} catch (Win32Exception e) {
 				throw new InvalidOperationException ("The Windows Console output window is not available.", e);
 				throw new InvalidOperationException ("The Windows Console output window is not available.", e);
 			}
 			}
@@ -1478,8 +1486,15 @@ namespace Terminal.Gui {
 				Right = (short)Cols
 				Right = (short)Cols
 			};
 			};
 			WinConsole.ForceRefreshCursorVisibility ();
 			WinConsole.ForceRefreshCursorVisibility ();
-			Console.Out.Write ("\x1b[3J");
-			Console.Out.Flush ();
+			if (!EnableConsoleScrolling) {
+				// ANSI ESC "[xJ" Clears part of the screen.
+				// If n is 0 (or missing), clear from cursor to end of screen.
+				// If n is 1, clear from cursor to beginning of the screen.
+				// If n is 2, clear entire screen (and moves cursor to upper left on DOS ANSI.SYS).
+				// If n is 3, clear entire screen and delete all lines saved in the scrollback buffer
+				// DO NOT USE 3J - even with the alternate screen buffer, it clears the entire scrollback buffer
+				Console.Out.Write ("\x1b[3J");
+			}
 		}
 		}
 
 
 		public override void UpdateOffScreen ()
 		public override void UpdateOffScreen ()
@@ -1517,49 +1532,72 @@ namespace Terminal.Gui {
 			var validClip = IsValidContent (ccol, crow, Clip);
 			var validClip = IsValidContent (ccol, crow, Clip);
 
 
 			if (validClip) {
 			if (validClip) {
-				if (runeWidth < 2 && ccol > 0
-					&& Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) {
-
+				if (runeWidth == 0 && ccol > 0) {
+					var r = contents [crow, ccol - 1, 0];
+					var s = new string (new char [] { (char)r, (char)rune });
+					string sn;
+					if (!s.IsNormalized ()) {
+						sn = s.Normalize ();
+					} else {
+						sn = s;
+					}
+					var c = sn [0];
 					var prevPosition = crow * Cols + (ccol - 1);
 					var prevPosition = crow * Cols + (ccol - 1);
-					OutputBuffer [prevPosition].Char.UnicodeChar = ' ';
-					contents [crow, ccol - 1, 0] = (int)(uint)' ';
+					OutputBuffer [prevPosition].Char.UnicodeChar = c;
+					contents [crow, ccol - 1, 0] = c;
+					OutputBuffer [prevPosition].Attributes = (ushort)CurrentAttribute;
+					contents [crow, ccol - 1, 1] = CurrentAttribute;
+					contents [crow, ccol - 1, 2] = 1;
+					WindowsConsole.SmallRect.Update (ref damageRegion, (short)(ccol - 1), (short)crow);
+				} else {
+					if (runeWidth < 2 && ccol > 0
+						&& Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) {
 
 
-				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
-					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+						var prevPosition = crow * Cols + (ccol - 1);
+						OutputBuffer [prevPosition].Char.UnicodeChar = ' ';
+						contents [crow, ccol - 1, 0] = (int)(uint)' ';
 
 
-					var prevPosition = GetOutputBufferPosition () + 1;
-					OutputBuffer [prevPosition].Char.UnicodeChar = (char)' ';
-					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+					} else if (runeWidth < 2 && ccol <= Clip.Right - 1
+						&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
 
 
+						var prevPosition = GetOutputBufferPosition () + 1;
+						OutputBuffer [prevPosition].Char.UnicodeChar = (char)' ';
+						contents [crow, ccol + 1, 0] = (int)(uint)' ';
+
+					}
+					if (runeWidth > 1 && ccol == Clip.Right - 1) {
+						OutputBuffer [position].Char.UnicodeChar = (char)' ';
+						contents [crow, ccol, 0] = (int)(uint)' ';
+					} else {
+						OutputBuffer [position].Char.UnicodeChar = (char)rune;
+						contents [crow, ccol, 0] = (int)(uint)rune;
+					}
+					OutputBuffer [position].Attributes = (ushort)CurrentAttribute;
+					contents [crow, ccol, 1] = CurrentAttribute;
+					contents [crow, ccol, 2] = 1;
+					WindowsConsole.SmallRect.Update (ref damageRegion, (short)ccol, (short)crow);
 				}
 				}
-				if (runeWidth > 1 && ccol == Clip.Right - 1) {
-					OutputBuffer [position].Char.UnicodeChar = (char)' ';
-					contents [crow, ccol, 0] = (int)(uint)' ';
-				} else {
-					OutputBuffer [position].Char.UnicodeChar = (char)rune;
-					contents [crow, ccol, 0] = (int)(uint)rune;
-				}
-				OutputBuffer [position].Attributes = (ushort)currentAttribute;
-				contents [crow, ccol, 1] = currentAttribute;
-				contents [crow, ccol, 2] = 1;
-				WindowsConsole.SmallRect.Update (ref damageRegion, (short)ccol, (short)crow);
 			}
 			}
 
 
-			ccol++;
+			if (runeWidth < 0 || runeWidth > 0) {
+				ccol++;
+			}
+
 			if (runeWidth > 1) {
 			if (runeWidth > 1) {
 				if (validClip && ccol < Clip.Right) {
 				if (validClip && ccol < Clip.Right) {
 					position = GetOutputBufferPosition ();
 					position = GetOutputBufferPosition ();
-					OutputBuffer [position].Attributes = (ushort)currentAttribute;
+					OutputBuffer [position].Attributes = (ushort)CurrentAttribute;
 					OutputBuffer [position].Char.UnicodeChar = (char)0x00;
 					OutputBuffer [position].Char.UnicodeChar = (char)0x00;
 					contents [crow, ccol, 0] = (int)(uint)0x00;
 					contents [crow, ccol, 0] = (int)(uint)0x00;
-					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 1] = CurrentAttribute;
 					contents [crow, ccol, 2] = 0;
 					contents [crow, ccol, 2] = 0;
 				}
 				}
 				ccol++;
 				ccol++;
 			}
 			}
 
 
-			if (sync)
+			if (sync) {
 				UpdateScreen ();
 				UpdateScreen ();
+			}
 		}
 		}
 
 
 		public override void AddStr (ustring str)
 		public override void AddStr (ustring str)
@@ -1568,11 +1606,9 @@ namespace Terminal.Gui {
 				AddRune (rune);
 				AddRune (rune);
 		}
 		}
 
 
-		Attribute currentAttribute;
-
 		public override void SetAttribute (Attribute c)
 		public override void SetAttribute (Attribute c)
 		{
 		{
-			currentAttribute = c;
+			base.SetAttribute (c);
 		}
 		}
 
 
 		public override Attribute MakeColor (Color foreground, Color background)
 		public override Attribute MakeColor (Color foreground, Color background)
@@ -1625,7 +1661,7 @@ namespace Terminal.Gui {
 			if (damageRegion.Left == -1)
 			if (damageRegion.Left == -1)
 				return;
 				return;
 
 
-			if (!HeightAsBuffer) {
+			if (!EnableConsoleScrolling) {
 				var windowSize = WinConsole.GetConsoleBufferWindow (out _);
 				var windowSize = WinConsole.GetConsoleBufferWindow (out _);
 				if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows))
 				if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows))
 					return;
 					return;
@@ -1672,11 +1708,18 @@ namespace Terminal.Gui {
 		{
 		{
 			WinConsole.Cleanup ();
 			WinConsole.Cleanup ();
 			WinConsole = null;
 			WinConsole = null;
-		}
 
 
-		public override Attribute GetAttribute ()
-		{
-			return currentAttribute;
+			// Needed for Windows Terminal
+			// Clear the alternative screen buffer from the cursor to the
+			// end of the screen.
+			// Note, [3J causes Windows Terminal to wipe out the entire NON ALTERNATIVE
+			// backbuffer! So we need to use [0J instead.
+			Console.Out.Write ("\x1b[0J");
+
+			// Disable alternative screen buffer.
+			Console.Out.Write ("\x1b[?1047l");
+
+			// Console.Out.Flush () is not needed. See https://stackoverflow.com/a/20450486/297526
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>
@@ -1757,8 +1800,8 @@ namespace Terminal.Gui {
 			foreground = default;
 			foreground = default;
 			background = default;
 			background = default;
 			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
 			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-			      .OfType<ConsoleColor> ()
-			      .Select (s => (int)s);
+				.OfType<ConsoleColor> ()
+				.Select (s => (int)s);
 			if (values.Contains ((value >> 4) & 0xffff)) {
 			if (values.Contains ((value >> 4) & 0xffff)) {
 				hasColor = true;
 				hasColor = true;
 				background = (Color)(ConsoleColor)((value >> 4) & 0xffff);
 				background = (Color)(ConsoleColor)((value >> 4) & 0xffff);
@@ -1874,9 +1917,9 @@ namespace Terminal.Gui {
 		{
 		{
 			while (true) {
 			while (true) {
 				Thread.Sleep (100);
 				Thread.Sleep (100);
-				if (!consoleDriver.HeightAsBuffer) {
+				if (!consoleDriver.EnableConsoleScrolling) {
 					windowSize = winConsole.GetConsoleBufferWindow (out _);
 					windowSize = winConsole.GetConsoleBufferWindow (out _);
-					//System.Diagnostics.Debug.WriteLine ($"{consoleDriver.HeightAsBuffer},{windowSize.Width},{windowSize.Height}");
+					//System.Diagnostics.Debug.WriteLine ($"{consoleDriver.EnableConsoleScrolling},{windowSize.Width},{windowSize.Height}");
 					if (windowSize != Size.Empty && windowSize.Width != consoleDriver.Cols
 					if (windowSize != Size.Empty && windowSize.Width != consoleDriver.Cols
 						|| windowSize.Height != consoleDriver.Rows) {
 						|| windowSize.Height != consoleDriver.Rows) {
 						return;
 						return;

+ 49 - 25
Terminal.Gui/Core/Application.cs

@@ -57,7 +57,7 @@ namespace Terminal.Gui {
 	///   </para>
 	///   </para>
 	/// </remarks>
 	/// </remarks>
 	public static class Application {
 	public static class Application {
-		static Stack<Toplevel> toplevels = new Stack<Toplevel> ();
+		static readonly Stack<Toplevel> toplevels = new Stack<Toplevel> ();
 
 
 		/// <summary>
 		/// <summary>
 		/// The current <see cref="ConsoleDriver"/> in use.
 		/// The current <see cref="ConsoleDriver"/> in use.
@@ -111,28 +111,54 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public static View WantContinuousButtonPressedView { get; private set; }
 		public static View WantContinuousButtonPressedView { get; private set; }
 
 
+		private static bool? _enableConsoleScrolling;
+
 		/// <summary>
 		/// <summary>
-		/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
+		/// The current <see cref="ConsoleDriver.EnableConsoleScrolling"/> used in the terminal.
 		/// </summary>
 		/// </summary>
-		public static bool HeightAsBuffer {
+		/// <remarks>
+		/// <para>
+		/// If <see langword="false"/> (the default) the height of the Terminal.Gui application (<see cref="ConsoleDriver.Rows"/>) 
+		/// tracks to the height of the visible console view when the console is resized. In this case 
+		/// scrolling in the console will be disabled and all <see cref="ConsoleDriver.Rows"/> will remain visible.
+		/// </para>
+		/// <para>
+		/// If <see langword="true"/> then height of the Terminal.Gui application <see cref="ConsoleDriver.Rows"/> only tracks 
+		/// the height of the visible console view when the console is made larger (the application will only grow in height, never shrink). 
+		/// In this case console scrolling is enabled and the contents (<see cref="ConsoleDriver.Rows"/> high) will scroll
+		/// as the console scrolls. 
+		/// </para>
+		/// This API was previously named 'HeightAsBuffer` but was renamed to make its purpose more clear.
+		/// </remarks>
+		public static bool EnableConsoleScrolling {
 			get {
 			get {
 				if (Driver == null) {
 				if (Driver == null) {
-					throw new ArgumentNullException ("The driver must be initialized first.");
+					return _enableConsoleScrolling.HasValue && _enableConsoleScrolling.Value;
 				}
 				}
-				return Driver.HeightAsBuffer;
+				return Driver.EnableConsoleScrolling;
 			}
 			}
 			set {
 			set {
+				_enableConsoleScrolling = value;
 				if (Driver == null) {
 				if (Driver == null) {
-					throw new ArgumentNullException ("The driver must be initialized first.");
+					return;
 				}
 				}
-				Driver.HeightAsBuffer = value;
+				Driver.EnableConsoleScrolling = value;
 			}
 			}
 		}
 		}
 
 
+		/// <summary>
+		/// This API is deprecated; use <see cref="EnableConsoleScrolling"/> instead.
+		/// </summary>
+		[Obsolete ("This API is deprecated; use EnableConsoleScrolling instead.", false)]
+		public static bool HeightAsBuffer {
+			get => EnableConsoleScrolling;
+			set => EnableConsoleScrolling = value;
+		}
+
 		static Key alternateForwardKey = Key.PageDown | Key.CtrlMask;
 		static Key alternateForwardKey = Key.PageDown | Key.CtrlMask;
 
 
 		/// <summary>
 		/// <summary>
-		/// Alternative key to navigate forwards through all views. Ctrl+Tab is always used.
+		/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
 		/// </summary>
 		/// </summary>
 		public static Key AlternateForwardKey {
 		public static Key AlternateForwardKey {
 			get => alternateForwardKey;
 			get => alternateForwardKey;
@@ -147,7 +173,7 @@ namespace Terminal.Gui {
 
 
 		static void OnAlternateForwardKeyChanged (Key oldKey)
 		static void OnAlternateForwardKeyChanged (Key oldKey)
 		{
 		{
-			foreach (var top in toplevels) {
+			foreach (var top in toplevels.ToArray ()) {
 				top.OnAlternateForwardKeyChanged (oldKey);
 				top.OnAlternateForwardKeyChanged (oldKey);
 			}
 			}
 		}
 		}
@@ -155,7 +181,7 @@ namespace Terminal.Gui {
 		static Key alternateBackwardKey = Key.PageUp | Key.CtrlMask;
 		static Key alternateBackwardKey = Key.PageUp | Key.CtrlMask;
 
 
 		/// <summary>
 		/// <summary>
-		/// Alternative key to navigate backwards through all views. Shift+Ctrl+Tab is always used.
+		/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
 		/// </summary>
 		/// </summary>
 		public static Key AlternateBackwardKey {
 		public static Key AlternateBackwardKey {
 			get => alternateBackwardKey;
 			get => alternateBackwardKey;
@@ -170,7 +196,7 @@ namespace Terminal.Gui {
 
 
 		static void OnAlternateBackwardKeyChanged (Key oldKey)
 		static void OnAlternateBackwardKeyChanged (Key oldKey)
 		{
 		{
-			foreach (var top in toplevels) {
+			foreach (var top in toplevels.ToArray ()) {
 				top.OnAlternateBackwardKeyChanged (oldKey);
 				top.OnAlternateBackwardKeyChanged (oldKey);
 			}
 			}
 		}
 		}
@@ -200,7 +226,8 @@ namespace Terminal.Gui {
 
 
 		static void OnQuitKeyChanged (Key oldKey)
 		static void OnQuitKeyChanged (Key oldKey)
 		{
 		{
-			foreach (var top in toplevels) {
+			// Duplicate the list so if it changes during enumeration we're safe
+			foreach (var top in toplevels.ToArray ()) {
 				top.OnQuitKeyChanged (oldKey);
 				top.OnQuitKeyChanged (oldKey);
 			}
 			}
 		}
 		}
@@ -212,7 +239,7 @@ namespace Terminal.Gui {
 		public static MainLoop MainLoop { get; private set; }
 		public static MainLoop MainLoop { get; private set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Disable or enable the mouse in this <see cref="Application"/>
+		/// Disable or enable the mouse. The mouse is enabled by default.
 		/// </summary>
 		/// </summary>
 		public static bool IsMouseDisabled { get; set; }
 		public static bool IsMouseDisabled { get; set; }
 
 
@@ -266,7 +293,7 @@ namespace Terminal.Gui {
 		// users use async/await on their code
 		// users use async/await on their code
 		//
 		//
 		class MainLoopSyncContext : SynchronizationContext {
 		class MainLoopSyncContext : SynchronizationContext {
-			MainLoop mainLoop;
+			readonly MainLoop mainLoop;
 
 
 			public MainLoopSyncContext (MainLoop mainLoop)
 			public MainLoopSyncContext (MainLoop mainLoop)
 			{
 			{
@@ -305,9 +332,9 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// If set, it forces the use of the System.Console-based driver.
+		/// If <see langword="true"/>, forces the use of the System.Console-based (see <see cref="NetDriver"/>) driver. The default is <see langword="false"/>.
 		/// </summary>
 		/// </summary>
-		public static bool UseSystemConsole;
+		public static bool UseSystemConsole { get; set; } = false;
 
 
 		// For Unit testing - ignores UseSystemConsole
 		// For Unit testing - ignores UseSystemConsole
 		internal static bool ForceFakeConsole;
 		internal static bool ForceFakeConsole;
@@ -422,6 +449,7 @@ namespace Terminal.Gui {
 			MainLoop = new MainLoop (mainLoopDriver);
 			MainLoop = new MainLoop (mainLoopDriver);
 
 
 			try {
 			try {
+				Driver.EnableConsoleScrolling = EnableConsoleScrolling;
 				Driver.Init (TerminalResized);
 				Driver.Init (TerminalResized);
 			} catch (InvalidOperationException ex) {
 			} catch (InvalidOperationException ex) {
 				// This is a case where the driver is unable to initialize the console.
 				// This is a case where the driver is unable to initialize the console.
@@ -933,6 +961,8 @@ namespace Terminal.Gui {
 				if (Top != null && toplevel != Top && !toplevels.Contains (Top)) {
 				if (Top != null && toplevel != Top && !toplevels.Contains (Top)) {
 					Top.Dispose ();
 					Top.Dispose ();
 					Top = null;
 					Top = null;
+				} else if (Top != null && toplevel != Top && toplevels.Contains (Top)) {
+					Top.OnLeave (toplevel);
 				}
 				}
 				if (string.IsNullOrEmpty (toplevel.Id.ToString ())) {
 				if (string.IsNullOrEmpty (toplevel.Id.ToString ())) {
 					var count = 1;
 					var count = 1;
@@ -986,9 +1016,7 @@ namespace Terminal.Gui {
 			toplevel.PositionToplevels ();
 			toplevel.PositionToplevels ();
 			toplevel.WillPresent ();
 			toplevel.WillPresent ();
 			if (refreshDriver) {
 			if (refreshDriver) {
-				if (MdiTop != null) {
-					MdiTop.OnChildLoaded (toplevel);
-				}
+				MdiTop?.OnChildLoaded (toplevel);
 				toplevel.OnLoaded ();
 				toplevel.OnLoaded ();
 				Redraw (toplevel);
 				Redraw (toplevel);
 				toplevel.PositionCursor ();
 				toplevel.PositionCursor ();
@@ -1043,6 +1071,7 @@ namespace Terminal.Gui {
 					MdiTop.OnAllChildClosed ();
 					MdiTop.OnAllChildClosed ();
 				} else {
 				} else {
 					SetCurrentAsTop ();
 					SetCurrentAsTop ();
+					Current.OnEnter (Current);
 				}
 				}
 				Refresh ();
 				Refresh ();
 			}
 			}
@@ -1096,6 +1125,7 @@ namespace Terminal.Gui {
 			NotifyStopRunState = null;
 			NotifyStopRunState = null;
 			_initialized = false;
 			_initialized = false;
 			mouseGrabView = null;
 			mouseGrabView = null;
+			_enableConsoleScrolling = false;
 
 
 			// Reset synchronization context to allow the user to run async/await,
 			// Reset synchronization context to allow the user to run async/await,
 			// as the main loop has been ended, the synchronization context from 
 			// as the main loop has been ended, the synchronization context from 
@@ -1111,12 +1141,6 @@ namespace Terminal.Gui {
 			Driver.Refresh ();
 			Driver.Refresh ();
 		}
 		}
 
 
-		static void Refresh (View view)
-		{
-			view.Redraw (view.Bounds);
-			Driver.Refresh ();
-		}
-
 		/// <summary>
 		/// <summary>
 		/// Triggers a refresh of the entire display.
 		/// Triggers a refresh of the entire display.
 		/// </summary>
 		/// </summary>

+ 45 - 22
Terminal.Gui/Core/Autocomplete/Autocomplete.cs

@@ -324,6 +324,7 @@ namespace Terminal.Gui {
 			if (IsWordChar ((char)kb.Key)) {
 			if (IsWordChar ((char)kb.Key)) {
 				Visible = true;
 				Visible = true;
 				closed = false;
 				closed = false;
+				return false;
 			}
 			}
 
 
 			if (kb.Key == Reopen) {
 			if (kb.Key == Reopen) {
@@ -332,6 +333,9 @@ namespace Terminal.Gui {
 
 
 			if (closed || Suggestions.Count == 0) {
 			if (closed || Suggestions.Count == 0) {
 				Visible = false;
 				Visible = false;
+				if (!closed) {
+					Close ();
+				}
 				return false;
 				return false;
 			}
 			}
 
 
@@ -345,6 +349,17 @@ namespace Terminal.Gui {
 				return true;
 				return true;
 			}
 			}
 
 
+			if (kb.Key == Key.CursorLeft || kb.Key == Key.CursorRight) {
+				GenerateSuggestions (kb.Key == Key.CursorLeft ? -1 : 1);
+				if (Suggestions.Count == 0) {
+					Visible = false;
+					if (!closed) {
+						Close ();
+					}
+				}
+				return false;
+			}
+
 			if (kb.Key == SelectionKey) {
 			if (kb.Key == SelectionKey) {
 				return Select ();
 				return Select ();
 			}
 			}
@@ -368,6 +383,9 @@ namespace Terminal.Gui {
 		public virtual bool MouseEvent (MouseEvent me, bool fromHost = false)
 		public virtual bool MouseEvent (MouseEvent me, bool fromHost = false)
 		{
 		{
 			if (fromHost) {
 			if (fromHost) {
+				if (!Visible) {
+					return false;
+				}
 				GenerateSuggestions ();
 				GenerateSuggestions ();
 				if (Visible && Suggestions.Count == 0) {
 				if (Visible && Suggestions.Count == 0) {
 					Visible = false;
 					Visible = false;
@@ -444,7 +462,8 @@ namespace Terminal.Gui {
 		/// Populates <see cref="Suggestions"/> with all strings in <see cref="AllSuggestions"/> that
 		/// Populates <see cref="Suggestions"/> with all strings in <see cref="AllSuggestions"/> that
 		/// match with the current cursor position/text in the <see cref="HostControl"/>
 		/// match with the current cursor position/text in the <see cref="HostControl"/>
 		/// </summary>
 		/// </summary>
-		public virtual void GenerateSuggestions ()
+		/// <param name="columnOffset">The column offset.</param>
+		public virtual void GenerateSuggestions (int columnOffset = 0)
 		{
 		{
 			// if there is nothing to pick from
 			// if there is nothing to pick from
 			if (AllSuggestions.Count == 0) {
 			if (AllSuggestions.Count == 0) {
@@ -452,7 +471,7 @@ namespace Terminal.Gui {
 				return;
 				return;
 			}
 			}
 
 
-			var currentWord = GetCurrentWord ();
+			var currentWord = GetCurrentWord (columnOffset);
 
 
 			if (string.IsNullOrWhiteSpace (currentWord)) {
 			if (string.IsNullOrWhiteSpace (currentWord)) {
 				ClearSuggestions ();
 				ClearSuggestions ();
@@ -524,11 +543,12 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Returns the currently selected word from the <see cref="HostControl"/>.
 		/// Returns the currently selected word from the <see cref="HostControl"/>.
 		/// <para>
 		/// <para>
-		/// When overriding this method views can make use of <see cref="IdxToWord(List{Rune}, int)"/>
+		/// When overriding this method views can make use of <see cref="IdxToWord(List{Rune}, int, int)"/>
 		/// </para>
 		/// </para>
 		/// </summary>
 		/// </summary>
+		/// <param name="columnOffset">The column offset.</param>
 		/// <returns></returns>
 		/// <returns></returns>
-		protected abstract string GetCurrentWord ();
+		protected abstract string GetCurrentWord (int columnOffset = 0);
 
 
 		/// <summary>
 		/// <summary>
 		/// <para>
 		/// <para>
@@ -536,37 +556,40 @@ namespace Terminal.Gui {
 		/// or null.  Also returns null if the <paramref name="idx"/> is positioned in the middle of a word.
 		/// or null.  Also returns null if the <paramref name="idx"/> is positioned in the middle of a word.
 		/// </para>
 		/// </para>
 		/// 
 		/// 
-		/// <para>Use this method to determine whether autocomplete should be shown when the cursor is at
-		/// a given point in a line and to get the word from which suggestions should be generated.</para>
+		/// <para>
+		/// Use this method to determine whether autocomplete should be shown when the cursor is at
+		/// a given point in a line and to get the word from which suggestions should be generated.
+		/// Use the <paramref name="columnOffset"/> to indicate if search the word at left (negative),
+		/// at right (positive) or at the current column (zero) which is the default.
+		/// </para>
 		/// </summary>
 		/// </summary>
 		/// <param name="line"></param>
 		/// <param name="line"></param>
 		/// <param name="idx"></param>
 		/// <param name="idx"></param>
+		/// <param name="columnOffset"></param>
 		/// <returns></returns>
 		/// <returns></returns>
-		protected virtual string IdxToWord (List<Rune> line, int idx)
+		protected virtual string IdxToWord (List<Rune> line, int idx, int columnOffset = 0)
 		{
 		{
 			StringBuilder sb = new StringBuilder ();
 			StringBuilder sb = new StringBuilder ();
+			var endIdx = idx;
 
 
-			// do not generate suggestions if the cursor is positioned in the middle of a word
-			bool areMidWord;
-
-			if (idx == line.Count) {
-				// the cursor positioned at the very end of the line
-				areMidWord = false;
-			} else {
-				// we are in the middle of a word if the cursor is over a letter/number
-				areMidWord = IsWordChar (line [idx]);
+			// get the ending word index
+			while (endIdx < line.Count) {
+				if (IsWordChar (line [endIdx])) {
+					endIdx++;
+				} else {
+					break;
+				}
 			}
 			}
 
 
-			// if we are in the middle of a word then there is no way to autocomplete that word
-			if (areMidWord) {
+			// It isn't a word char then there is no way to autocomplete that word
+			if (endIdx == idx && columnOffset != 0) {
 				return null;
 				return null;
 			}
 			}
 
 
 			// we are at the end of a word.  Work out what has been typed so far
 			// we are at the end of a word.  Work out what has been typed so far
-			while (idx-- > 0) {
-
-				if (IsWordChar (line [idx])) {
-					sb.Insert (0, (char)line [idx]);
+			while (endIdx-- > 0) {
+				if (IsWordChar (line [endIdx])) {
+					sb.Insert (0, (char)line [endIdx]);
 				} else {
 				} else {
 					break;
 					break;
 				}
 				}

+ 2 - 1
Terminal.Gui/Core/Autocomplete/IAutocomplete.cs

@@ -109,6 +109,7 @@ namespace Terminal.Gui {
 		/// Populates <see cref="Suggestions"/> with all strings in <see cref="AllSuggestions"/> that
 		/// Populates <see cref="Suggestions"/> with all strings in <see cref="AllSuggestions"/> that
 		/// match with the current cursor position/text in the <see cref="HostControl"/>.
 		/// match with the current cursor position/text in the <see cref="HostControl"/>.
 		/// </summary>
 		/// </summary>
-		void GenerateSuggestions ();
+		/// <param name="columnOffset">The column offset. Current (zero - default), left (negative), right (positive).</param>
+		void GenerateSuggestions (int columnOffset = 0);
 	}
 	}
 }
 }

+ 52 - 10
Terminal.Gui/Core/Border.cs

@@ -325,6 +325,7 @@ namespace Terminal.Gui {
 		private Point effect3DOffset = new Point (1, 1);
 		private Point effect3DOffset = new Point (1, 1);
 		private Attribute? effect3DBrush;
 		private Attribute? effect3DBrush;
 		private ustring title = ustring.Empty;
 		private ustring title = ustring.Empty;
+		private View child;
 
 
 		/// <summary>
 		/// <summary>
 		/// Specifies the <see cref="Gui.BorderStyle"/> for a view.
 		/// Specifies the <see cref="Gui.BorderStyle"/> for a view.
@@ -433,7 +434,47 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Gets or sets the single child element of a <see cref="View"/>.
 		/// Gets or sets the single child element of a <see cref="View"/>.
 		/// </summary>
 		/// </summary>
-		public View Child { get; set; }
+		public View Child {
+			get => child;
+			set {
+				child = value;
+				if (child != null && Parent != null) {
+					Parent.Initialized += Parent_Initialized;
+					Parent.Removed += Parent_Removed;
+				}
+			}
+		}
+
+		private void Parent_Removed (View obj)
+		{
+			BorderBrush = default;
+			Background = default;
+			child.Removed -= Parent_Removed;
+		}
+
+		private void Parent_Initialized (object s, EventArgs e)
+		{
+			SetMarginFrameTitleBrush ();
+			child.Initialized -= Parent_Initialized;
+		}
+
+		private void SetMarginFrameTitleBrush ()
+		{
+			if (child != null) {
+				var view = Parent?.Border != null ? Parent : child;
+				if (view.ColorScheme != null) {
+					if (borderBrush == default) {
+						BorderBrush = view.GetNormalColor ().Foreground;
+					}
+					if (background == default) {
+						Background = view.GetNormalColor ().Background;
+					}
+					return;
+				}
+			}
+			BorderBrush = default;
+			Background = default;
+		}
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets the parent <see cref="Child"/> parent if any.
 		/// Gets the parent <see cref="Child"/> parent if any.
@@ -583,7 +624,7 @@ namespace Terminal.Gui {
 				Child.Clear (borderRect);
 				Child.Clear (borderRect);
 			}
 			}
 
 
-			driver.SetAttribute (savedAttribute);
+			driver.SetAttribute (new Attribute (BorderBrush, Background));
 
 
 			// Draw margin frame
 			// Draw margin frame
 			if (DrawMarginFrame) {
 			if (DrawMarginFrame) {
@@ -607,6 +648,7 @@ namespace Terminal.Gui {
 					driver.DrawWindowFrame (borderRect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
 					driver.DrawWindowFrame (borderRect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
 				}
 				}
 			}
 			}
+			driver.SetAttribute (savedAttribute);
 		}
 		}
 
 
 		private void DrawChildBorder (Rect frame, bool fill = true)
 		private void DrawChildBorder (Rect frame, bool fill = true)
@@ -703,7 +745,7 @@ namespace Terminal.Gui {
 				}
 				}
 			}
 			}
 
 
-			driver.SetAttribute (savedAttribute);
+			driver.SetAttribute (new Attribute (BorderBrush, Background));
 
 
 			// Draw the MarginFrame
 			// Draw the MarginFrame
 			if (DrawMarginFrame) {
 			if (DrawMarginFrame) {
@@ -858,7 +900,7 @@ namespace Terminal.Gui {
 				}
 				}
 			}
 			}
 
 
-			driver.SetAttribute (savedAttribute);
+			driver.SetAttribute (new Attribute (BorderBrush, Background));
 
 
 			// Draw the MarginFrame
 			// Draw the MarginFrame
 			if (DrawMarginFrame) {
 			if (DrawMarginFrame) {
@@ -947,9 +989,9 @@ namespace Terminal.Gui {
 		{
 		{
 			var driver = Application.Driver;
 			var driver = Application.Driver;
 			if (DrawMarginFrame) {
 			if (DrawMarginFrame) {
-				driver.SetAttribute (Child.GetNormalColor ());
-				if (Child.HasFocus)
-					driver.SetAttribute (Child.ColorScheme.HotNormal);
+				driver.SetAttribute (new Attribute (BorderBrush, Background));
+				if (view.HasFocus)
+					driver.SetAttribute (new Attribute (Child.ColorScheme.HotNormal.Foreground, Background));
 				var padding = view.Border.GetSumThickness ();
 				var padding = view.Border.GetSumThickness ();
 				Rect scrRect;
 				Rect scrRect;
 				if (view == Child) {
 				if (view == Child) {
@@ -958,7 +1000,7 @@ namespace Terminal.Gui {
 					driver.DrawWindowTitle (scrRect, Title, 0, 0, 0, 0);
 					driver.DrawWindowTitle (scrRect, Title, 0, 0, 0, 0);
 				} else {
 				} else {
 					scrRect = view.ViewToScreen (new Rect (0, 0, view.Frame.Width, view.Frame.Height));
 					scrRect = view.ViewToScreen (new Rect (0, 0, view.Frame.Width, view.Frame.Height));
-					driver.DrawWindowTitle (scrRect, Title,
+					driver.DrawWindowTitle (scrRect, Parent.Border.Title,
 						padding.Left, padding.Top, padding.Right, padding.Bottom);
 						padding.Left, padding.Top, padding.Right, padding.Bottom);
 				}
 				}
 			}
 			}
@@ -974,9 +1016,9 @@ namespace Terminal.Gui {
 		{
 		{
 			var driver = Application.Driver;
 			var driver = Application.Driver;
 			if (DrawMarginFrame) {
 			if (DrawMarginFrame) {
-				driver.SetAttribute (view.GetNormalColor ());
+				driver.SetAttribute (new Attribute (BorderBrush, Background));
 				if (view.HasFocus) {
 				if (view.HasFocus) {
-					driver.SetAttribute (view.ColorScheme.HotNormal);
+					driver.SetAttribute (new Attribute (view.ColorScheme.HotNormal.Foreground, Background));
 				}
 				}
 				var padding = Parent.Border.GetSumThickness ();
 				var padding = Parent.Border.GetSumThickness ();
 				var scrRect = Parent.ViewToScreen (new Rect (0, 0, rect.Width, rect.Height));
 				var scrRect = Parent.ViewToScreen (new Rect (0, 0, rect.Width, rect.Height));

+ 320 - 255
Terminal.Gui/Core/ConsoleDriver.cs

@@ -1,23 +1,22 @@
 //
 //
-// ConsoleDriver.cs: Definition for the Console Driver API
+// ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations.
 //
 //
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-// Define this to enable diagnostics drawing for Window Frames
 using NStack;
 using NStack;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using Unix.Terminal;
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
 	/// <summary>
 	/// <summary>
-	/// Basic colors that can be used to set the foreground and background colors in console applications.
+	/// Colors that can be used to set the foreground and background colors in console applications.
 	/// </summary>
 	/// </summary>
+	/// <remarks>
+	/// The <see cref="Attribute.HasValidColors"/> value indicates either no-color has been set or the color is invalid.
+	/// </remarks>
 	public enum Color {
 	public enum Color {
 		/// <summary>
 		/// <summary>
 		/// The black color.
 		/// The black color.
@@ -86,22 +85,104 @@ namespace Terminal.Gui {
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
-	/// Attributes are used as elements that contain both a foreground and a background or platform specific features
+	/// Indicates the RGB for true colors.
+	/// </summary>
+	public class TrueColor {
+		/// <summary>
+		/// Red color component.
+		/// </summary>
+		public int Red { get; }
+		/// <summary>
+		/// Green color component.
+		/// </summary>
+		public int Green { get; }
+		/// <summary>
+		/// Blue color component.
+		/// </summary>
+		public int Blue { get; }
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="TrueColor"/> struct.
+		/// </summary>
+		/// <param name="red"></param>
+		/// <param name="green"></param>
+		/// <param name="blue"></param>
+		public TrueColor (int red, int green, int blue)
+		{
+			Red = red;
+			Green = green;
+			Blue = blue;
+		}
+
+		/// <summary>
+		/// Converts true color to console color.
+		/// </summary>
+		/// <returns></returns>
+		public Color ToConsoleColor ()
+		{
+			var trueColorMap = new Dictionary<TrueColor, Color> () {
+				{ new TrueColor (0,0,0),Color.Black},
+				{ new TrueColor (0, 0, 0x80),Color.Blue},
+				{ new TrueColor (0, 0x80, 0),Color.Green},
+				{ new TrueColor (0, 0x80, 0x80),Color.Cyan},
+				{ new TrueColor (0x80, 0, 0),Color.Red},
+				{ new TrueColor (0x80, 0, 0x80),Color.Magenta},
+				{ new TrueColor (0xC1, 0x9C, 0x00),Color.Brown},  // TODO confirm this
+				{ new TrueColor (0xC0, 0xC0, 0xC0),Color.Gray},
+				{ new TrueColor (0x80, 0x80, 0x80),Color.DarkGray},
+				{ new TrueColor (0, 0, 0xFF),Color.BrightBlue},
+				{ new TrueColor (0, 0xFF, 0),Color.BrightGreen},
+				{ new TrueColor (0, 0xFF, 0xFF),Color.BrightCyan},
+				{ new TrueColor (0xFF, 0, 0),Color.BrightRed},
+				{ new TrueColor (0xFF, 0, 0xFF),Color.BrightMagenta },
+				{ new TrueColor (0xFF, 0xFF, 0),Color.BrightYellow},
+				{ new TrueColor (0xFF, 0xFF, 0xFF),Color.White},
+				};
+			// Iterate over all colors in the map
+			var distances = trueColorMap.Select (
+							k => Tuple.Create (
+								// the candidate we are considering matching against (RGB)
+								k.Key,
+
+								CalculateDistance (k.Key, this)
+							));
+
+			// get the closest
+			var match = distances.OrderBy (t => t.Item2).First ();
+			return trueColorMap [match.Item1];
+		}
+
+		private float CalculateDistance (TrueColor color1, TrueColor color2)
+		{
+			// use RGB distance
+			return
+				Math.Abs (color1.Red - color2.Red) +
+				Math.Abs (color1.Green - color2.Green) +
+				Math.Abs (color1.Blue - color2.Blue);
+		}
+	}
+
+	/// <summary>
+	/// Attributes are used as elements that contain both a foreground and a background or platform specific features.
 	/// </summary>
 	/// </summary>
 	/// <remarks>
 	/// <remarks>
-	///   <see cref="Attribute"/>s are needed to map colors to terminal capabilities that might lack colors, on color
-	///   scenarios, they encode both the foreground and the background color and are used in the <see cref="ColorScheme"/>
-	///   class to define color schemes that can be used in your application.
+	///   <see cref="Attribute"/>s are needed to map colors to terminal capabilities that might lack colors. 
+	///   They encode both the foreground and the background color and are used in the <see cref="ColorScheme"/>
+	///   class to define color schemes that can be used in an application.
 	/// </remarks>
 	/// </remarks>
 	public struct Attribute {
 	public struct Attribute {
 		/// <summary>
 		/// <summary>
-		/// The color attribute value.
+		/// The <see cref="ConsoleDriver"/>-specific color attribute value. If <see cref="Initialized"/> is <see langword="false"/> 
+		/// the value of this property is invalid (typically because the Attribute was created before a driver was loaded)
+		/// and the attribute should be re-made (see <see cref="Make(Color, Color)"/>) before it is used.
 		/// </summary>
 		/// </summary>
 		public int Value { get; }
 		public int Value { get; }
+
 		/// <summary>
 		/// <summary>
 		/// The foreground color.
 		/// The foreground color.
 		/// </summary>
 		/// </summary>
 		public Color Foreground { get; }
 		public Color Foreground { get; }
+
 		/// <summary>
 		/// <summary>
 		/// The background color.
 		/// The background color.
 		/// </summary>
 		/// </summary>
@@ -117,8 +198,10 @@ namespace Terminal.Gui {
 			Color foreground = default;
 			Color foreground = default;
 			Color background = default;
 			Color background = default;
 
 
+			Initialized = false;
 			if (Application.Driver != null) {
 			if (Application.Driver != null) {
 				Application.Driver.GetColors (value, out foreground, out background);
 				Application.Driver.GetColors (value, out foreground, out background);
+				Initialized = true;
 			}
 			}
 			Value = value;
 			Value = value;
 			Foreground = foreground;
 			Foreground = foreground;
@@ -136,6 +219,7 @@ namespace Terminal.Gui {
 			Value = value;
 			Value = value;
 			Foreground = foreground;
 			Foreground = foreground;
 			Background = background;
 			Background = background;
+			Initialized = true;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -145,7 +229,9 @@ namespace Terminal.Gui {
 		/// <param name="background">Background</param>
 		/// <param name="background">Background</param>
 		public Attribute (Color foreground = new Color (), Color background = new Color ())
 		public Attribute (Color foreground = new Color (), Color background = new Color ())
 		{
 		{
-			Value = Make (foreground, background).Value;
+			var make = Make (foreground, background);
+			Initialized = make.Initialized;
+			Value = make.Value;
 			Foreground = foreground;
 			Foreground = foreground;
 			Background = background;
 			Background = background;
 		}
 		}
@@ -158,29 +244,42 @@ namespace Terminal.Gui {
 		public Attribute (Color color) : this (color, color) { }
 		public Attribute (Color color) : this (color, color) { }
 
 
 		/// <summary>
 		/// <summary>
-		/// Implicit conversion from an <see cref="Attribute"/> to the underlying Int32 representation
+		/// Implicit conversion from an <see cref="Attribute"/> to the underlying, driver-specific, Int32 representation
+		/// of the color.
 		/// </summary>
 		/// </summary>
-		/// <returns>The integer value stored in the attribute.</returns>
+		/// <returns>The driver-specific color value stored in the attribute.</returns>
 		/// <param name="c">The attribute to convert</param>
 		/// <param name="c">The attribute to convert</param>
-		public static implicit operator int (Attribute c) => c.Value;
+		public static implicit operator int (Attribute c)
+		{
+			if (!c.Initialized) throw new InvalidOperationException ("Attribute: Attributes must be initialized by a driver before use.");
+			return c.Value;
+		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Implicitly convert an integer value into an <see cref="Attribute"/>
+		/// Implicitly convert an driver-specific color value into an <see cref="Attribute"/>
 		/// </summary>
 		/// </summary>
-		/// <returns>An attribute with the specified integer value.</returns>
+		/// <returns>An attribute with the specified driver-specific color value.</returns>
 		/// <param name="v">value</param>
 		/// <param name="v">value</param>
 		public static implicit operator Attribute (int v) => new Attribute (v);
 		public static implicit operator Attribute (int v) => new Attribute (v);
 
 
 		/// <summary>
 		/// <summary>
-		/// Creates an <see cref="Attribute"/> from the specified foreground and background.
+		/// Creates an <see cref="Attribute"/> from the specified foreground and background colors.
 		/// </summary>
 		/// </summary>
-		/// <returns>The make.</returns>
+		/// <remarks>
+		/// If a <see cref="ConsoleDriver"/> has not been loaded (<c>Application.Driver == null</c>) this
+		/// method will return an attribute with <see cref="Initialized"/> set to  <see langword="false"/>.
+		/// </remarks>
+		/// <returns>The new attribute.</returns>
 		/// <param name="foreground">Foreground color to use.</param>
 		/// <param name="foreground">Foreground color to use.</param>
 		/// <param name="background">Background color to use.</param>
 		/// <param name="background">Background color to use.</param>
 		public static Attribute Make (Color foreground, Color background)
 		public static Attribute Make (Color foreground, Color background)
 		{
 		{
-			if (Application.Driver == null)
-				throw new InvalidOperationException ("The Application has not been initialized");
+			if (Application.Driver == null) {
+				// Create the attribute, but show it's not been initialized
+				return new Attribute (-1, foreground, background) {
+					Initialized = false
+				};
+			}
 			return Application.Driver.MakeAttribute (foreground, background);
 			return Application.Driver.MakeAttribute (foreground, background);
 		}
 		}
 
 
@@ -194,170 +293,109 @@ namespace Terminal.Gui {
 				throw new InvalidOperationException ("The Application has not been initialized");
 				throw new InvalidOperationException ("The Application has not been initialized");
 			return Application.Driver.GetAttribute ();
 			return Application.Driver.GetAttribute ();
 		}
 		}
+
+		/// <summary>
+		/// If <see langword="true"/> the attribute has been initialized by a <see cref="ConsoleDriver"/> and 
+		/// thus has <see cref="Value"/> that is valid for that driver. If <see langword="false"/> the <see cref="Foreground"/>
+		/// and <see cref="Background"/> colors may have been set '-1' but
+		/// the attribute has not been mapped to a <see cref="ConsoleDriver"/> specific color value.
+		/// </summary>
+		/// <remarks>
+		/// Attributes that have not been initialized must eventually be initialized before being passed to a driver.
+		/// </remarks>
+		public bool Initialized { get; internal set; }
+
+		/// <summary>
+		/// Returns <see langword="true"/> if the Attribute is valid (both foreground and background have valid color values).
+		/// </summary>
+		/// <returns></returns>
+		public bool HasValidColors { get => (int)Foreground > -1 && (int)Background > -1; }
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
-	/// Color scheme definitions, they cover some common scenarios and are used
-	/// typically in containers such as <see cref="Window"/> and <see cref="FrameView"/> to set the scheme that is used by all the
-	/// views contained inside.
+	/// Defines the color <see cref="Attribute"/>s for common visible elements in a <see cref="View"/>. 
+	/// Containers such as <see cref="Window"/> and <see cref="FrameView"/> use <see cref="ColorScheme"/> to determine
+	/// the colors used by sub-views.
 	/// </summary>
 	/// </summary>
+	/// <remarks>
+	/// See also: <see cref="Colors.ColorSchemes"/>.
+	/// </remarks>
 	public class ColorScheme : IEquatable<ColorScheme> {
 	public class ColorScheme : IEquatable<ColorScheme> {
-		Attribute _normal;
-		Attribute _focus;
-		Attribute _hotNormal;
-		Attribute _hotFocus;
-		Attribute _disabled;
-		internal string caller = "";
+		Attribute _normal = new Attribute (Color.White, Color.Black);
+		Attribute _focus = new Attribute (Color.White, Color.Black);
+		Attribute _hotNormal = new Attribute (Color.White, Color.Black);
+		Attribute _hotFocus = new Attribute (Color.White, Color.Black);
+		Attribute _disabled = new Attribute (Color.White, Color.Black);
 
 
 		/// <summary>
 		/// <summary>
-		/// The default color for text, when the view is not focused.
+		/// Used by <see cref="Colors.SetColorScheme(ColorScheme, string)"/> and <see cref="Colors.GetColorScheme(string)"/> to track which ColorScheme 
+		/// is being accessed.
 		/// </summary>
 		/// </summary>
-		public Attribute Normal { get { return _normal; } set { _normal = SetAttribute (value); } }
+		internal string schemeBeingSet = "";
 
 
 		/// <summary>
 		/// <summary>
-		/// The color for text when the view has the focus.
+		/// The foreground and background color for text when the view is not focused, hot, or disabled.
 		/// </summary>
 		/// </summary>
-		public Attribute Focus { get { return _focus; } set { _focus = SetAttribute (value); } }
+		public Attribute Normal {
+			get { return _normal; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_normal = value;
+			}
+		}
 
 
 		/// <summary>
 		/// <summary>
-		/// The color for the hotkey when a view is not focused
+		/// The foreground and background color for text when the view has the focus.
 		/// </summary>
 		/// </summary>
-		public Attribute HotNormal { get { return _hotNormal; } set { _hotNormal = SetAttribute (value); } }
+		public Attribute Focus {
+			get { return _focus; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_focus = value;
+			}
+		}
 
 
 		/// <summary>
 		/// <summary>
-		/// The color for the hotkey when the view is focused.
+		/// The foreground and background color for text when the view is highlighted (hot).
 		/// </summary>
 		/// </summary>
-		public Attribute HotFocus { get { return _hotFocus; } set { _hotFocus = SetAttribute (value); } }
+		public Attribute HotNormal {
+			get { return _hotNormal; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_hotNormal = value;
+			}
+		}
 
 
 		/// <summary>
 		/// <summary>
-		/// The default color for text, when the view is disabled.
+		/// The foreground and background color for text when the view is highlighted (hot) and has focus.
 		/// </summary>
 		/// </summary>
-		public Attribute Disabled { get { return _disabled; } set { _disabled = SetAttribute (value); } }
-
-		bool preparingScheme = false;
-
-		Attribute SetAttribute (Attribute attribute, [CallerMemberName] string callerMemberName = null)
-		{
-			if (!Application._initialized && !preparingScheme)
-				return attribute;
-
-			if (preparingScheme)
-				return attribute;
-
-			preparingScheme = true;
-			switch (caller) {
-			case "TopLevel":
-				switch (callerMemberName) {
-				case "Normal":
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					break;
-				case "Focus":
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
-					break;
-				case "HotNormal":
-					HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
-					break;
-				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
-					if (Focus.Foreground != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
-					break;
-				}
-				break;
-
-			case "Base":
-				switch (callerMemberName) {
-				case "Normal":
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					break;
-				case "Focus":
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
-					break;
-				case "HotNormal":
-					HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
-					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
-					break;
-				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
-					if (Focus.Foreground != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
-					break;
+		public Attribute HotFocus {
+			get { return _hotFocus; }
+			set {
+				if (!value.HasValidColors) {
+					return;
 				}
 				}
-				break;
-
-			case "Menu":
-				switch (callerMemberName) {
-				case "Normal":
-					if (Focus.Background != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (attribute.Foreground, Focus.Background);
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					Disabled = Application.Driver.MakeAttribute (Disabled.Foreground, attribute.Background);
-					break;
-				case "Focus":
-					Normal = Application.Driver.MakeAttribute (attribute.Foreground, Normal.Background);
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
-					break;
-				case "HotNormal":
-					if (Focus.Background != attribute.Background)
-						HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
-					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
-					Disabled = Application.Driver.MakeAttribute (Disabled.Foreground, attribute.Background);
-					break;
-				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
-					if (Focus.Foreground != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
-					break;
-				case "Disabled":
-					if (Focus.Background != attribute.Background)
-						HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
-					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					break;
-				}
-				break;
-
-			case "Dialog":
-				switch (callerMemberName) {
-				case "Normal":
-					if (Focus.Background != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (attribute.Foreground, Focus.Background);
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					break;
-				case "Focus":
-					Normal = Application.Driver.MakeAttribute (attribute.Foreground, Normal.Background);
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
-					break;
-				case "HotNormal":
-					if (Focus.Background != attribute.Background)
-						HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
-					if (Normal.Foreground != attribute.Background)
-						Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
-					break;
-				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
-					if (Focus.Foreground != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
-					break;
-				}
-				break;
+				_hotFocus = value;
+			}
+		}
 
 
-			case "Error":
-				switch (callerMemberName) {
-				case "Normal":
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
-					break;
-				case "HotNormal":
-				case "HotFocus":
-					HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, attribute.Background);
-					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
-					break;
+		/// <summary>
+		/// The default foreground and background color for text, when the view is disabled.
+		/// </summary>
+		public Attribute Disabled {
+			get { return _disabled; }
+			set {
+				if (!value.HasValidColors) {
+					return;
 				}
 				}
-				break;
+				_disabled = value;
 			}
 			}
-			preparingScheme = false;
-			return attribute;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -421,20 +459,67 @@ namespace Terminal.Gui {
 		{
 		{
 			return !(left == right);
 			return !(left == right);
 		}
 		}
+
+		internal void Initialize ()
+		{
+			// If the new scheme was created before a driver was loaded, we need to re-make
+			// the attributes
+			if (!_normal.Initialized) {
+				_normal = new Attribute (_normal.Foreground, _normal.Background);
+			}
+			if (!_focus.Initialized) {
+				_focus = new Attribute (_focus.Foreground, _focus.Background);
+			}
+			if (!_hotNormal.Initialized) {
+				_hotNormal = new Attribute (_hotNormal.Foreground, _hotNormal.Background);
+			}
+			if (!_hotFocus.Initialized) {
+				_hotFocus = new Attribute (_hotFocus.Foreground, _hotFocus.Background);
+			}
+			if (!_disabled.Initialized) {
+				_disabled = new Attribute (_disabled.Foreground, _disabled.Background);
+			}
+		}
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
 	/// The default <see cref="ColorScheme"/>s for the application.
 	/// The default <see cref="ColorScheme"/>s for the application.
 	/// </summary>
 	/// </summary>
+	/// <remarks>
+	/// This property can be set in a Theme to change the default <see cref="Colors"/> for the application.
+	/// </remarks>
 	public static class Colors {
 	public static class Colors {
+		private class SchemeNameComparerIgnoreCase : IEqualityComparer<string> {
+			public bool Equals (string x, string y)
+			{
+				if (x != null && y != null) {
+					return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase);
+				}
+				return false;
+			}
+
+			public int GetHashCode (string obj)
+			{
+				return obj.ToLowerInvariant ().GetHashCode ();
+			}
+		}
+
 		static Colors ()
 		static Colors ()
+		{
+			ColorSchemes = Create ();
+		}
+
+		/// <summary>
+		/// Creates a new dictionary of new <see cref="ColorScheme"/> objects.
+		/// </summary>
+		public static Dictionary<string, ColorScheme> Create ()
 		{
 		{
 			// Use reflection to dynamically create the default set of ColorSchemes from the list defined 
 			// Use reflection to dynamically create the default set of ColorSchemes from the list defined 
 			// by the class. 
 			// by the class. 
-			ColorSchemes = typeof (Colors).GetProperties ()
+			return typeof (Colors).GetProperties ()
 				.Where (p => p.PropertyType == typeof (ColorScheme))
 				.Where (p => p.PropertyType == typeof (ColorScheme))
-				.Select (p => new KeyValuePair<string, ColorScheme> (p.Name, new ColorScheme ())) // (ColorScheme)p.GetValue (p)))
-				.ToDictionary (t => t.Key, t => t.Value);
+				.Select (p => new KeyValuePair<string, ColorScheme> (p.Name, new ColorScheme ()))
+				.ToDictionary (t => t.Key, t => t.Value, comparer: new SchemeNameComparerIgnoreCase ());
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -487,21 +572,21 @@ namespace Terminal.Gui {
 		/// </remarks>
 		/// </remarks>
 		public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); }
 		public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); }
 
 
-		static ColorScheme GetColorScheme ([CallerMemberName] string callerMemberName = null)
+		static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null)
 		{
 		{
-			return ColorSchemes [callerMemberName];
+			return ColorSchemes [schemeBeingSet];
 		}
 		}
 
 
-		static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string callerMemberName = null)
+		static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null)
 		{
 		{
-			ColorSchemes [callerMemberName] = colorScheme;
-			colorScheme.caller = callerMemberName;
+			ColorSchemes [schemeBeingSet] = colorScheme;
+			colorScheme.schemeBeingSet = schemeBeingSet;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
 		/// Provides the defined <see cref="ColorScheme"/>s.
 		/// Provides the defined <see cref="ColorScheme"/>s.
 		/// </summary>
 		/// </summary>
-		public static Dictionary<string, ColorScheme> ColorSchemes { get; }
+		public static Dictionary<string, ColorScheme> ColorSchemes { get; private set; }
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
@@ -561,72 +646,7 @@ namespace Terminal.Gui {
 		/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
 		/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
 		BoxFix = 0x02020164,
 		BoxFix = 0x02020164,
 	}
 	}
-
-	///// <summary>
-	///// Special characters that can be drawn with 
-	///// </summary>
-	//public enum SpecialChar {
-	//	/// <summary>
-	//	/// Horizontal line character.
-	//	/// </summary>
-	//	HLine,
-
-	//	/// <summary>
-	//	/// Vertical line character.
-	//	/// </summary>
-	//	VLine,
-
-	//	/// <summary>
-	//	/// Stipple pattern
-	//	/// </summary>
-	//	Stipple,
-
-	//	/// <summary>
-	//	/// Diamond character
-	//	/// </summary>
-	//	Diamond,
-
-	//	/// <summary>
-	//	/// Upper left corner
-	//	/// </summary>
-	//	ULCorner,
-
-	//	/// <summary>
-	//	/// Lower left corner
-	//	/// </summary>
-	//	LLCorner,
-
-	//	/// <summary>
-	//	/// Upper right corner
-	//	/// </summary>
-	//	URCorner,
-
-	//	/// <summary>
-	//	/// Lower right corner
-	//	/// </summary>
-	//	LRCorner,
-
-	//	/// <summary>
-	//	/// Left tee
-	//	/// </summary>
-	//	LeftTee,
-
-	//	/// <summary>
-	//	/// Right tee
-	//	/// </summary>
-	//	RightTee,
-
-	//	/// <summary>
-	//	/// Top tee
-	//	/// </summary>
-	//	TopTee,
-
-	//	/// <summary>
-	//	/// The bottom tee.
-	//	/// </summary>
-	//	BottomTee,
-	//}
-
+	
 	/// <summary>
 	/// <summary>
 	/// ConsoleDriver is an abstract class that defines the requirements for a console driver.  
 	/// ConsoleDriver is an abstract class that defines the requirements for a console driver.  
 	/// There are currently three implementations: <see cref="CursesDriver"/> (for Unix and Mac), <see cref="WindowsDriver"/>, and <see cref="NetDriver"/> that uses the .NET Console API.
 	/// There are currently three implementations: <see cref="CursesDriver"/> (for Unix and Mac), <see cref="WindowsDriver"/>, and <see cref="NetDriver"/> that uses the .NET Console API.
@@ -663,9 +683,27 @@ namespace Terminal.Gui {
 		public abstract IClipboard Clipboard { get; }
 		public abstract IClipboard Clipboard { get; }
 
 
 		/// <summary>
 		/// <summary>
-		/// If false height is measured by the window height and thus no scrolling.
-		/// If true then height is measured by the buffer height, enabling scrolling.
+		/// <para>
+		/// If <see langword="false"/> (the default) the height of the Terminal.Gui application (<see cref="Rows"/>) 
+		/// tracks to the height of the visible console view when the console is resized. In this case 
+		/// scrolling in the console will be disabled and all <see cref="Rows"/> will remain visible.
+		/// </para>
+		/// <para>
+		/// If <see langword="true"/> then height of the Terminal.Gui application <see cref="Rows"/> only tracks 
+		/// the height of the visible console view when the console is made larger (the application will only grow in height, never shrink). 
+		/// In this case console scrolling is enabled and the contents (<see cref="Rows"/> high) will scroll
+		/// as the console scrolls. 
+		/// </para>
 		/// </summary>
 		/// </summary>
+		/// <remarks>
+		/// NOTE: This functionaliy is currently broken on Windows Terminal.
+		/// </remarks>
+		public abstract bool EnableConsoleScrolling { get; set; }
+
+		/// <summary>
+		/// This API is deprecated; use <see cref="EnableConsoleScrolling"/> instead.
+		/// </summary>
+		[Obsolete ("This API is deprecated; use EnableConsoleScrolling instead.", false)]
 		public abstract bool HeightAsBuffer { get; set; }
 		public abstract bool HeightAsBuffer { get; set; }
 
 
 		/// <summary>
 		/// <summary>
@@ -785,13 +823,35 @@ namespace Terminal.Gui {
 		public abstract void UpdateScreen ();
 		public abstract void UpdateScreen ();
 
 
 		/// <summary>
 		/// <summary>
-		/// Selects the specified attribute as the attribute to use for future calls to AddRune, AddString.
+		/// The current attribute the driver is using. 
+		/// </summary>
+		public virtual Attribute CurrentAttribute {
+			get => currentAttribute;
+			set {
+				if (!value.Initialized && value.HasValidColors && Application.Driver != null) {
+					CurrentAttribute = Application.Driver.MakeAttribute (value.Foreground, value.Background);
+					return;
+				}
+				if (!value.Initialized) Debug.WriteLine ("ConsoleDriver.CurrentAttribute: Attributes must be initialized before use.");
+
+				currentAttribute = value;
+			}
+		}
+
+		/// <summary>
+		/// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.
 		/// </summary>
 		/// </summary>
+		/// <remarks>
+		/// Implementations should call <c>base.SetAttribute(c)</c>.
+		/// </remarks>
 		/// <param name="c">C.</param>
 		/// <param name="c">C.</param>
-		public abstract void SetAttribute (Attribute c);
+		public virtual void SetAttribute (Attribute c)
+		{
+			CurrentAttribute = c;
+		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Set Colors from limit sets of colors.
+		/// Set Colors from limit sets of colors. Not implemented by any driver: See Issue #2300.
 		/// </summary>
 		/// </summary>
 		/// <param name="foreground">Foreground.</param>
 		/// <param name="foreground">Foreground.</param>
 		/// <param name="background">Background.</param>
 		/// <param name="background">Background.</param>
@@ -801,7 +861,7 @@ namespace Terminal.Gui {
 		// that independently with the R, G, B values.
 		// that independently with the R, G, B values.
 		/// <summary>
 		/// <summary>
 		/// Advanced uses - set colors to any pre-set pairs, you would need to init_color
 		/// Advanced uses - set colors to any pre-set pairs, you would need to init_color
-		/// that independently with the R, G, B values.
+		/// that independently with the R, G, B values. Not implemented by any driver: See Issue #2300.
 		/// </summary>
 		/// </summary>
 		/// <param name="foregroundColorId">Foreground color identifier.</param>
 		/// <param name="foregroundColorId">Foreground color identifier.</param>
 		/// <param name="backgroundColorId">Background color identifier.</param>
 		/// <param name="backgroundColorId">Background color identifier.</param>
@@ -1124,12 +1184,13 @@ namespace Terminal.Gui {
 		public abstract void StopReportingMouseMoves ();
 		public abstract void StopReportingMouseMoves ();
 
 
 		/// <summary>
 		/// <summary>
-		/// Disables the cooked event processing from the mouse driver.  At startup, it is assumed mouse events are cooked.
+		/// Disables the cooked event processing from the mouse driver. 
+		/// At startup, it is assumed mouse events are cooked. Not implemented by any driver: See Issue #2300.
 		/// </summary>
 		/// </summary>
 		public abstract void UncookMouse ();
 		public abstract void UncookMouse ();
 
 
 		/// <summary>
 		/// <summary>
-		/// Enables the cooked event processing from the mouse driver
+		/// Enables the cooked event processing from the mouse driver. Not implemented by any driver: See Issue #2300.
 		/// </summary>
 		/// </summary>
 		public abstract void CookMouse ();
 		public abstract void CookMouse ();
 
 
@@ -1322,6 +1383,7 @@ namespace Terminal.Gui {
 		/// Lower right rounded corner
 		/// Lower right rounded corner
 		/// </summary>
 		/// </summary>
 		public Rune LRRCorner = '\u256f';
 		public Rune LRRCorner = '\u256f';
+		private Attribute currentAttribute;
 
 
 		/// <summary>
 		/// <summary>
 		/// Make the attribute for the foreground and background colors.
 		/// Make the attribute for the foreground and background colors.
@@ -1335,7 +1397,7 @@ namespace Terminal.Gui {
 		/// Gets the current <see cref="Attribute"/>.
 		/// Gets the current <see cref="Attribute"/>.
 		/// </summary>
 		/// </summary>
 		/// <returns>The current attribute.</returns>
 		/// <returns>The current attribute.</returns>
-		public abstract Attribute GetAttribute ();
+		public Attribute GetAttribute () => CurrentAttribute;
 
 
 		/// <summary>
 		/// <summary>
 		/// Make the <see cref="Colors"/> for the <see cref="ColorScheme"/>.
 		/// Make the <see cref="Colors"/> for the <see cref="ColorScheme"/>.
@@ -1346,21 +1408,24 @@ namespace Terminal.Gui {
 		public abstract Attribute MakeColor (Color foreground, Color background);
 		public abstract Attribute MakeColor (Color foreground, Color background);
 
 
 		/// <summary>
 		/// <summary>
-		/// Create all <see cref="Colors"/> with the <see cref="ColorScheme"/> for the console driver.
+		/// Ensures all <see cref="Attribute"/>s in <see cref="Colors.ColorSchemes"/> are correctly 
+		/// initialized by the driver.
 		/// </summary>
 		/// </summary>
-		/// <param name="hasColors">Flag indicating if colors are supported.</param>
-		public void CreateColors (bool hasColors = true)
+		/// <param name="supportsColors">Flag indicating if colors are supported (not used).</param>
+		public void InitalizeColorSchemes (bool supportsColors = true)
 		{
 		{
-			Colors.TopLevel = new ColorScheme ();
-			Colors.Base = new ColorScheme ();
-			Colors.Dialog = new ColorScheme ();
-			Colors.Menu = new ColorScheme ();
-			Colors.Error = new ColorScheme ();
+			// Ensure all Attributes are initialized by the driver
+			foreach (var s in Colors.ColorSchemes) {
+				s.Value.Initialize ();
+			}
 
 
-			if (!hasColors) {
+			if (!supportsColors) {
 				return;
 				return;
 			}
 			}
 
 
+
+			// Define the default color theme only if the user has not defined one.
+
 			Colors.TopLevel.Normal = MakeColor (Color.BrightGreen, Color.Black);
 			Colors.TopLevel.Normal = MakeColor (Color.BrightGreen, Color.Black);
 			Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan);
 			Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan);
 			Colors.TopLevel.HotNormal = MakeColor (Color.Brown, Color.Black);
 			Colors.TopLevel.HotNormal = MakeColor (Color.Brown, Color.Black);

+ 19 - 0
Terminal.Gui/Core/ConsoleKeyMapping.cs

@@ -334,6 +334,25 @@ namespace Terminal.Gui {
 			return (Key)consoleKey;
 			return (Key)consoleKey;
 		}
 		}
 
 
+		/// <summary>
+		/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="Key"/>.
+		/// </summary>
+		/// <param name="keyInfo">The console key info.</param>
+		/// <param name="key">The key.</param>
+		/// <returns>The <see cref="Key"/> with <see cref="ConsoleModifiers"/> or the <paramref name="key"/></returns>
+		public static Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
+		{
+			Key keyMod = new Key ();
+			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
+				keyMod = Key.ShiftMask;
+			if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0)
+				keyMod |= Key.CtrlMask;
+			if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0)
+				keyMod |= Key.AltMask;
+
+			return keyMod != Key.Null ? keyMod | key : key;
+		}
+
 		private static HashSet<ScanCodeMapping> scanCodes = new HashSet<ScanCodeMapping> {
 		private static HashSet<ScanCodeMapping> scanCodes = new HashSet<ScanCodeMapping> {
 			new ScanCodeMapping (1,27,0,27),	// Escape
 			new ScanCodeMapping (1,27,0,27),	// Escape
 			new ScanCodeMapping (1,27,ConsoleModifiers.Shift,27),
 			new ScanCodeMapping (1,27,ConsoleModifiers.Shift,27),

+ 109 - 0
Terminal.Gui/Core/EscSeqUtils/EscSeqReq.cs

@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Represents the state of an ANSI escape sequence request.
+	/// </summary>
+	/// <remarks>
+	/// This is needed because there are some escape sequence requests responses that are equal
+	/// with some normal escape sequences and thus, will be only considered the responses to the
+	/// requests that were registered with this object.
+	/// </remarks>
+	public class EscSeqReqStatus {
+		/// <summary>
+		/// Gets the terminating.
+		/// </summary>
+		public string Terminating { get; }
+		/// <summary>
+		/// Gets the number of requests.
+		/// </summary>
+		public int NumRequests { get; }
+		/// <summary>
+		/// Gets information about unfinished requests.
+		/// </summary>
+		public int NumOutstanding { get; set; }
+
+		/// <summary>
+		/// Creates a new state of escape sequence request.
+		/// </summary>
+		/// <param name="terminating">The terminating.</param>
+		/// <param name="numOfReq">The number of requests.</param>
+		public EscSeqReqStatus (string terminating, int numOfReq)
+		{
+			Terminating = terminating;
+			NumRequests = NumOutstanding = numOfReq;
+		}
+	}
+
+	/// <summary>
+	/// Manages a list of <see cref="EscSeqReqStatus"/>.
+	/// </summary>
+	public class EscSeqReqProc {
+		/// <summary>
+		/// Gets the <see cref="EscSeqReqStatus"/> list.
+		/// </summary>
+		public List<EscSeqReqStatus> EscSeqReqStats { get; } = new List<EscSeqReqStatus> ();
+
+		/// <summary>
+		/// Adds a new <see cref="EscSeqReqStatus"/> instance to the <see cref="EscSeqReqStats"/> list.
+		/// </summary>
+		/// <param name="terminating">The terminating.</param>
+		/// <param name="numOfReq">The number of requests.</param>
+		public void Add (string terminating, int numOfReq = 1)
+		{
+			lock (EscSeqReqStats) {
+				var found = EscSeqReqStats.Find (x => x.Terminating == terminating);
+				if (found == null) {
+					EscSeqReqStats.Add (new EscSeqReqStatus (terminating, numOfReq));
+				} else if (found != null && found.NumOutstanding < found.NumRequests) {
+					found.NumOutstanding = Math.Min (found.NumOutstanding + numOfReq, found.NumRequests);
+				}
+			}
+		}
+
+		/// <summary>
+		/// Removes a <see cref="EscSeqReqStatus"/> instance from the <see cref="EscSeqReqStats"/> list.
+		/// </summary>
+		/// <param name="terminating">The terminating string.</param>
+		public void Remove (string terminating)
+		{
+			lock (EscSeqReqStats) {
+				var found = EscSeqReqStats.Find (x => x.Terminating == terminating);
+				if (found == null) {
+					return;
+				}
+				if (found != null && found.NumOutstanding == 0) {
+					EscSeqReqStats.Remove (found);
+				} else if (found != null && found.NumOutstanding > 0) {
+					found.NumOutstanding--;
+					if (found.NumOutstanding == 0) {
+						EscSeqReqStats.Remove (found);
+					}
+				}
+			}
+		}
+
+		/// <summary>
+		/// Indicates if a <see cref="EscSeqReqStatus"/> with the <paramref name="terminating"/> exist
+		/// in the <see cref="EscSeqReqStats"/> list.
+		/// </summary>
+		/// <param name="terminating"></param>
+		/// <returns><see langword="true"/> if exist, <see langword="false"/> otherwise.</returns>
+		public bool Requested (string terminating)
+		{
+			lock (EscSeqReqStats) {
+				var found = EscSeqReqStats.Find (x => x.Terminating == terminating);
+				if (found == null) {
+					return false;
+				}
+				if (found != null && found.NumOutstanding > 0) {
+					return true;
+				} else {
+					EscSeqReqStats.Remove (found);
+				}
+				return false;
+			}
+		}
+	}
+}

+ 907 - 0
Terminal.Gui/Core/EscSeqUtils/EscSeqUtils.cs

@@ -0,0 +1,907 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Management;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Provides a platform-independent API for managing ANSI escape sequence codes.
+	/// </summary>
+	public static class EscSeqUtils {
+		/// <summary>
+		/// Represents the escape key.
+		/// </summary>
+		public static readonly char KeyEsc = (char)Key.Esc;
+		/// <summary>
+		/// Represents the CSI (Control Sequence Introducer).
+		/// </summary>
+		public static readonly string KeyCSI = $"{KeyEsc}[";
+		/// <summary>
+		/// Represents the CSI for enable any mouse event tracking.
+		/// </summary>
+		public static readonly string CSI_EnableAnyEventMouse = KeyCSI + "?1003h";
+		/// <summary>
+		/// Represents the CSI for enable SGR (Select Graphic Rendition).
+		/// </summary>
+		public static readonly string CSI_EnableSgrExtModeMouse = KeyCSI + "?1006h";
+		/// <summary>
+		/// Represents the CSI for enable URXVT (Unicode Extended Virtual Terminal).
+		/// </summary>
+		public static readonly string CSI_EnableUrxvtExtModeMouse = KeyCSI + "?1015h";
+		/// <summary>
+		/// Represents the CSI for disable any mouse event tracking.
+		/// </summary>
+		public static readonly string CSI_DisableAnyEventMouse = KeyCSI + "?1003l";
+		/// <summary>
+		/// Represents the CSI for disable SGR (Select Graphic Rendition).
+		/// </summary>
+		public static readonly string CSI_DisableSgrExtModeMouse = KeyCSI + "?1006l";
+		/// <summary>
+		/// Represents the CSI for disable URXVT (Unicode Extended Virtual Terminal).
+		/// </summary>
+		public static readonly string CSI_DisableUrxvtExtModeMouse = KeyCSI + "?1015l";
+
+		/// <summary>
+		/// Control sequence for enable mouse events.
+		/// </summary>
+		public static string EnableMouseEvents { get; set; } =
+			CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse;
+		/// <summary>
+		/// Control sequence for disable mouse events.
+		/// </summary>
+		public static string DisableMouseEvents { get; set; } =
+			CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse;
+
+		/// <summary>
+		/// Ensures a console key is mapped to one that works correctly with ANSI escape sequences.
+		/// </summary>
+		/// <param name="consoleKeyInfo">The <see cref="ConsoleKeyInfo"/>.</param>
+		/// <returns>The <see cref="ConsoleKeyInfo"/> modified.</returns>
+		public static ConsoleKeyInfo GetConsoleInputKey (ConsoleKeyInfo consoleKeyInfo)
+		{
+			ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo;
+			ConsoleKey key;
+			var keyChar = consoleKeyInfo.KeyChar;
+			switch ((uint)keyChar) {
+			case 0:
+				if (consoleKeyInfo.Key == (ConsoleKey)64) {    // Ctrl+Space in Windows.
+					newConsoleKeyInfo = new ConsoleKeyInfo (' ', ConsoleKey.Spacebar,
+						(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+						(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+						(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
+				}
+				break;
+			case uint n when (n >= '\u0001' && n <= '\u001a'):
+				if (consoleKeyInfo.Key == 0 && consoleKeyInfo.KeyChar == '\r') {
+					key = ConsoleKey.Enter;
+					newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar,
+						key,
+						(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+						(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+						(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
+				} else if (consoleKeyInfo.Key == 0) {
+					key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + (uint)ConsoleKey.A - 1);
+					newConsoleKeyInfo = new ConsoleKeyInfo ((char)key,
+						key,
+						(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+						(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+						true);
+				}
+				break;
+			case 127:
+				newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar, ConsoleKey.Backspace,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
+				break;
+			default:
+				newConsoleKeyInfo = consoleKeyInfo;
+				break;
+			}
+
+			return newConsoleKeyInfo;
+		}
+
+		/// <summary>
+		/// A helper to resize the <see cref="ConsoleKeyInfo"/> as needed.
+		/// </summary>
+		/// <param name="consoleKeyInfo">The <see cref="ConsoleKeyInfo"/>.</param>
+		/// <param name="cki">The <see cref="ConsoleKeyInfo"/> array to resize.</param>
+		/// <returns>The <see cref="ConsoleKeyInfo"/> resized.</returns>
+		public static ConsoleKeyInfo [] ResizeArray (ConsoleKeyInfo consoleKeyInfo, ConsoleKeyInfo [] cki)
+		{
+			Array.Resize (ref cki, cki == null ? 1 : cki.Length + 1);
+			cki [cki.Length - 1] = consoleKeyInfo;
+			return cki;
+		}
+
+		/// <summary>
+		/// Decodes a escape sequence to been processed in the appropriate manner.
+		/// </summary>
+		/// <param name="escSeqReqProc">The <see cref="EscSeqReqProc"/> which may contain a request.</param>
+		/// <param name="newConsoleKeyInfo">The <see cref="ConsoleKeyInfo"/> which may changes.</param>
+		/// <param name="key">The <see cref="ConsoleKey"/> which may changes.</param>
+		/// <param name="cki">The <see cref="ConsoleKeyInfo"/> array.</param>
+		/// <param name="mod">The <see cref="ConsoleModifiers"/> which may changes.</param>
+		/// <param name="c1Control">The control returned by the <see cref="GetC1ControlChar(char)"/> method.</param>
+		/// <param name="code">The code returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
+		/// <param name="values">The values returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
+		/// <param name="terminating">The terminating returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
+		/// <param name="isKeyMouse">Indicates if the escape sequence is a mouse key.</param>
+		/// <param name="buttonState">The <see cref="MouseFlags"/> button state.</param>
+		/// <param name="pos">The <see cref="MouseFlags"/> position.</param>
+		/// <param name="isReq">Indicates if the escape sequence is a response to a request.</param>
+		/// <param name="continuousButtonPressedHandler">The handler that will process the event.</param>
+		public static void DecodeEscSeq (EscSeqReqProc escSeqReqProc, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string [] values, out string terminating, out bool isKeyMouse, out List<MouseFlags> buttonState, out Point pos, out bool isReq, Action<MouseFlags, Point> continuousButtonPressedHandler)
+		{
+			char [] kChars = GetKeyCharArray (cki);
+			(c1Control, code, values, terminating) = GetEscapeResult (kChars);
+			isKeyMouse = false;
+			buttonState = new List<MouseFlags> () { 0 };
+			pos = default;
+			isReq = false;
+			switch (c1Control) {
+			case "ESC":
+				if (values == null && string.IsNullOrEmpty (terminating)) {
+					key = ConsoleKey.Escape;
+					newConsoleKeyInfo = new ConsoleKeyInfo (cki [0].KeyChar, key,
+						(mod & ConsoleModifiers.Shift) != 0,
+						(mod & ConsoleModifiers.Alt) != 0,
+						(mod & ConsoleModifiers.Control) != 0);
+				} else if ((uint)cki [1].KeyChar >= 1 && (uint)cki [1].KeyChar <= 26) {
+					key = (ConsoleKey)(char)(cki [1].KeyChar + (uint)ConsoleKey.A - 1);
+					newConsoleKeyInfo = new ConsoleKeyInfo (cki [1].KeyChar,
+						key,
+						false,
+						true,
+						true);
+				} else {
+					if (cki [1].KeyChar >= 97 && cki [1].KeyChar <= 122) {
+						key = (ConsoleKey)cki [1].KeyChar.ToString ().ToUpper () [0];
+					} else {
+						key = (ConsoleKey)cki [1].KeyChar;
+					}
+					newConsoleKeyInfo = new ConsoleKeyInfo ((char)key,
+						(ConsoleKey)Math.Min ((uint)key, 255),
+						false,
+						true,
+						false);
+				}
+				break;
+			case "SS3":
+				key = GetConsoleKey (terminating [0], values [0], ref mod);
+				newConsoleKeyInfo = new ConsoleKeyInfo ('\0',
+					key,
+					(mod & ConsoleModifiers.Shift) != 0,
+					(mod & ConsoleModifiers.Alt) != 0,
+					(mod & ConsoleModifiers.Control) != 0);
+				break;
+			case "CSI":
+				if (!string.IsNullOrEmpty (code) && code == "<") {
+					GetMouse (cki, out buttonState, out pos, continuousButtonPressedHandler);
+					isKeyMouse = true;
+					return;
+				} else if (escSeqReqProc != null && escSeqReqProc.Requested (terminating)) {
+					isReq = true;
+					escSeqReqProc.Remove (terminating);
+					return;
+				}
+				key = GetConsoleKey (terminating [0], values [0], ref mod);
+				if (key != 0 && values.Length > 1) {
+					mod |= GetConsoleModifiers (values [1]);
+				}
+				newConsoleKeyInfo = new ConsoleKeyInfo ('\0',
+					key,
+					(mod & ConsoleModifiers.Shift) != 0,
+					(mod & ConsoleModifiers.Alt) != 0,
+					(mod & ConsoleModifiers.Control) != 0);
+				break;
+			}
+		}
+
+		/// <summary>
+		/// Gets all the needed information about a escape sequence.
+		/// </summary>
+		/// <param name="kChar">The array with all chars.</param>
+		/// <returns>
+		/// The c1Control returned by <see cref="GetC1ControlChar(char)"/>, code, values and terminating.
+		/// </returns>
+		public static (string c1Control, string code, string [] values, string terminating) GetEscapeResult (char [] kChar)
+		{
+			if (kChar == null || kChar.Length == 0) {
+				return (null, null, null, null);
+			}
+			if (kChar [0] != '\x1b') {
+				throw new InvalidOperationException ("Invalid escape character!");
+			}
+			if (kChar.Length == 1) {
+				return ("ESC", null, null, null);
+			}
+			if (kChar.Length == 2) {
+				return ("ESC", null, null, kChar [1].ToString ());
+			}
+			string c1Control = GetC1ControlChar (kChar [1]);
+			string code = null;
+			int nSep = kChar.Count (x => x == ';') + 1;
+			string [] values = new string [nSep];
+			int valueIdx = 0;
+			string terminating = "";
+			for (int i = 2; i < kChar.Length; i++) {
+				var c = kChar [i];
+				if (char.IsDigit (c)) {
+					values [valueIdx] += c.ToString ();
+				} else if (c == ';') {
+					valueIdx++;
+				} else if (valueIdx == nSep - 1 || i == kChar.Length - 1) {
+					terminating += c.ToString ();
+				} else {
+					code += c.ToString ();
+				}
+			}
+
+			return (c1Control, code, values, terminating);
+		}
+
+		/// <summary>
+		/// Gets the c1Control used in the called escape sequence.
+		/// </summary>
+		/// <param name="c">The char used.</param>
+		/// <returns>The c1Control.</returns>
+		public static string GetC1ControlChar (char c)
+		{
+			// These control characters are used in the vtXXX emulation.
+			switch (c) {
+			case 'D':
+				return "IND"; // Index
+			case 'E':
+				return "NEL"; // Next Line
+			case 'H':
+				return "HTS"; // Tab Set
+			case 'M':
+				return "RI"; // Reverse Index
+			case 'N':
+				return "SS2"; // Single Shift Select of G2 Character Set: affects next character only
+			case 'O':
+				return "SS3"; // Single Shift Select of G3 Character Set: affects next character only
+			case 'P':
+				return "DCS"; // Device Control String
+			case 'V':
+				return "SPA"; // Start of Guarded Area
+			case 'W':
+				return "EPA"; // End of Guarded Area
+			case 'X':
+				return "SOS"; // Start of String
+			case 'Z':
+				return "DECID"; // Return Terminal ID Obsolete form of CSI c (DA)
+			case '[':
+				return "CSI"; // Control Sequence Introducer
+			case '\\':
+				return "ST"; // String Terminator
+			case ']':
+				return "OSC"; // Operating System Command
+			case '^':
+				return "PM"; // Privacy Message
+			case '_':
+				return "APC"; // Application Program Command
+			default:
+				return ""; // Not supported
+			}
+		}
+
+		/// <summary>
+		/// Gets the <see cref="ConsoleModifiers"/> from the value.
+		/// </summary>
+		/// <param name="value">The value.</param>
+		/// <returns>The <see cref="ConsoleModifiers"/> or zero.</returns>
+		public static ConsoleModifiers GetConsoleModifiers (string value)
+		{
+			switch (value) {
+			case "2":
+				return ConsoleModifiers.Shift;
+			case "3":
+				return ConsoleModifiers.Alt;
+			case "4":
+				return ConsoleModifiers.Shift | ConsoleModifiers.Alt;
+			case "5":
+				return ConsoleModifiers.Control;
+			case "6":
+				return ConsoleModifiers.Shift | ConsoleModifiers.Control;
+			case "7":
+				return ConsoleModifiers.Alt | ConsoleModifiers.Control;
+			case "8":
+				return ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control;
+			default:
+				return 0;
+			}
+		}
+
+		/// <summary>
+		/// Gets the <see cref="ConsoleKey"/> depending on terminating and value.
+		/// </summary>
+		/// <param name="terminating">The terminating.</param>
+		/// <param name="value">The value.</param>
+		/// <param name="mod">The <see cref="ConsoleModifiers"/> which may changes.</param>
+		/// <returns>The <see cref="ConsoleKey"/> and probably the <see cref="ConsoleModifiers"/>.</returns>
+		public static ConsoleKey GetConsoleKey (char terminating, string value, ref ConsoleModifiers mod)
+		{
+			ConsoleKey key;
+			switch (terminating) {
+			case 'A':
+				key = ConsoleKey.UpArrow;
+				break;
+			case 'B':
+				key = ConsoleKey.DownArrow;
+				break;
+			case 'C':
+				key = ConsoleKey.RightArrow;
+				break;
+			case 'D':
+				key = ConsoleKey.LeftArrow;
+				break;
+			case 'F':
+				key = ConsoleKey.End;
+				break;
+			case 'H':
+				key = ConsoleKey.Home;
+				break;
+			case 'P':
+				key = ConsoleKey.F1;
+				break;
+			case 'Q':
+				key = ConsoleKey.F2;
+				break;
+			case 'R':
+				key = ConsoleKey.F3;
+				break;
+			case 'S':
+				key = ConsoleKey.F4;
+				break;
+			case 'Z':
+				key = ConsoleKey.Tab;
+				mod |= ConsoleModifiers.Shift;
+				break;
+			case '~':
+				switch (value) {
+				case "2":
+					key = ConsoleKey.Insert;
+					break;
+				case "3":
+					key = ConsoleKey.Delete;
+					break;
+				case "5":
+					key = ConsoleKey.PageUp;
+					break;
+				case "6":
+					key = ConsoleKey.PageDown;
+					break;
+				case "15":
+					key = ConsoleKey.F5;
+					break;
+				case "17":
+					key = ConsoleKey.F6;
+					break;
+				case "18":
+					key = ConsoleKey.F7;
+					break;
+				case "19":
+					key = ConsoleKey.F8;
+					break;
+				case "20":
+					key = ConsoleKey.F9;
+					break;
+				case "21":
+					key = ConsoleKey.F10;
+					break;
+				case "23":
+					key = ConsoleKey.F11;
+					break;
+				case "24":
+					key = ConsoleKey.F12;
+					break;
+				default:
+					key = 0;
+					break;
+				}
+				break;
+			default:
+				key = 0;
+				break;
+			}
+
+			return key;
+		}
+
+		/// <summary>
+		/// A helper to get only the <see cref="ConsoleKeyInfo.KeyChar"/> from the <see cref="ConsoleKeyInfo"/> array.
+		/// </summary>
+		/// <param name="cki"></param>
+		/// <returns>The char array of the escape sequence.</returns>
+		public static char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
+		{
+			char [] kChar = new char [] { };
+			var length = 0;
+			foreach (var kc in cki) {
+				length++;
+				Array.Resize (ref kChar, length);
+				kChar [length - 1] = kc.KeyChar;
+			}
+
+			return kChar;
+		}
+
+		private static MouseFlags? lastMouseButtonPressed;
+		//private static MouseFlags? lastMouseButtonReleased;
+		private static bool isButtonPressed;
+		//private static bool isButtonReleased;
+		private static bool isButtonClicked;
+		private static bool isButtonDoubleClicked;
+		private static bool isButtonTripleClicked;
+		private static Point point;
+
+		/// <summary>
+		/// Gets the <see cref="MouseFlags"/> mouse button flags and the position.
+		/// </summary>
+		/// <param name="cki">The <see cref="ConsoleKeyInfo"/> array.</param>
+		/// <param name="mouseFlags">The mouse button flags.</param>
+		/// <param name="pos">The mouse position.</param>
+		/// <param name="continuousButtonPressedHandler">The handler that will process the event.</param>
+		public static void GetMouse (ConsoleKeyInfo [] cki, out List<MouseFlags> mouseFlags, out Point pos, Action<MouseFlags, Point> continuousButtonPressedHandler)
+		{
+			MouseFlags buttonState = 0;
+			pos = new Point ();
+			int buttonCode = 0;
+			bool foundButtonCode = false;
+			int foundPoint = 0;
+			string value = "";
+			var kChar = GetKeyCharArray (cki);
+			//System.Diagnostics.Debug.WriteLine ($"kChar: {new string (kChar)}");
+			for (int i = 0; i < kChar.Length; i++) {
+				var c = kChar [i];
+				if (c == '<') {
+					foundButtonCode = true;
+				} else if (foundButtonCode && c != ';') {
+					value += c.ToString ();
+				} else if (c == ';') {
+					if (foundButtonCode) {
+						foundButtonCode = false;
+						buttonCode = int.Parse (value);
+					}
+					if (foundPoint == 1) {
+						pos.X = int.Parse (value) - 1;
+					}
+					value = "";
+					foundPoint++;
+				} else if (foundPoint > 0 && c != 'm' && c != 'M') {
+					value += c.ToString ();
+				} else if (c == 'm' || c == 'M') {
+					//pos.Y = int.Parse (value) + Console.WindowTop - 1;
+					pos.Y = int.Parse (value) - 1;
+
+					switch (buttonCode) {
+					case 0:
+					case 8:
+					case 16:
+					case 24:
+					case 32:
+					case 36:
+					case 40:
+					case 48:
+					case 56:
+						buttonState = c == 'M' ? MouseFlags.Button1Pressed
+							: MouseFlags.Button1Released;
+						break;
+					case 1:
+					case 9:
+					case 17:
+					case 25:
+					case 33:
+					case 37:
+					case 41:
+					case 45:
+					case 49:
+					case 53:
+					case 57:
+					case 61:
+						buttonState = c == 'M' ? MouseFlags.Button2Pressed
+							: MouseFlags.Button2Released;
+						break;
+					case 2:
+					case 10:
+					case 14:
+					case 18:
+					case 22:
+					case 26:
+					case 30:
+					case 34:
+					case 42:
+					case 46:
+					case 50:
+					case 54:
+					case 58:
+					case 62:
+						buttonState = c == 'M' ? MouseFlags.Button3Pressed
+							: MouseFlags.Button3Released;
+						break;
+					case 35:
+					//// Needed for Windows OS
+					//if (isButtonPressed && c == 'm'
+					//	&& (lastMouseEvent.ButtonState == MouseFlags.Button1Pressed
+					//	|| lastMouseEvent.ButtonState == MouseFlags.Button2Pressed
+					//	|| lastMouseEvent.ButtonState == MouseFlags.Button3Pressed)) {
+
+					//	switch (lastMouseEvent.ButtonState) {
+					//	case MouseFlags.Button1Pressed:
+					//		buttonState = MouseFlags.Button1Released;
+					//		break;
+					//	case MouseFlags.Button2Pressed:
+					//		buttonState = MouseFlags.Button2Released;
+					//		break;
+					//	case MouseFlags.Button3Pressed:
+					//		buttonState = MouseFlags.Button3Released;
+					//		break;
+					//	}
+					//} else {
+					//	buttonState = MouseFlags.ReportMousePosition;
+					//}
+					//break;
+					case 39:
+					case 43:
+					case 47:
+					case 51:
+					case 55:
+					case 59:
+					case 63:
+						buttonState = MouseFlags.ReportMousePosition;
+						break;
+					case 64:
+						buttonState = MouseFlags.WheeledUp;
+						break;
+					case 65:
+						buttonState = MouseFlags.WheeledDown;
+						break;
+					case 68:
+					case 72:
+					case 80:
+						buttonState = MouseFlags.WheeledLeft;       // Shift/Ctrl+WheeledUp
+						break;
+					case 69:
+					case 73:
+					case 81:
+						buttonState = MouseFlags.WheeledRight;      // Shift/Ctrl+WheeledDown
+						break;
+					}
+					// Modifiers.
+					switch (buttonCode) {
+					case 8:
+					case 9:
+					case 10:
+					case 43:
+						buttonState |= MouseFlags.ButtonAlt;
+						break;
+					case 14:
+					case 47:
+						buttonState |= MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
+						break;
+					case 16:
+					case 17:
+					case 18:
+					case 51:
+						buttonState |= MouseFlags.ButtonCtrl;
+						break;
+					case 22:
+					case 55:
+						buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
+						break;
+					case 24:
+					case 25:
+					case 26:
+					case 59:
+						buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
+						break;
+					case 30:
+					case 63:
+						buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
+						break;
+					case 32:
+					case 33:
+					case 34:
+						buttonState |= MouseFlags.ReportMousePosition;
+						break;
+					case 36:
+					case 37:
+						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonShift;
+						break;
+					case 39:
+					case 68:
+					case 69:
+						buttonState |= MouseFlags.ButtonShift;
+						break;
+					case 40:
+					case 41:
+					case 42:
+						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt;
+						break;
+					case 45:
+					case 46:
+						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
+						break;
+					case 48:
+					case 49:
+					case 50:
+						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl;
+						break;
+					case 53:
+					case 54:
+						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
+						break;
+					case 56:
+					case 57:
+					case 58:
+						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
+						break;
+					case 61:
+					case 62:
+						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
+						break;
+					}
+				}
+			}
+
+			mouseFlags = new List<MouseFlags> () { MouseFlags.AllEvents };
+
+			if (lastMouseButtonPressed != null && !isButtonPressed && !buttonState.HasFlag (MouseFlags.ReportMousePosition)
+				&& !buttonState.HasFlag (MouseFlags.Button1Released)
+				&& !buttonState.HasFlag (MouseFlags.Button2Released)
+				&& !buttonState.HasFlag (MouseFlags.Button3Released)
+				&& !buttonState.HasFlag (MouseFlags.Button4Released)) {
+
+				lastMouseButtonPressed = null;
+				isButtonPressed = false;
+			}
+
+			if (!isButtonClicked && !isButtonDoubleClicked && ((buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
+				  buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed) && lastMouseButtonPressed == null) ||
+				  isButtonPressed && lastMouseButtonPressed != null && buttonState.HasFlag (MouseFlags.ReportMousePosition)) {
+
+				mouseFlags [0] = buttonState;
+				lastMouseButtonPressed = buttonState;
+				isButtonPressed = true;
+
+				if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) {
+					point = new Point () {
+						X = pos.X,
+						Y = pos.Y
+					};
+
+					Application.MainLoop.AddIdle (() => {
+						Task.Run (async () => await ProcessContinuousButtonPressedAsync (buttonState, continuousButtonPressedHandler));
+						return false;
+					});
+				} else if (mouseFlags [0] == MouseFlags.ReportMousePosition) {
+					isButtonPressed = false;
+				}
+
+			} else if (isButtonDoubleClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
+				buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) {
+
+				mouseFlags [0] = GetButtonTripleClicked (buttonState);
+				isButtonDoubleClicked = false;
+				isButtonTripleClicked = true;
+
+			} else if (isButtonClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
+				buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) {
+
+				mouseFlags [0] = GetButtonDoubleClicked (buttonState);
+				isButtonClicked = false;
+				isButtonDoubleClicked = true;
+				Application.MainLoop.AddIdle (() => {
+					Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
+					return false;
+				});
+
+			}
+			//else if (isButtonReleased && !isButtonClicked && buttonState == MouseFlags.ReportMousePosition) {
+			//	mouseFlag [0] = GetButtonClicked ((MouseFlags)lastMouseButtonReleased);
+			//	lastMouseButtonReleased = null;
+			//	isButtonReleased = false;
+			//	isButtonClicked = true;
+			//	Application.MainLoop.AddIdle (() => {
+			//		Task.Run (async () => await ProcessButtonClickedAsync ());
+			//		return false;
+			//	});
+
+			//} 
+			else if (!isButtonClicked && !isButtonDoubleClicked && (buttonState == MouseFlags.Button1Released || buttonState == MouseFlags.Button2Released ||
+				  buttonState == MouseFlags.Button3Released || buttonState == MouseFlags.Button4Released)) {
+
+				mouseFlags [0] = buttonState;
+				isButtonPressed = false;
+
+				if (isButtonTripleClicked) {
+					isButtonTripleClicked = false;
+				} else if (pos.X == point.X && pos.Y == point.Y) {
+					mouseFlags.Add (GetButtonClicked (buttonState));
+					isButtonClicked = true;
+					Application.MainLoop.AddIdle (() => {
+						Task.Run (async () => await ProcessButtonClickedAsync ());
+						return false;
+					});
+				}
+
+				point = pos;
+
+				//if ((lastMouseButtonPressed & MouseFlags.ReportMousePosition) == 0) {
+				//	lastMouseButtonReleased = buttonState;
+				//	isButtonPressed = false;
+				//	isButtonReleased = true;
+				//} else {
+				//	lastMouseButtonPressed = null;
+				//	isButtonPressed = false;
+				//}
+
+			} else if (buttonState == MouseFlags.WheeledUp) {
+
+				mouseFlags [0] = MouseFlags.WheeledUp;
+
+			} else if (buttonState == MouseFlags.WheeledDown) {
+
+				mouseFlags [0] = MouseFlags.WheeledDown;
+
+			} else if (buttonState == MouseFlags.WheeledLeft) {
+
+				mouseFlags [0] = MouseFlags.WheeledLeft;
+
+			} else if (buttonState == MouseFlags.WheeledRight) {
+
+				mouseFlags [0] = MouseFlags.WheeledRight;
+
+			} else if (buttonState == MouseFlags.ReportMousePosition) {
+				mouseFlags [0] = MouseFlags.ReportMousePosition;
+
+			} else {
+				mouseFlags [0] = buttonState;
+				//foreach (var flag in buttonState.GetUniqueFlags()) {
+				//	mouseFlag [0] |= flag;
+				//}
+			}
+
+			mouseFlags [0] = SetControlKeyStates (buttonState, mouseFlags [0]);
+			//buttonState = mouseFlags;
+
+			//System.Diagnostics.Debug.WriteLine ($"buttonState: {buttonState} X: {pos.X} Y: {pos.Y}");
+			//foreach (var mf in mouseFlags) {
+			//	System.Diagnostics.Debug.WriteLine ($"mouseFlags: {mf} X: {pos.X} Y: {pos.Y}");
+			//}
+		}
+
+		private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action<MouseFlags, Point> continuousButtonPressedHandler)
+		{
+			while (isButtonPressed) {
+				await Task.Delay (100);
+				//var me = new MouseEvent () {
+				//	X = point.X,
+				//	Y = point.Y,
+				//	Flags = mouseFlag
+				//};
+
+				var view = Application.WantContinuousButtonPressedView;
+				if (view == null)
+					break;
+				if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
+					Application.MainLoop.Invoke (() => continuousButtonPressedHandler (mouseFlag, point));
+				}
+			}
+		}
+
+		private static async Task ProcessButtonClickedAsync ()
+		{
+			await Task.Delay (300);
+			isButtonClicked = false;
+		}
+
+		private static async Task ProcessButtonDoubleClickedAsync ()
+		{
+			await Task.Delay (300);
+			isButtonDoubleClicked = false;
+		}
+
+		private static MouseFlags GetButtonClicked (MouseFlags mouseFlag)
+		{
+			MouseFlags mf = default;
+			switch (mouseFlag) {
+			case MouseFlags.Button1Released:
+				mf = MouseFlags.Button1Clicked;
+				break;
+
+			case MouseFlags.Button2Released:
+				mf = MouseFlags.Button2Clicked;
+				break;
+
+			case MouseFlags.Button3Released:
+				mf = MouseFlags.Button3Clicked;
+				break;
+			}
+			return mf;
+		}
+
+		private static MouseFlags GetButtonDoubleClicked (MouseFlags mouseFlag)
+		{
+			MouseFlags mf = default;
+			switch (mouseFlag) {
+			case MouseFlags.Button1Pressed:
+				mf = MouseFlags.Button1DoubleClicked;
+				break;
+
+			case MouseFlags.Button2Pressed:
+				mf = MouseFlags.Button2DoubleClicked;
+				break;
+
+			case MouseFlags.Button3Pressed:
+				mf = MouseFlags.Button3DoubleClicked;
+				break;
+			}
+			return mf;
+		}
+
+		private static MouseFlags GetButtonTripleClicked (MouseFlags mouseFlag)
+		{
+			MouseFlags mf = default;
+			switch (mouseFlag) {
+			case MouseFlags.Button1Pressed:
+				mf = MouseFlags.Button1TripleClicked;
+				break;
+
+			case MouseFlags.Button2Pressed:
+				mf = MouseFlags.Button2TripleClicked;
+				break;
+
+			case MouseFlags.Button3Pressed:
+				mf = MouseFlags.Button3TripleClicked;
+				break;
+			}
+			return mf;
+		}
+
+		private static MouseFlags SetControlKeyStates (MouseFlags buttonState, MouseFlags mouseFlag)
+		{
+			if ((buttonState & MouseFlags.ButtonCtrl) != 0 && (mouseFlag & MouseFlags.ButtonCtrl) == 0)
+				mouseFlag |= MouseFlags.ButtonCtrl;
+
+			if ((buttonState & MouseFlags.ButtonShift) != 0 && (mouseFlag & MouseFlags.ButtonShift) == 0)
+				mouseFlag |= MouseFlags.ButtonShift;
+
+			if ((buttonState & MouseFlags.ButtonAlt) != 0 && (mouseFlag & MouseFlags.ButtonAlt) == 0)
+				mouseFlag |= MouseFlags.ButtonAlt;
+			return mouseFlag;
+		}
+
+		/// <summary>
+		/// Get the terminal that holds the console driver.
+		/// </summary>
+		/// <param name="process">The process.</param>
+		/// <returns>If supported the executable console process, null otherwise.</returns>
+		public static Process GetParentProcess (Process process)
+		{
+			if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+				return null;
+			}
+
+			string query = "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = " + process.Id;
+			using (ManagementObjectSearcher mos = new ManagementObjectSearcher (query)) {
+				foreach (ManagementObject mo in mos.Get ()) {
+					if (mo ["ParentProcessId"] != null) {
+						try {
+							var id = Convert.ToInt32 (mo ["ParentProcessId"]);
+							return Process.GetProcessById (id);
+						} catch {
+						}
+					}
+				}
+			}
+			return null;
+		}
+	}
+}

+ 1 - 1
Terminal.Gui/Core/Event.cs

@@ -749,7 +749,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		WheeledUp = unchecked((int)0x10000000),
 		WheeledUp = unchecked((int)0x10000000),
 		/// <summary>
 		/// <summary>
-		/// Vertical button wheeled up.
+		/// Vertical button wheeled down.
 		/// </summary>
 		/// </summary>
 		WheeledDown = unchecked((int)0x20000000),
 		WheeledDown = unchecked((int)0x20000000),
 		/// <summary>
 		/// <summary>

+ 605 - 0
Terminal.Gui/Core/Graphs/LineCanvas.cs

@@ -0,0 +1,605 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Terminal.Gui.Graphs {
+
+
+	/// <summary>
+	/// Facilitates box drawing and line intersection detection
+	/// and rendering.  Does not support diagonal lines.
+	/// </summary>
+	public class LineCanvas {
+
+
+		private List<StraightLine> lines = new List<StraightLine> ();
+
+		Dictionary<IntersectionRuneType, IntersectionRuneResolver> runeResolvers = new Dictionary<IntersectionRuneType, IntersectionRuneResolver> {
+			{IntersectionRuneType.ULCorner,new ULIntersectionRuneResolver()},
+			{IntersectionRuneType.URCorner,new URIntersectionRuneResolver()},
+			{IntersectionRuneType.LLCorner,new LLIntersectionRuneResolver()},
+			{IntersectionRuneType.LRCorner,new LRIntersectionRuneResolver()},
+
+			{IntersectionRuneType.TopTee,new TopTeeIntersectionRuneResolver()},
+			{IntersectionRuneType.LeftTee,new LeftTeeIntersectionRuneResolver()},
+			{IntersectionRuneType.RightTee,new RightTeeIntersectionRuneResolver()},
+			{IntersectionRuneType.BottomTee,new BottomTeeIntersectionRuneResolver()},
+
+
+			{IntersectionRuneType.Crosshair,new CrosshairIntersectionRuneResolver()},
+			// TODO: Add other resolvers
+		};
+
+		/// <summary>
+		/// Add a new line to the canvas starting at <paramref name="from"/>.
+		/// Use positive <paramref name="length"/> for Right and negative for Left
+		/// when <see cref="Orientation"/> is <see cref="Orientation.Horizontal"/>.
+		/// Use positive <paramref name="length"/> for Down and negative for Up
+		/// when <see cref="Orientation"/> is <see cref="Orientation.Vertical"/>.
+		/// </summary>
+		/// <param name="from">Starting point.</param>
+		/// <param name="length">Length of line.  0 for a dot.  
+		/// Positive for Down/Right.  Negative for Up/Left.</param>
+		/// <param name="orientation">Direction of the line.</param>
+		/// <param name="style">The style of line to use</param>
+		public void AddLine (Point from, int length, Orientation orientation, BorderStyle style)
+		{
+			lines.Add (new StraightLine (from, length, orientation, style));
+		}
+		/// <summary>
+		/// Evaluate all currently defined lines that lie within 
+		/// <paramref name="inArea"/> and map that
+		/// shows what characters (if any) should be rendered at each
+		/// point so that all lines connect up correctly with appropriate
+		/// intersection symbols.
+		/// <returns></returns>
+		/// </summary>
+		/// <param name="inArea"></param>
+		/// <returns>Mapping of all the points within <paramref name="inArea"/> to
+		/// line or intersection runes which should be drawn there.</returns>
+		public Dictionary<Point,Rune> GenerateImage (Rect inArea)
+		{
+			var map = new Dictionary<Point,Rune>();
+
+			// walk through each pixel of the bitmap
+			for (int y = inArea.Y; y < inArea.Height; y++) {
+				for (int x = inArea.X; x < inArea.Width; x++) {
+
+					var intersects = lines
+						.Select (l => l.Intersects (x, y))
+						.Where (i => i != null)
+						.ToArray ();
+
+					// TODO: use Driver and LineStyle to map
+					var rune = GetRuneForIntersects (Application.Driver, intersects);
+
+					if(rune != null)
+					{
+						map.Add(new Point(x,y),rune.Value);
+					}
+				}
+			}
+
+			return map;
+		}
+
+		private abstract class IntersectionRuneResolver {
+			readonly Rune round;
+			readonly Rune doubleH;
+			readonly Rune doubleV;
+			readonly Rune doubleBoth;
+			readonly Rune normal;
+
+			public IntersectionRuneResolver (Rune round, Rune doubleH, Rune doubleV, Rune doubleBoth, Rune normal)
+			{
+				this.round = round;
+				this.doubleH = doubleH;
+				this.doubleV = doubleV;
+				this.doubleBoth = doubleBoth;
+				this.normal = normal;
+			}
+
+			public Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects)
+			{
+				var useRounded = intersects.Any (i => i.Line.Style == BorderStyle.Rounded && i.Line.Length != 0);
+
+				bool doubleHorizontal = intersects.Any (l => l.Line.Orientation == Orientation.Horizontal && l.Line.Style == BorderStyle.Double);
+				bool doubleVertical = intersects.Any (l => l.Line.Orientation == Orientation.Vertical && l.Line.Style == BorderStyle.Double);
+
+
+				if (doubleHorizontal) {
+					return doubleVertical ? doubleBoth : doubleH;
+				}
+
+				if (doubleVertical) {
+					return doubleV;
+				}
+
+				return useRounded ? round : normal;
+			}
+		}
+
+		private class ULIntersectionRuneResolver : IntersectionRuneResolver {
+			public ULIntersectionRuneResolver () :
+				base ('╭', '╒', '╓', '╔', '┌')
+			{
+
+			}
+		}
+		private class URIntersectionRuneResolver : IntersectionRuneResolver {
+
+			public URIntersectionRuneResolver () :
+				base ('╮', '╕', '╖', '╗', '┐')
+			{
+
+			}
+		}
+		private class LLIntersectionRuneResolver : IntersectionRuneResolver {
+
+			public LLIntersectionRuneResolver () :
+				base ('╰', '╘', '╙', '╚', '└')
+			{
+
+			}
+		}
+		private class LRIntersectionRuneResolver : IntersectionRuneResolver {
+			public LRIntersectionRuneResolver () :
+				base ('╯', '╛', '╜', '╝', '┘')
+			{
+
+			}
+		}
+
+		private class TopTeeIntersectionRuneResolver : IntersectionRuneResolver {
+			public TopTeeIntersectionRuneResolver () :
+				base ('┬', '╤', '╥', '╦', '┬')
+			{
+
+			}
+		}
+		private class LeftTeeIntersectionRuneResolver : IntersectionRuneResolver {
+			public LeftTeeIntersectionRuneResolver () :
+				base ('├', '╞', '╟', '╠', '├')
+			{
+
+			}
+		}
+		private class RightTeeIntersectionRuneResolver : IntersectionRuneResolver {
+			public RightTeeIntersectionRuneResolver () :
+				base ('┤', '╡', '╢', '╣', '┤')
+			{
+
+			}
+		}
+		private class BottomTeeIntersectionRuneResolver : IntersectionRuneResolver {
+			public BottomTeeIntersectionRuneResolver () :
+				base ('┴', '╧', '╨', '╩', '┴')
+			{
+
+			}
+		}
+		private class CrosshairIntersectionRuneResolver : IntersectionRuneResolver {
+			public CrosshairIntersectionRuneResolver () :
+				base ('┼', '╪', '╫', '╬', '┼')
+			{
+
+			}
+		}
+
+		private Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects)
+		{
+			if (!intersects.Any ())
+				return null;
+
+			var runeType = GetRuneTypeForIntersects (intersects);
+
+			if (runeResolvers.ContainsKey (runeType)) {
+				return runeResolvers [runeType].GetRuneForIntersects (driver, intersects);
+			}
+
+			// TODO: Remove these two once we have all of the below ported to IntersectionRuneResolvers
+			var useDouble = intersects.Any (i => i.Line.Style == BorderStyle.Double && i.Line.Length != 0);
+			var useRounded = intersects.Any (i => i.Line.Style == BorderStyle.Rounded && i.Line.Length != 0);
+
+			// TODO: maybe make these resolvers to for simplicity?
+			// or for dotted lines later on or that kind of thing?
+			switch (runeType) {
+			case IntersectionRuneType.None:
+				return null;
+			case IntersectionRuneType.Dot:
+				return (Rune)'.';
+			case IntersectionRuneType.HLine:
+				return useDouble ? driver.HDLine : driver.HLine;
+			case IntersectionRuneType.VLine:
+				return useDouble ? driver.VDLine : driver.VLine;
+			default: throw new Exception ("Could not find resolver or switch case for " + nameof (runeType) + ":" + runeType);
+			}
+		}
+
+
+		private IntersectionRuneType GetRuneTypeForIntersects (IntersectionDefinition [] intersects)
+		{
+			if (intersects.All (i => i.Line.Length == 0)) {
+				return IntersectionRuneType.Dot;
+			}
+
+			// ignore dots
+			intersects = intersects.Where (i => i.Type != IntersectionType.Dot).ToArray ();
+
+			var set = new HashSet<IntersectionType> (intersects.Select (i => i.Type));
+
+			#region Crosshair Conditions
+			if (Has (set,
+				IntersectionType.PassOverHorizontal,
+				IntersectionType.PassOverVertical
+				)) {
+				return IntersectionRuneType.Crosshair;
+			}
+
+			if (Has (set,
+				IntersectionType.PassOverVertical,
+				IntersectionType.StartLeft,
+				IntersectionType.StartRight
+				)) {
+				return IntersectionRuneType.Crosshair;
+			}
+
+			if (Has (set,
+				IntersectionType.PassOverHorizontal,
+				IntersectionType.StartUp,
+				IntersectionType.StartDown
+				)) {
+				return IntersectionRuneType.Crosshair;
+			}
+
+
+			if (Has (set,
+				IntersectionType.StartLeft,
+				IntersectionType.StartRight,
+				IntersectionType.StartUp,
+				IntersectionType.StartDown)) {
+				return IntersectionRuneType.Crosshair;
+			}
+			#endregion
+
+
+			#region Corner Conditions
+			if (Exactly (set,
+				IntersectionType.StartRight,
+				IntersectionType.StartDown)) {
+				return IntersectionRuneType.ULCorner;
+			}
+
+			if (Exactly (set,
+				IntersectionType.StartLeft,
+				IntersectionType.StartDown)) {
+				return IntersectionRuneType.URCorner;
+			}
+
+			if (Exactly (set,
+				IntersectionType.StartUp,
+				IntersectionType.StartLeft)) {
+				return IntersectionRuneType.LRCorner;
+			}
+
+			if (Exactly (set,
+				IntersectionType.StartUp,
+				IntersectionType.StartRight)) {
+				return IntersectionRuneType.LLCorner;
+			}
+			#endregion Corner Conditions
+
+			#region T Conditions
+			if (Has (set,
+				IntersectionType.PassOverHorizontal,
+				IntersectionType.StartDown)) {
+				return IntersectionRuneType.TopTee;
+			}
+			if (Has (set,
+				IntersectionType.StartRight,
+				IntersectionType.StartLeft,
+				IntersectionType.StartDown)) {
+				return IntersectionRuneType.TopTee;
+			}
+
+			if (Has (set,
+				IntersectionType.PassOverHorizontal,
+				IntersectionType.StartUp)) {
+				return IntersectionRuneType.BottomTee;
+			}
+			if (Has (set,
+				IntersectionType.StartRight,
+				IntersectionType.StartLeft,
+				IntersectionType.StartUp)) {
+				return IntersectionRuneType.BottomTee;
+			}
+
+
+			if (Has (set,
+				IntersectionType.PassOverVertical,
+				IntersectionType.StartRight)) {
+				return IntersectionRuneType.LeftTee;
+			}
+			if (Has (set,
+				IntersectionType.StartRight,
+				IntersectionType.StartDown,
+				IntersectionType.StartUp)) {
+				return IntersectionRuneType.LeftTee;
+			}
+
+
+			if (Has (set,
+				IntersectionType.PassOverVertical,
+				IntersectionType.StartLeft)) {
+				return IntersectionRuneType.RightTee;
+			}
+			if (Has (set,
+				IntersectionType.StartLeft,
+				IntersectionType.StartDown,
+				IntersectionType.StartUp)) {
+				return IntersectionRuneType.RightTee;
+			}
+			#endregion
+
+			if (All (intersects, Orientation.Horizontal)) {
+				return IntersectionRuneType.HLine;
+			}
+
+			if (All (intersects, Orientation.Vertical)) {
+				return IntersectionRuneType.VLine;
+			}
+
+			return IntersectionRuneType.Dot;
+		}
+
+		private bool All (IntersectionDefinition [] intersects, Orientation orientation)
+		{
+			return intersects.All (i => i.Line.Orientation == orientation);
+		}
+
+		/// <summary>
+		/// Returns true if the <paramref name="intersects"/> collection has all the <paramref name="types"/>
+		/// specified (i.e. AND).
+		/// </summary>
+		/// <param name="intersects"></param>
+		/// <param name="types"></param>
+		/// <returns></returns>
+		private bool Has (HashSet<IntersectionType> intersects, params IntersectionType [] types)
+		{
+			return types.All (t => intersects.Contains (t));
+		}
+
+		/// <summary>
+		/// Returns true if all requested <paramref name="types"/> appear in <paramref name="intersects"/>
+		/// and there are no additional <see cref="IntersectionRuneType"/>
+		/// </summary>
+		/// <param name="intersects"></param>
+		/// <param name="types"></param>
+		/// <returns></returns>
+		private bool Exactly (HashSet<IntersectionType> intersects, params IntersectionType [] types)
+		{
+			return intersects.SetEquals (types);
+		}
+
+		class IntersectionDefinition {
+			/// <summary>
+			/// The point at which the intersection happens
+			/// </summary>
+			public Point Point { get; }
+
+			/// <summary>
+			/// Defines how <see cref="Line"/> position relates
+			/// to <see cref="Point"/>.
+			/// </summary>
+			public IntersectionType Type { get; }
+
+			/// <summary>
+			/// The line that intersects <see cref="Point"/>
+			/// </summary>
+			public StraightLine Line { get; }
+
+			public IntersectionDefinition (Point point, IntersectionType type, StraightLine line)
+			{
+				Point = point;
+				Type = type;
+				Line = line;
+			}
+		}
+
+		/// <summary>
+		/// The type of Rune that we will use before considering
+		/// double width, curved borders etc
+		/// </summary>
+		enum IntersectionRuneType {
+			None,
+			Dot,
+			ULCorner,
+			URCorner,
+			LLCorner,
+			LRCorner,
+			TopTee,
+			BottomTee,
+			RightTee,
+			LeftTee,
+			Crosshair,
+			HLine,
+			VLine,
+		}
+
+		enum IntersectionType {
+			/// <summary>
+			/// There is no intersection
+			/// </summary>
+			None,
+
+			/// <summary>
+			///  A line passes directly over this point traveling along
+			///  the horizontal axis
+			/// </summary>
+			PassOverHorizontal,
+
+			/// <summary>
+			///  A line passes directly over this point traveling along
+			///  the vertical axis
+			/// </summary>
+			PassOverVertical,
+
+			/// <summary>
+			/// A line starts at this point and is traveling up
+			/// </summary>
+			StartUp,
+
+			/// <summary>
+			/// A line starts at this point and is traveling right
+			/// </summary>
+			StartRight,
+
+			/// <summary>
+			/// A line starts at this point and is traveling down
+			/// </summary>
+			StartDown,
+
+			/// <summary>
+			/// A line starts at this point and is traveling left
+			/// </summary>
+			StartLeft,
+
+			/// <summary>
+			/// A line exists at this point who has 0 length
+			/// </summary>
+			Dot
+		}
+
+		class StraightLine {
+			public Point Start { get; }
+			public int Length { get; }
+			public Orientation Orientation { get; }
+			public BorderStyle Style { get; }
+
+			public StraightLine (Point start, int length, Orientation orientation, BorderStyle style)
+			{
+				this.Start = start;
+				this.Length = length;
+				this.Orientation = orientation;
+				this.Style = style;
+			}
+
+			internal IntersectionDefinition Intersects (int x, int y)
+			{
+				if (IsDot ()) {
+					if (StartsAt (x, y)) {
+						return new IntersectionDefinition (Start, IntersectionType.Dot, this);
+					} else {
+						return null;
+					}
+				}
+
+				switch (Orientation) {
+				case Orientation.Horizontal: return IntersectsHorizontally (x, y);
+				case Orientation.Vertical: return IntersectsVertically (x, y);
+				default: throw new ArgumentOutOfRangeException (nameof (Orientation));
+				}
+
+			}
+
+			private IntersectionDefinition IntersectsHorizontally (int x, int y)
+			{
+				if (Start.Y != y) {
+					return null;
+				} else {
+					if (StartsAt (x, y)) {
+
+						return new IntersectionDefinition (
+							Start,
+							Length < 0 ? IntersectionType.StartLeft : IntersectionType.StartRight,
+							this
+							);
+
+					}
+
+					if (EndsAt (x, y)) {
+
+						return new IntersectionDefinition (
+							Start,
+							Length < 0 ? IntersectionType.StartRight : IntersectionType.StartLeft,
+							this
+							);
+
+					} else {
+						var xmin = Math.Min (Start.X, Start.X + Length);
+						var xmax = Math.Max (Start.X, Start.X + Length);
+
+						if (xmin < x && xmax > x) {
+							return new IntersectionDefinition (
+							new Point (x, y),
+							IntersectionType.PassOverHorizontal,
+							this
+							);
+						}
+					}
+
+					return null;
+				}
+			}
+
+			private IntersectionDefinition IntersectsVertically (int x, int y)
+			{
+				if (Start.X != x) {
+					return null;
+				} else {
+					if (StartsAt (x, y)) {
+
+						return new IntersectionDefinition (
+							Start,
+							Length < 0 ? IntersectionType.StartUp : IntersectionType.StartDown,
+							this
+							);
+
+					}
+
+					if (EndsAt (x, y)) {
+
+						return new IntersectionDefinition (
+							Start,
+							Length < 0 ? IntersectionType.StartDown : IntersectionType.StartUp,
+							this
+							);
+
+					} else {
+						var ymin = Math.Min (Start.Y, Start.Y + Length);
+						var ymax = Math.Max (Start.Y, Start.Y + Length);
+
+						if (ymin < y && ymax > y) {
+							return new IntersectionDefinition (
+							new Point (x, y),
+							IntersectionType.PassOverVertical,
+							this
+							);
+						}
+					}
+
+					return null;
+				}
+			}
+
+			private bool EndsAt (int x, int y)
+			{
+				if (Orientation == Orientation.Horizontal) {
+					return Start.X + Length == x && Start.Y == y;
+				}
+
+				return Start.X == x && Start.Y + Length == y;
+			}
+
+			private bool StartsAt (int x, int y)
+			{
+				return Start.X == x && Start.Y == y;
+			}
+
+			private bool IsDot ()
+			{
+				return Length == 0;
+			}
+		}
+	}
+}

+ 9 - 7
Terminal.Gui/Core/TextFormatter.cs

@@ -1190,7 +1190,9 @@ namespace Terminal.Gui {
 			for (int line = 0; line < linesFormated.Count; line++) {
 			for (int line = 0; line < linesFormated.Count; line++) {
 				if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height))
 				if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height))
 					continue;
 					continue;
-				if ((isVertical && line > maxBounds.Left + maxBounds.Width - bounds.X) || (!isVertical && line > maxBounds.Top + maxBounds.Height - bounds.Y))
+				if ((isVertical && line >= maxBounds.Left + maxBounds.Width)
+					|| (!isVertical && line >= maxBounds.Top + maxBounds.Height))
+
 					break;
 					break;
 
 
 				var runes = lines [line].ToRunes ();
 				var runes = lines [line].ToRunes ();
@@ -1212,11 +1214,11 @@ namespace Terminal.Gui {
 					if (isVertical) {
 					if (isVertical) {
 						var runesWidth = GetSumMaxCharWidth (Lines, line);
 						var runesWidth = GetSumMaxCharWidth (Lines, line);
 						x = bounds.Right - runesWidth;
 						x = bounds.Right - runesWidth;
-						CursorPosition = bounds.Width - runesWidth + hotKeyPos;
+						CursorPosition = bounds.Width - runesWidth + (hotKeyPos > -1 ? hotKeyPos : 0);
 					} else {
 					} else {
 						var runesWidth = GetTextWidth (ustring.Make (runes));
 						var runesWidth = GetTextWidth (ustring.Make (runes));
 						x = bounds.Right - runesWidth;
 						x = bounds.Right - runesWidth;
-						CursorPosition = bounds.Width - runesWidth + hotKeyPos;
+						CursorPosition = bounds.Width - runesWidth + (hotKeyPos > -1 ? hotKeyPos : 0);
 					}
 					}
 				} else if (textAlignment == TextAlignment.Left || textAlignment == TextAlignment.Justified) {
 				} else if (textAlignment == TextAlignment.Left || textAlignment == TextAlignment.Justified) {
 					if (isVertical) {
 					if (isVertical) {
@@ -1225,16 +1227,16 @@ namespace Terminal.Gui {
 					} else {
 					} else {
 						x = bounds.Left;
 						x = bounds.Left;
 					}
 					}
-					CursorPosition = hotKeyPos;
+					CursorPosition = hotKeyPos > -1 ? hotKeyPos : 0;
 				} else if (textAlignment == TextAlignment.Centered) {
 				} else if (textAlignment == TextAlignment.Centered) {
 					if (isVertical) {
 					if (isVertical) {
 						var runesWidth = GetSumMaxCharWidth (Lines, line);
 						var runesWidth = GetSumMaxCharWidth (Lines, line);
 						x = bounds.Left + line + ((bounds.Width - runesWidth) / 2);
 						x = bounds.Left + line + ((bounds.Width - runesWidth) / 2);
-						CursorPosition = (bounds.Width - runesWidth) / 2 + hotKeyPos;
+						CursorPosition = (bounds.Width - runesWidth) / 2 + (hotKeyPos > -1 ? hotKeyPos : 0);
 					} else {
 					} else {
 						var runesWidth = GetTextWidth (ustring.Make (runes));
 						var runesWidth = GetTextWidth (ustring.Make (runes));
 						x = bounds.Left + (bounds.Width - runesWidth) / 2;
 						x = bounds.Left + (bounds.Width - runesWidth) / 2;
-						CursorPosition = (bounds.Width - runesWidth) / 2 + hotKeyPos;
+						CursorPosition = (bounds.Width - runesWidth) / 2 + (hotKeyPos > -1 ? hotKeyPos : 0);
 					}
 					}
 				} else {
 				} else {
 					throw new ArgumentOutOfRangeException ();
 					throw new ArgumentOutOfRangeException ();
@@ -1291,7 +1293,7 @@ namespace Terminal.Gui {
 							rune = runes [idx];
 							rune = runes [idx];
 						}
 						}
 					}
 					}
-					if (idx == HotKeyPos) {
+					if (HotKeyPos > -1 && idx == HotKeyPos) {
 						if ((isVertical && textVerticalAlignment == VerticalTextAlignment.Justified) ||
 						if ((isVertical && textVerticalAlignment == VerticalTextAlignment.Justified) ||
 						(!isVertical && textAlignment == TextAlignment.Justified)) {
 						(!isVertical && textAlignment == TextAlignment.Justified)) {
 							CursorPosition = idx - start;
 							CursorPosition = idx - start;

+ 26 - 5
Terminal.Gui/Core/Toplevel.cs

@@ -613,8 +613,9 @@ namespace Terminal.Gui {
 			}
 			}
 			nx = Math.Max (x, 0);
 			nx = Math.Max (x, 0);
 			nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx;
 			nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx;
-			if (nx + (top.Border != null && top.Border.DrawMarginFrame ? 2 : 1) > top.Frame.X + top.Frame.Width) {
-				nx = Math.Max (top.Frame.Right - (top.Border.DrawMarginFrame ? 2 : 1), 0);
+			var mfLength = top.Border?.DrawMarginFrame == true ? 2 : 1;
+			if (nx + mfLength > top.Frame.X + top.Frame.Width) {
+				nx = Math.Max (top.Frame.Right - mfLength, 0);
 			}
 			}
 			//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
 			//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
 			bool m, s;
 			bool m, s;
@@ -653,8 +654,8 @@ namespace Terminal.Gui {
 			}
 			}
 			ny = Math.Min (ny, l);
 			ny = Math.Min (ny, l);
 			ny = ny + top.Frame.Height >= l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
 			ny = ny + top.Frame.Height >= l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
-			if (ny + (top.Border != null && top.Border.DrawMarginFrame ? 2 : 1) > top.Frame.Y + top.Frame.Height) {
-				ny = Math.Max (top.Frame.Bottom - (top.Border.DrawMarginFrame ? 2 : 1), 0);
+			if (ny + mfLength > top.Frame.Y + top.Frame.Height) {
+				ny = Math.Max (top.Frame.Bottom - mfLength, 0);
 			}
 			}
 			//System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
 			//System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
 
 
@@ -820,7 +821,6 @@ namespace Terminal.Gui {
 
 
 			if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) {
 			if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) {
 				Application.UngrabMouse ();
 				Application.UngrabMouse ();
-				Driver.UncookMouse ();
 				dragPosition = null;
 				dragPosition = null;
 			}
 			}
 
 
@@ -908,6 +908,12 @@ namespace Terminal.Gui {
 		{
 		{
 			if (!IsMdiContainer) {
 			if (!IsMdiContainer) {
 				base.PositionCursor ();
 				base.PositionCursor ();
+				if (Focused == null) {
+					EnsureFocus ();
+					if (Focused == null) {
+						Driver.SetCursorVisibility (CursorVisibility.Invisible);
+					}
+				}
 				return;
 				return;
 			}
 			}
 
 
@@ -920,6 +926,9 @@ namespace Terminal.Gui {
 				}
 				}
 			}
 			}
 			base.PositionCursor ();
 			base.PositionCursor ();
+			if (Focused == null) {
+				Driver.SetCursorVisibility (CursorVisibility.Invisible);
+			}
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -959,6 +968,18 @@ namespace Terminal.Gui {
 			}
 			}
 			return false;
 			return false;
 		}
 		}
+
+		///<inheritdoc/>
+		public override bool OnEnter (View view)
+		{
+			return MostFocused?.OnEnter (view) ?? base.OnEnter (view);
+		}
+
+		///<inheritdoc/>
+		public override bool OnLeave (View view)
+		{
+			return MostFocused?.OnLeave (view) ?? base.OnLeave (view);
+		}
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>

+ 99 - 31
Terminal.Gui/Core/View.cs

@@ -446,14 +446,11 @@ namespace Terminal.Gui {
 		public virtual Rect Frame {
 		public virtual Rect Frame {
 			get => frame;
 			get => frame;
 			set {
 			set {
-				if (SuperView != null) {
-					SuperView.SetNeedsDisplay (frame);
-					SuperView.SetNeedsDisplay (value);
-				}
+				var rect = GetMaxNeedDisplay (frame, value);
 				frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
 				frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
 				TextFormatter.Size = GetBoundsTextFormatterSize ();
 				TextFormatter.Size = GetBoundsTextFormatterSize ();
 				SetNeedsLayout ();
 				SetNeedsLayout ();
-				SetNeedsDisplay (frame);
+				SetNeedsDisplay (rect);
 			}
 			}
 		}
 		}
 
 
@@ -811,6 +808,7 @@ namespace Terminal.Gui {
 		{
 		{
 			var actX = x is Pos.PosAbsolute ? x.Anchor (0) : frame.X;
 			var actX = x is Pos.PosAbsolute ? x.Anchor (0) : frame.X;
 			var actY = y is Pos.PosAbsolute ? y.Anchor (0) : frame.Y;
 			var actY = y is Pos.PosAbsolute ? y.Anchor (0) : frame.Y;
+			Rect oldFrame = frame;
 
 
 			if (AutoSize) {
 			if (AutoSize) {
 				var s = GetAutoSize ();
 				var s = GetAutoSize ();
@@ -825,7 +823,21 @@ namespace Terminal.Gui {
 			}
 			}
 			TextFormatter.Size = GetBoundsTextFormatterSize ();
 			TextFormatter.Size = GetBoundsTextFormatterSize ();
 			SetNeedsLayout ();
 			SetNeedsLayout ();
-			SetNeedsDisplay ();
+			SetNeedsDisplay (GetMaxNeedDisplay (oldFrame, frame));
+		}
+
+		Rect GetMaxNeedDisplay (Rect oldFrame, Rect newFrame)
+		{
+			var rect = new Rect () {
+				X = Math.Min (oldFrame.X, newFrame.X),
+				Y = Math.Min (oldFrame.Y, newFrame.Y),
+				Width = Math.Max (oldFrame.Width, newFrame.Width),
+				Height = Math.Max (oldFrame.Height, newFrame.Height)
+			};
+			rect.Width += Math.Max (oldFrame.X - newFrame.X, 0);
+			rect.Height += Math.Max (oldFrame.Y - newFrame.Y, 0);
+
+			return rect;
 		}
 		}
 
 
 		void TextFormatter_HotKeyChanged (Key obj)
 		void TextFormatter_HotKeyChanged (Key obj)
@@ -939,6 +951,10 @@ namespace Terminal.Gui {
 				view.tabIndex = tabIndexes.IndexOf (view);
 				view.tabIndex = tabIndexes.IndexOf (view);
 				addingView = false;
 				addingView = false;
 			}
 			}
+			if (view.Enabled && !Enabled) {
+				view.oldEnabled = true;
+				view.Enabled = false;
+			}
 			SetNeedsLayout ();
 			SetNeedsLayout ();
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 			OnAdded (view);
 			OnAdded (view);
@@ -1291,14 +1307,16 @@ namespace Terminal.Gui {
 				return;
 				return;
 			}
 			}
 
 
-			if (focused?.Visible == true && focused?.Enabled == true && focused?.Frame.Width > 0 && focused.Frame.Height > 0) {
+			if (focused == null && SuperView != null) {
+				SuperView.EnsureFocus ();
+			} else if (focused?.Visible == true && focused?.Enabled == true && focused?.Frame.Width > 0 && focused.Frame.Height > 0) {
 				focused.PositionCursor ();
 				focused.PositionCursor ();
+			} else if (focused?.Visible == true && focused?.Enabled == false) {
+				focused = null;
+			} else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
+				Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
 			} else {
 			} else {
-				if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
-					Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
-				} else {
-					Move (frame.X, frame.Y);
-				}
+				Move (frame.X, frame.Y);
 			}
 			}
 		}
 		}
 
 
@@ -1428,8 +1446,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public virtual ColorScheme ColorScheme {
 		public virtual ColorScheme ColorScheme {
 			get {
 			get {
-				if (colorScheme == null)
+				if (colorScheme == null) {
 					return SuperView?.ColorScheme;
 					return SuperView?.ColorScheme;
+				}
 				return colorScheme;
 				return colorScheme;
 			}
 			}
 			set {
 			set {
@@ -1491,13 +1510,13 @@ namespace Terminal.Gui {
 			var clipRect = new Rect (Point.Empty, frame.Size);
 			var clipRect = new Rect (Point.Empty, frame.Size);
 
 
 			if (ColorScheme != null) {
 			if (ColorScheme != null) {
-				Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
+				Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
 			}
 			}
 
 
-			if (Border != null) {
+			if (!IgnoreBorderPropertyOnRedraw && Border != null) {
 				Border.DrawContent (this);
 				Border.DrawContent (this);
 			} else if (ustring.IsNullOrEmpty (TextFormatter.Text) &&
 			} else if (ustring.IsNullOrEmpty (TextFormatter.Text) &&
-				(GetType ().IsNestedPublic) && !IsOverridden (this, "Redraw") &&
+				(GetType ().IsNestedPublic && !IsOverridden (this, "Redraw") || GetType ().Name == "View") &&
 				(!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded)) {
 				(!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded)) {
 
 
 				Clear ();
 				Clear ();
@@ -1505,19 +1524,15 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			if (!ustring.IsNullOrEmpty (TextFormatter.Text)) {
 			if (!ustring.IsNullOrEmpty (TextFormatter.Text)) {
-				Clear ();
+				Rect containerBounds = GetContainerBounds ();
+				Clear (ViewToScreen (GetNeedDisplay (containerBounds)));
 				SetChildNeedsDisplay ();
 				SetChildNeedsDisplay ();
 				// Draw any Text
 				// Draw any Text
 				if (TextFormatter != null) {
 				if (TextFormatter != null) {
 					TextFormatter.NeedsFormat = true;
 					TextFormatter.NeedsFormat = true;
 				}
 				}
-				var containerBounds = SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds);
-				containerBounds.X = Math.Max (containerBounds.X, Driver.Clip.X);
-				containerBounds.Y = Math.Max (containerBounds.Y, Driver.Clip.Y);
-				containerBounds.Width = Math.Min (containerBounds.Width, Driver.Clip.Width);
-				containerBounds.Height = Math.Min (containerBounds.Height, Driver.Clip.Height);
-				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
-				    HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
+				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
+				    HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
 				    containerBounds);
 				    containerBounds);
 			}
 			}
 
 
@@ -1534,12 +1549,7 @@ namespace Terminal.Gui {
 							// Draw the subview
 							// Draw the subview
 							// Use the view's bounds (view-relative; Location will always be (0,0)
 							// Use the view's bounds (view-relative; Location will always be (0,0)
 							if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
 							if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
-								var rect = new Rect () {
-									X = Math.Min (view.Bounds.X, view.NeedDisplay.X),
-									Y = Math.Min (view.Bounds.Y, view.NeedDisplay.Y),
-									Width = Math.Max (view.Bounds.Width, view.NeedDisplay.Width),
-									Height = Math.Max (view.Bounds.Height, view.NeedDisplay.Height)
-								};
+								var rect = view.Bounds;
 								view.OnDrawContent (rect);
 								view.OnDrawContent (rect);
 								view.Redraw (rect);
 								view.Redraw (rect);
 								view.OnDrawContentComplete (rect);
 								view.OnDrawContentComplete (rect);
@@ -1558,6 +1568,38 @@ namespace Terminal.Gui {
 			ClearNeedsDisplay ();
 			ClearNeedsDisplay ();
 		}
 		}
 
 
+		Rect GetNeedDisplay (Rect containerBounds)
+		{
+			Rect rect = NeedDisplay;
+			if (!containerBounds.IsEmpty) {
+				rect.Width = Math.Min (NeedDisplay.Width, containerBounds.Width);
+				rect.Height = Math.Min (NeedDisplay.Height, containerBounds.Height);
+			}
+
+			return rect;
+		}
+
+		Rect GetContainerBounds ()
+		{
+			var containerBounds = SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds);
+			var driverClip = Driver == null ? Rect.Empty : Driver.Clip;
+			containerBounds.X = Math.Max (containerBounds.X, driverClip.X);
+			containerBounds.Y = Math.Max (containerBounds.Y, driverClip.Y);
+			var lenOffset = (driverClip.X + driverClip.Width) - (containerBounds.X + containerBounds.Width);
+			if (containerBounds.X + containerBounds.Width > driverClip.X + driverClip.Width) {
+				containerBounds.Width = Math.Max (containerBounds.Width + lenOffset, 0);
+			} else {
+				containerBounds.Width = Math.Min (containerBounds.Width, driverClip.Width);
+			}
+			lenOffset = (driverClip.Y + driverClip.Height) - (containerBounds.Y + containerBounds.Height);
+			if (containerBounds.Y + containerBounds.Height > driverClip.Y + driverClip.Height) {
+				containerBounds.Height = Math.Max (containerBounds.Height + lenOffset, 0);
+			} else {
+				containerBounds.Height = Math.Min (containerBounds.Height, driverClip.Height);
+			}
+			return containerBounds;
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Event invoked when the content area of the View is to be drawn.
 		/// Event invoked when the content area of the View is to be drawn.
 		/// </summary>
 		/// </summary>
@@ -2581,7 +2623,13 @@ namespace Terminal.Gui {
 			get => base.Enabled;
 			get => base.Enabled;
 			set {
 			set {
 				if (base.Enabled != value) {
 				if (base.Enabled != value) {
-					base.Enabled = value;
+					if (value) {
+						if (SuperView == null || SuperView?.Enabled == true) {
+							base.Enabled = value;
+						}
+					} else {
+						base.Enabled = value;
+					}
 					if (!value && HasFocus) {
 					if (!value && HasFocus) {
 						SetHasFocus (false, this);
 						SetHasFocus (false, this);
 					}
 					}
@@ -2641,6 +2689,15 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		/// <summary>
+		/// Get or sets whether the view will use <see cref="Terminal.Gui.Border"/> (if <see cref="Border"/> is set) to draw 
+		/// a border. If <see langword="false"/> (the default),
+		/// <see cref="View.Redraw(Rect)"/> will call <see cref="Border.DrawContent(View, bool)"/>
+		/// to draw the view's border. If <see langword="true"/> no border is drawn (and the view is expected to draw the border
+		/// itself).
+		/// </summary>
+		public virtual bool IgnoreBorderPropertyOnRedraw { get; set; }
+
 		/// <summary>
 		/// <summary>
 		/// Pretty prints the View
 		/// Pretty prints the View
 		/// </summary>
 		/// </summary>
@@ -3064,6 +3121,17 @@ namespace Terminal.Gui {
 			return Enabled ? ColorScheme.Normal : ColorScheme.Disabled;
 			return Enabled ? ColorScheme.Normal : ColorScheme.Disabled;
 		}
 		}
 
 
+		/// <summary>
+		/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
+		/// </summary>
+		/// <returns><see cref="Terminal.Gui.ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/>
+		/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
+		/// If it's overridden can return other values.</returns>
+		public virtual Attribute GetFocusColor ()
+		{
+			return Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 		/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 		/// </summary>
 		/// </summary>

+ 7 - 12
Terminal.Gui/Core/Window.cs

@@ -35,6 +35,9 @@ namespace Terminal.Gui {
 				if (!OnTitleChanging (title, value)) {
 				if (!OnTitleChanging (title, value)) {
 					var old = title;
 					var old = title;
 					title = value;
 					title = value;
+					if (Border != null) {
+						Border.Title = title;
+					}
 					OnTitleChanged (old, title);
 					OnTitleChanged (old, title);
 				}
 				}
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
@@ -180,10 +183,13 @@ namespace Terminal.Gui {
 				Border = new Border () {
 				Border = new Border () {
 					BorderStyle = BorderStyle.Single,
 					BorderStyle = BorderStyle.Single,
 					Padding = new Thickness (padding),
 					Padding = new Thickness (padding),
-					BorderBrush = ColorScheme.Normal.Background
+					Title = title
 				};
 				};
 			} else {
 			} else {
 				Border = border;
 				Border = border;
+				if (ustring.IsNullOrEmpty (border.Title)) {
+					border.Title = title;
+				}
 			}
 			}
 			AdjustContentView (frame);
 			AdjustContentView (frame);
 		}
 		}
@@ -275,9 +281,6 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		public override void Redraw (Rect bounds)
 		{
 		{
-			var padding = Border.GetSumThickness ();
-			var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height));
-
 			if (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded) {
 			if (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded) {
 				Driver.SetAttribute (GetNormalColor ());
 				Driver.SetAttribute (GetNormalColor ());
 				Clear ();
 				Clear ();
@@ -286,7 +289,6 @@ namespace Terminal.Gui {
 			var savedClip = contentView.ClipToBounds ();
 			var savedClip = contentView.ClipToBounds ();
 
 
 			// Redraw our contentView
 			// Redraw our contentView
-			// DONE: smartly constrict contentView.Bounds to just be what intersects with the 'bounds' we were passed
 			contentView.Redraw (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded ? contentView.Bounds : bounds);
 			contentView.Redraw (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded ? contentView.Bounds : bounds);
 			Driver.Clip = savedClip;
 			Driver.Clip = savedClip;
 
 
@@ -294,14 +296,7 @@ namespace Terminal.Gui {
 			ClearNeedsDisplay ();
 			ClearNeedsDisplay ();
 
 
 			Driver.SetAttribute (GetNormalColor ());
 			Driver.SetAttribute (GetNormalColor ());
-			//Driver.DrawWindowFrame (scrRect, padding.Left + borderLength, padding.Top + borderLength, padding.Right + borderLength, padding.Bottom + borderLength,
-			//	Border.BorderStyle != BorderStyle.None, fill: true, Border.BorderStyle);
 			Border.DrawContent (this, false);
 			Border.DrawContent (this, false);
-			if (HasFocus)
-				Driver.SetAttribute (ColorScheme.HotNormal);
-			if (Border.DrawMarginFrame)
-				Driver.DrawWindowTitle (scrRect, Title, padding.Left, padding.Top, padding.Right, padding.Bottom);
-			Driver.SetAttribute (GetNormalColor ());
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>

+ 5 - 3
Terminal.Gui/Terminal.Gui.csproj

@@ -10,9 +10,10 @@
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- In the source tree the version will always be 1.0 for all projects. -->
     <!-- In the source tree the version will always be 1.0 for all projects. -->
     <!-- Do not modify these. Do NOT commit after manually running `dotnet-gitversion /updateprojectfiles` -->
     <!-- Do not modify these. Do NOT commit after manually running `dotnet-gitversion /updateprojectfiles` -->
-    <AssemblyVersion>1.9</AssemblyVersion>
-    <Version>1.9</Version>
-    <InformationalVersion>1.9</InformationalVersion>
+    <AssemblyVersion>1.0</AssemblyVersion>
+    <FileVersion>1.0</FileVersion>
+    <Version>1.0</Version>
+    <InformationalVersion>1.0</InformationalVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
@@ -52,6 +53,7 @@
   <!-- Enable Nuget Source Link for github -->
   <!-- Enable Nuget Source Link for github -->
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
+    <PackageReference Include="System.Management" Version="7.0.0" />
   </ItemGroup>
   </ItemGroup>
   <PropertyGroup>
   <PropertyGroup>
     <TargetFrameworks>net472;netstandard2.0;net6.0</TargetFrameworks>
     <TargetFrameworks>net472;netstandard2.0;net6.0</TargetFrameworks>

+ 9 - 11
Terminal.Gui/Views/FrameView.cs

@@ -30,6 +30,9 @@ namespace Terminal.Gui {
 			get => title;
 			get => title;
 			set {
 			set {
 				title = value;
 				title = value;
+				if (Border != null) {
+					Border.Title = title;
+				}
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
 			}
 			}
 		}
 		}
@@ -112,10 +115,14 @@ namespace Terminal.Gui {
 			this.Title = title;
 			this.Title = title;
 			if (border == null) {
 			if (border == null) {
 				Border = new Border () {
 				Border = new Border () {
-					BorderStyle = BorderStyle.Single
+					BorderStyle = BorderStyle.Single,
+					Title = title
 				};
 				};
 			} else {
 			} else {
 				Border = border;
 				Border = border;
+				if (ustring.IsNullOrEmpty (border.Title)) {
+					border.Title = title;
+				}
 			}
 			}
 			AdjustContentView (frame, views);
 			AdjustContentView (frame, views);
 		}
 		}
@@ -214,12 +221,8 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		public override void Redraw (Rect bounds)
 		{
 		{
-			var padding = Border.GetSumThickness ();
-			var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height));
-
 			if (!NeedDisplay.IsEmpty) {
 			if (!NeedDisplay.IsEmpty) {
 				Driver.SetAttribute (GetNormalColor ());
 				Driver.SetAttribute (GetNormalColor ());
-				//Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: true);
 				Clear ();
 				Clear ();
 			}
 			}
 
 
@@ -227,16 +230,11 @@ namespace Terminal.Gui {
 			contentView.Redraw (!NeedDisplay.IsEmpty ? contentView.Bounds : bounds);
 			contentView.Redraw (!NeedDisplay.IsEmpty ? contentView.Bounds : bounds);
 			Driver.Clip = savedClip;
 			Driver.Clip = savedClip;
 
 
+			ClearLayoutNeeded ();
 			ClearNeedsDisplay ();
 			ClearNeedsDisplay ();
 
 
 			Driver.SetAttribute (GetNormalColor ());
 			Driver.SetAttribute (GetNormalColor ());
-			//Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false);
 			Border.DrawContent (this, false);
 			Border.DrawContent (this, false);
-			if (HasFocus)
-				Driver.SetAttribute (ColorScheme.HotNormal);
-			if (Border.DrawMarginFrame)
-				Driver.DrawWindowTitle (scrRect, Title, padding.Left, padding.Top, padding.Right, padding.Bottom);
-			Driver.SetAttribute (GetNormalColor ());
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>

+ 8 - 5
Terminal.Gui/Views/ListView.cs

@@ -430,7 +430,7 @@ namespace Terminal.Gui {
 				var newItem = KeystrokeNavigator?.GetNextMatchingItem (SelectedItem, (char)kb.KeyValue);
 				var newItem = KeystrokeNavigator?.GetNextMatchingItem (SelectedItem, (char)kb.KeyValue);
 				if (newItem is int && newItem != -1) {
 				if (newItem is int && newItem != -1) {
 					SelectedItem = (int)newItem;
 					SelectedItem = (int)newItem;
-					EnsuresVisibilitySelectedItem ();
+					EnsureSelectedItemVisible ();
 					SetNeedsDisplay ();
 					SetNeedsDisplay ();
 					return true;
 					return true;
 				}
 				}
@@ -727,7 +727,7 @@ namespace Terminal.Gui {
 			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
 
 			if (lastSelectedItem == -1) {
 			if (lastSelectedItem == -1) {
-				EnsuresVisibilitySelectedItem ();
+				EnsureSelectedItemVisible ();
 			}
 			}
 
 
 			return base.OnEnter (view);
 			return base.OnEnter (view);
@@ -743,7 +743,10 @@ namespace Terminal.Gui {
 			return base.OnLeave (view);
 			return base.OnLeave (view);
 		}
 		}
 
 
-		void EnsuresVisibilitySelectedItem ()
+		/// <summary>
+		/// Ensures the selected item is always visible on the screen.
+		/// </summary>
+		public void EnsureSelectedItemVisible ()
 		{
 		{
 			SuperView?.LayoutSubviews ();
 			SuperView?.LayoutSubviews ();
 			if (selected < top) {
 			if (selected < top) {
@@ -840,7 +843,7 @@ namespace Terminal.Gui {
 			if (src == null || src?.Count == 0) {
 			if (src == null || src?.Count == 0) {
 				return 0;
 				return 0;
 			}
 			}
-			
+
 			int maxLength = 0;
 			int maxLength = 0;
 			for (int i = 0; i < src.Count; i++) {
 			for (int i = 0; i < src.Count; i++) {
 				var t = src [i];
 				var t = src [i];
@@ -924,7 +927,7 @@ namespace Terminal.Gui {
 						return i;
 						return i;
 					}
 					}
 				} else if (t is string s) {
 				} else if (t is string s) {
-					if (s.ToUpperInvariant ().StartsWith (search.ToUpperInvariant ())) {
+					if (s.StartsWith (search, StringComparison.InvariantCultureIgnoreCase)) {
 						return i;
 						return i;
 					}
 					}
 				}
 				}

+ 64 - 1
Terminal.Gui/Views/TabView.cs

@@ -53,6 +53,14 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public event EventHandler<TabChangedEventArgs> SelectedTabChanged;
 		public event EventHandler<TabChangedEventArgs> SelectedTabChanged;
 
 
+
+		/// <summary>
+		/// Event fired when a <see cref="TabView.Tab"/> is clicked.  Can be used to cancel navigation,
+		/// show context menu (e.g. on right click) etc.
+		/// </summary>
+		public event EventHandler<TabMouseEventArgs> TabClicked;
+
+
 		/// <summary>
 		/// <summary>
 		/// The currently selected member of <see cref="Tabs"/> chosen by the user
 		/// The currently selected member of <see cref="Tabs"/> chosen by the user
 		/// </summary>
 		/// </summary>
@@ -665,6 +673,22 @@ namespace Terminal.Gui {
 
 
 			public override bool MouseEvent (MouseEvent me)
 			public override bool MouseEvent (MouseEvent me)
 			{
 			{
+				var hit = ScreenToTab (me.X, me.Y);
+
+				bool isClick = me.Flags.HasFlag (MouseFlags.Button1Clicked) ||
+					me.Flags.HasFlag (MouseFlags.Button2Clicked) ||
+					me.Flags.HasFlag (MouseFlags.Button3Clicked);
+
+				if (isClick) {
+					host.OnTabClicked (new TabMouseEventArgs (hit, me));
+
+					// user canceled click
+					if (me.Handled) {
+						return true;
+					}
+				}
+
+
 				if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
 				if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
 				!me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
 				!me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
 				!me.Flags.HasFlag (MouseFlags.Button1TripleClicked))
 				!me.Flags.HasFlag (MouseFlags.Button1TripleClicked))
@@ -689,7 +713,7 @@ namespace Terminal.Gui {
 						return true;
 						return true;
 					}
 					}
 
 
-					var hit = ScreenToTab (me.X, me.Y);
+
 					if (hit != null) {
 					if (hit != null) {
 						host.SelectedTab = hit;
 						host.SelectedTab = hit;
 						SetNeedsDisplay ();
 						SetNeedsDisplay ();
@@ -738,6 +762,45 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		/// <summary>
+		/// Raises the <see cref="TabClicked"/> event.
+		/// </summary>
+		/// <param name="tabMouseEventArgs"></param>
+		protected virtual private void OnTabClicked (TabMouseEventArgs tabMouseEventArgs)
+		{
+			TabClicked?.Invoke (this, tabMouseEventArgs);
+		}
+
+		/// <summary>
+		/// Describes a mouse event over a specific <see cref="TabView.Tab"/> in a <see cref="TabView"/>.
+		/// </summary>
+		public class TabMouseEventArgs : EventArgs {
+
+			/// <summary>
+			/// Gets the <see cref="TabView.Tab"/> (if any) that the mouse
+			/// was over when the <see cref="MouseEvent"/> occurred.
+			/// </summary>
+			/// <remarks>This will be null if the click is after last tab
+			/// or before first.</remarks>
+			public Tab Tab { get; }
+
+			/// <summary>
+			/// Gets the actual mouse event.  Use <see cref="MouseEvent.Handled"/> to cancel this event
+			/// and perform custom behavior (e.g. show a context menu).
+			/// </summary>
+			public MouseEvent MouseEvent { get; }
+
+			/// <summary>
+			/// Creates a new instance of the <see cref="TabMouseEventArgs"/> class.
+			/// </summary>
+			/// <param name="tab"><see cref="TabView.Tab"/> that the mouse was over when the event occurred.</param>
+			/// <param name="mouseEvent">The mouse activity being reported</param>
+			public TabMouseEventArgs (Tab tab, MouseEvent mouseEvent)
+			{
+				Tab = tab;
+				MouseEvent = mouseEvent;
+			}
+		}
 
 
 		/// <summary>
 		/// <summary>
 		/// A single tab in a <see cref="TabView"/>
 		/// A single tab in a <see cref="TabView"/>

+ 6 - 5
Terminal.Gui/Views/TextField.cs

@@ -298,6 +298,7 @@ namespace Terminal.Gui {
 					}
 					}
 					return;
 					return;
 				}
 				}
+				ClearAllSelection ();
 				text = TextModel.ToRunes (newText.NewText);
 				text = TextModel.ToRunes (newText.NewText);
 
 
 				if (!Secret && !historyText.IsFromHistory) {
 				if (!Secret && !historyText.IsFromHistory) {
@@ -1063,8 +1064,8 @@ namespace Terminal.Gui {
 				EnsureHasFocus ();
 				EnsureHasFocus ();
 				int x = PositionCursor (ev);
 				int x = PositionCursor (ev);
 				int sbw = x;
 				int sbw = x;
-				if (x == text.Count || (x > 0 && (char)Text [x - 1] != ' '
-					|| (x > 0 && (char)Text [x] == ' '))) {
+				if (x == text.Count || (x > 0 && (char)text [x - 1] != ' ')
+					|| (x > 0 && (char)text [x] == ' ')) {
 
 
 					sbw = WordBackward (x);
 					sbw = WordBackward (x);
 				}
 				}
@@ -1345,12 +1346,12 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>
-		protected override string GetCurrentWord ()
+		protected override string GetCurrentWord (int columnOffset = 0)
 		{
 		{
 			var host = (TextField)HostControl;
 			var host = (TextField)HostControl;
 			var currentLine = host.Text.ToRuneList ();
 			var currentLine = host.Text.ToRuneList ();
-			var cursorPosition = Math.Min (host.CursorPosition, currentLine.Count);
-			return IdxToWord (currentLine, cursorPosition);
+			var cursorPosition = Math.Min (host.CursorPosition + columnOffset, currentLine.Count);
+			return IdxToWord (currentLine, cursorPosition, columnOffset);
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>

+ 23 - 15
Terminal.Gui/Views/TextView.cs

@@ -2035,6 +2035,16 @@ namespace Terminal.Gui {
 			return base.OnEnter (view);
 			return base.OnEnter (view);
 		}
 		}
 
 
+		///<inheritdoc/>
+		public override bool OnLeave (View view)
+		{
+			if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
+				Application.UngrabMouse ();
+			}
+
+			return base.OnLeave (view);
+		}
+
 		// Returns an encoded region start..end (top 32 bits are the row, low32 the column)
 		// Returns an encoded region start..end (top 32 bits are the row, low32 the column)
 		void GetEncodedRegionBounds (out long start, out long end,
 		void GetEncodedRegionBounds (out long start, out long end,
 			int? startRow = null, int? startCol = null, int? cRow = null, int? cCol = null)
 			int? startRow = null, int? startCol = null, int? cRow = null, int? cCol = null)
@@ -2437,6 +2447,10 @@ namespace Terminal.Gui {
 
 
 			PositionCursor ();
 			PositionCursor ();
 
 
+			if (clickWithSelecting) {
+				clickWithSelecting = false;
+				return;
+			}
 			if (SelectedLength > 0)
 			if (SelectedLength > 0)
 				return;
 				return;
 
 
@@ -2667,8 +2681,10 @@ namespace Terminal.Gui {
 				need = true;
 				need = true;
 			} else if ((wordWrap && leftColumn > 0) || (dSize.size + RightOffset < Frame.Width + offB.width
 			} else if ((wordWrap && leftColumn > 0) || (dSize.size + RightOffset < Frame.Width + offB.width
 				&& tSize.size + RightOffset < Frame.Width + offB.width)) {
 				&& tSize.size + RightOffset < Frame.Width + offB.width)) {
-				leftColumn = 0;
-				need = true;
+				if (leftColumn > 0) {
+					leftColumn = 0;
+					need = true;
+				}
 			}
 			}
 
 
 			if (currentRow < topRow) {
 			if (currentRow < topRow) {
@@ -4269,6 +4285,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		bool isButtonShift;
 		bool isButtonShift;
+		bool clickWithSelecting;
 
 
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override bool MouseEvent (MouseEvent ev)
 		public override bool MouseEvent (MouseEvent ev)
@@ -4362,6 +4379,7 @@ namespace Terminal.Gui {
 				columnTrack = currentColumn;
 				columnTrack = currentColumn;
 			} else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed)) {
 			} else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed)) {
 				if (shiftSelecting) {
 				if (shiftSelecting) {
+					clickWithSelecting = true;
 					StopSelecting ();
 					StopSelecting ();
 				}
 				}
 				ProcessMouseClick (ev, out _);
 				ProcessMouseClick (ev, out _);
@@ -4447,16 +4465,6 @@ namespace Terminal.Gui {
 			line = r;
 			line = r;
 		}
 		}
 
 
-		///<inheritdoc/>
-		public override bool OnLeave (View view)
-		{
-			if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
-				Application.UngrabMouse ();
-			}
-
-			return base.OnLeave (view);
-		}
-
 		/// <summary>
 		/// <summary>
 		/// Allows clearing the <see cref="HistoryText.HistoryTextItem"/> items updating the original text.
 		/// Allows clearing the <see cref="HistoryText.HistoryTextItem"/> items updating the original text.
 		/// </summary>
 		/// </summary>
@@ -4475,12 +4483,12 @@ namespace Terminal.Gui {
 	public class TextViewAutocomplete : Autocomplete {
 	public class TextViewAutocomplete : Autocomplete {
 
 
 		///<inheritdoc/>
 		///<inheritdoc/>
-		protected override string GetCurrentWord ()
+		protected override string GetCurrentWord (int columnOffset = 0)
 		{
 		{
 			var host = (TextView)HostControl;
 			var host = (TextView)HostControl;
 			var currentLine = host.GetCurrentLine ();
 			var currentLine = host.GetCurrentLine ();
-			var cursorPosition = Math.Min (host.CurrentColumn, currentLine.Count);
-			return IdxToWord (currentLine, cursorPosition);
+			var cursorPosition = Math.Min (host.CurrentColumn + columnOffset, currentLine.Count);
+			return IdxToWord (currentLine, cursorPosition, columnOffset);
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>

+ 6 - 10
UICatalog/Properties/launchSettings.json

@@ -3,6 +3,12 @@
     "UICatalog": {
     "UICatalog": {
       "commandName": "Project"
       "commandName": "Project"
     },
     },
+    "WSL : UICatalog": {
+      "commandName": "Executable",
+      "executablePath": "wsl",
+      "commandLineArgs": "dotnet UICatalog.dll",
+      "distributionName": ""
+    },
     "UICatalog -usc": {
     "UICatalog -usc": {
       "commandName": "Project",
       "commandName": "Project",
       "commandLineArgs": "-usc"
       "commandLineArgs": "-usc"
@@ -29,10 +35,6 @@
       "commandName": "Project",
       "commandName": "Project",
       "commandLineArgs": "WizardAsView"
       "commandLineArgs": "WizardAsView"
     },
     },
-    "VkeyPacketSimulator": {
-      "commandName": "Project",
-      "commandLineArgs": "VkeyPacketSimulator"
-    },
     "CollectionNavigatorTester": {
     "CollectionNavigatorTester": {
       "commandName": "Project",
       "commandName": "Project",
       "commandLineArgs": "\"Search Collection Nav\""
       "commandLineArgs": "\"Search Collection Nav\""
@@ -48,12 +50,6 @@
     "Windows & FrameViews": {
     "Windows & FrameViews": {
       "commandName": "Project",
       "commandName": "Project",
       "commandLineArgs": "\"Windows & FrameViews\""
       "commandLineArgs": "\"Windows & FrameViews\""
-    },
-    "WSL : UICatalog": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll",
-      "distributionName": ""
     }
     }
   }
   }
 }
 }

+ 313 - 0
UICatalog/Scenarios/ASCIICustomButton.cs

@@ -0,0 +1,313 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "ASCIICustomButtonTest", Description: "ASCIICustomButton sample")]
+	[ScenarioCategory ("Controls")]
+	public class ASCIICustomButtonTest : Scenario {
+		private static bool smallerWindow;
+		private ScrollViewTestWindow scrollViewTestWindow;
+		private MenuItem miSmallerWindow;
+
+		public override void Init (ColorScheme colorScheme)
+		{
+			Application.Init ();
+			scrollViewTestWindow = new ScrollViewTestWindow ();
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem("Window Size", new MenuItem [] {
+					miSmallerWindow = new MenuItem ("Smaller Window", "", ChangeWindowSize) {
+						CheckType = MenuItemCheckStyle.Checked
+					},
+					null,
+					new MenuItem("Quit", "",() => Application.RequestStop(),null,null, Key.Q | Key.CtrlMask)
+				})
+			});
+			Application.Top.Add (menu, scrollViewTestWindow);
+			Application.Run ();
+		}
+
+		private void ChangeWindowSize ()
+		{
+			smallerWindow = miSmallerWindow.Checked = !miSmallerWindow.Checked;
+			scrollViewTestWindow.Dispose ();
+			Application.Top.Remove (scrollViewTestWindow);
+			scrollViewTestWindow = new ScrollViewTestWindow ();
+			Application.Top.Add (scrollViewTestWindow);
+		}
+
+		public override void Run ()
+		{
+		}
+
+		public class ASCIICustomButton : Button {
+			public string Description => $"Description of: {id}";
+
+			public event Action<ASCIICustomButton> PointerEnter;
+
+			private Label fill;
+			private FrameView border;
+			private string id;
+
+			public ASCIICustomButton (string text, Pos x, Pos y, int width, int height) : base (text)
+			{
+				CustomInitialize ("", text, x, y, width, height);
+			}
+
+			public ASCIICustomButton (string id, string text, Pos x, Pos y, int width, int height) : base (text)
+			{
+				CustomInitialize (id, text, x, y, width, height);
+			}
+
+			private void CustomInitialize (string id, string text, Pos x, Pos y, int width, int height)
+			{
+				this.id = id;
+				X = x;
+				Y = y;
+
+				Frame = new Rect {
+					Width = width,
+					Height = height
+				};
+
+				border = new FrameView () {
+					Width = width,
+					Height = height
+				};
+
+				AutoSize = false;
+
+				var fillText = new System.Text.StringBuilder ();
+				for (int i = 0; i < Bounds.Height; i++) {
+					if (i > 0) {
+						fillText.AppendLine ("");
+					}
+					for (int j = 0; j < Bounds.Width; j++) {
+						fillText.Append ("█");
+					}
+				}
+
+				fill = new Label (fillText.ToString ()) {
+					Visible = false,
+					CanFocus = false
+				};
+
+				var title = new Label (text) {
+					X = Pos.Center (),
+					Y = Pos.Center (),
+				};
+
+				border.MouseClick += This_MouseClick;
+				border.Subviews [0].MouseClick += This_MouseClick;
+				fill.MouseClick += This_MouseClick;
+				title.MouseClick += This_MouseClick;
+
+				Add (border, fill, title);
+			}
+
+			private void This_MouseClick (MouseEventArgs obj)
+			{
+				OnMouseEvent (obj.MouseEvent);
+			}
+
+			public override bool OnMouseEvent (MouseEvent mouseEvent)
+			{
+				Debug.WriteLine ($"{mouseEvent.Flags}");
+				if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
+					if (!HasFocus && SuperView != null) {
+						if (!SuperView.HasFocus) {
+							SuperView.SetFocus ();
+						}
+						SetFocus ();
+						SetNeedsDisplay ();
+					}
+
+					OnClicked ();
+					return true;
+				}
+				return base.OnMouseEvent (mouseEvent);
+			}
+
+			public override bool OnEnter (View view)
+			{
+				border.Visible = false;
+				fill.Visible = true;
+				PointerEnter.Invoke (this);
+				view = this;
+				return base.OnEnter (view);
+			}
+
+			public override bool OnLeave (View view)
+			{
+				border.Visible = true;
+				fill.Visible = false;
+				if (view == null)
+					view = this;
+				return base.OnLeave (view);
+			}
+		}
+
+		public class ScrollViewTestWindow : Window {
+			private List<Button> buttons;
+			private const int BUTTONS_ON_PAGE = 7;
+			private const int BUTTON_HEIGHT = 3;
+
+			private ScrollView scrollView;
+			private ASCIICustomButton selected;
+
+			public ScrollViewTestWindow ()
+			{
+				Title = "ScrollViewTestWindow";
+
+				Label titleLabel = null;
+				if (smallerWindow) {
+					Width = 80;
+					Height = 25;
+
+					scrollView = new ScrollView () {
+						X = 3,
+						Y = 1,
+						Width = 24,
+						Height = BUTTONS_ON_PAGE * BUTTON_HEIGHT,
+						ShowVerticalScrollIndicator = true,
+						ShowHorizontalScrollIndicator = false
+					};
+				} else {
+					Width = Dim.Fill ();
+					Height = Dim.Fill ();
+
+					titleLabel = new Label ("DOCUMENTS") {
+						X = 0,
+						Y = 0
+					};
+
+					scrollView = new ScrollView () {
+						X = 0,
+						Y = 1,
+						Width = 27,
+						Height = BUTTONS_ON_PAGE * BUTTON_HEIGHT,
+						ShowVerticalScrollIndicator = true,
+						ShowHorizontalScrollIndicator = false
+					};
+				}
+
+				scrollView.ClearKeybindings ();
+
+				buttons = new List<Button> ();
+				Button prevButton = null;
+				int count = 20;
+				for (int j = 0; j < count; j++) {
+					Pos yPos = prevButton == null ? 0 : Pos.Bottom (prevButton);
+					var button = new ASCIICustomButton (j.ToString (), $"section {j}", 0, yPos, 25, BUTTON_HEIGHT);
+					button.Id = $"button{j}";
+					button.Clicked += Button_Clicked;
+					button.PointerEnter += Button_PointerEnter;
+					button.MouseClick += Button_MouseClick;
+					button.KeyPress += Button_KeyPress;
+					scrollView.Add (button);
+					buttons.Add (button);
+					prevButton = button;
+				}
+
+				var closeButton = new ASCIICustomButton ("close", "Close", 0, Pos.Bottom (prevButton), 25, BUTTON_HEIGHT);
+				closeButton.Clicked += Button_Clicked;
+				closeButton.PointerEnter += Button_PointerEnter;
+				closeButton.MouseClick += Button_MouseClick;
+				closeButton.KeyPress += Button_KeyPress;
+				scrollView.Add (closeButton);
+				buttons.Add (closeButton);
+
+				var pages = buttons.Count / BUTTONS_ON_PAGE;
+				if (buttons.Count % BUTTONS_ON_PAGE > 0)
+					pages++;
+
+				scrollView.ContentSize = new Size (25, pages * BUTTONS_ON_PAGE * BUTTON_HEIGHT);
+				if (smallerWindow) {
+					Add (scrollView);
+				} else {
+					Add (titleLabel, scrollView);
+				}
+			}
+
+			private void Button_KeyPress (KeyEventEventArgs obj)
+			{
+				switch (obj.KeyEvent.Key) {
+				case Key.End:
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						 -(scrollView.ContentSize.Height - scrollView.Frame.Height
+						 + (scrollView.ShowHorizontalScrollIndicator ? 1 : 0)));
+					obj.Handled = true;
+					return;
+				case Key.Home:
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X, 0);
+					obj.Handled = true;
+					return;
+				case Key.PageDown:
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						 Math.Max (scrollView.ContentOffset.Y - scrollView.Frame.Height,
+						 -(scrollView.ContentSize.Height - scrollView.Frame.Height
+						 + (scrollView.ShowHorizontalScrollIndicator ? 1 : 0))));
+					obj.Handled = true;
+					return;
+				case Key.PageUp:
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						 Math.Min (scrollView.ContentOffset.Y + scrollView.Frame.Height, 0));
+					obj.Handled = true;
+					return;
+				}
+			}
+
+			private void Button_MouseClick (MouseEventArgs obj)
+			{
+				if (obj.MouseEvent.Flags == MouseFlags.WheeledDown) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						scrollView.ContentOffset.Y - BUTTON_HEIGHT);
+					obj.Handled = true;
+				} else if (obj.MouseEvent.Flags == MouseFlags.WheeledUp) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						Math.Min (scrollView.ContentOffset.Y + BUTTON_HEIGHT, 0));
+					obj.Handled = true;
+				}
+			}
+
+			private void Button_Clicked ()
+			{
+				MessageBox.Query ("Button clicked.", $"'{selected.Text}' clicked!", "Ok");
+				if (selected.Text == "Close") {
+					Application.RequestStop ();
+				}
+			}
+
+			private void Button_PointerEnter (ASCIICustomButton obj)
+			{
+				bool? moveDown;
+				if (obj.Frame.Y > selected?.Frame.Y) {
+					moveDown = true;
+				} else if (obj.Frame.Y < selected?.Frame.Y) {
+					moveDown = false;
+				} else {
+					moveDown = null;
+				}
+				var offSet = selected != null ? obj.Frame.Y - selected.Frame.Y + (-scrollView.ContentOffset.Y % BUTTON_HEIGHT) : 0;
+				selected = obj;
+				if (moveDown == true && selected.Frame.Y + scrollView.ContentOffset.Y + BUTTON_HEIGHT >= scrollView.Frame.Height && offSet != BUTTON_HEIGHT) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						Math.Min (scrollView.ContentOffset.Y - BUTTON_HEIGHT, -(selected.Frame.Y - scrollView.Frame.Height + BUTTON_HEIGHT)));
+				} else if (moveDown == true && selected.Frame.Y + scrollView.ContentOffset.Y >= scrollView.Frame.Height) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						scrollView.ContentOffset.Y - BUTTON_HEIGHT);
+				} else if (moveDown == true && selected.Frame.Y + scrollView.ContentOffset.Y < 0) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						-selected.Frame.Y);
+				} else if (moveDown == false && selected.Frame.Y < -scrollView.ContentOffset.Y) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						Math.Max (scrollView.ContentOffset.Y + BUTTON_HEIGHT, selected.Frame.Y));
+				} else if (moveDown == false && selected.Frame.Y + scrollView.ContentOffset.Y > scrollView.Frame.Height) {
+					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
+						 -(selected.Frame.Y - scrollView.Frame.Height + BUTTON_HEIGHT));
+				}
+			}
+		}
+	}
+}

+ 234 - 0
UICatalog/Scenarios/Animation.cs

@@ -0,0 +1,234 @@
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.ColorSpaces;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Terminal.Gui;
+using Attribute = Terminal.Gui.Attribute;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Animation", Description: "Demonstration of how to render animated images with threading.")]
+	[ScenarioCategory ("Colors")]
+	public class Animation : Scenario
+	{
+		private bool isDisposed;
+
+		public override void Setup ()
+		{
+			base.Setup ();
+
+
+			var imageView = new ImageView () {
+				Width = Dim.Fill(),
+				Height = Dim.Fill()-2,
+			};
+
+			Win.Add (imageView);
+
+			var lbl = new Label("Image by Wikiscient"){
+				Y = Pos.AnchorEnd(2)
+			};
+			Win.Add(lbl);
+
+			var lbl2 = new Label("https://commons.wikimedia.org/wiki/File:Spinning_globe.gif"){
+				Y = Pos.AnchorEnd(1)
+			};
+			Win.Add(lbl2);
+
+			var dir = new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
+			
+			var f = new FileInfo(
+				Path.Combine(dir.FullName,"Scenarios","Spinning_globe_dark_small.gif"));
+			if(!f.Exists)
+			{
+				MessageBox.ErrorQuery("Could not find gif","Could not find "+ f.FullName,"Ok");
+				return;
+			}
+
+			imageView.SetImage(Image.Load<Rgba32> (File.ReadAllBytes (f.FullName)));
+
+			Task.Run(()=>{
+				while(!isDisposed)
+				{
+					// When updating from a Thread/Task always use Invoke
+					Application.MainLoop.Invoke(()=>
+					{
+						imageView.NextFrame();
+						imageView.SetNeedsDisplay();
+					});
+
+					Task.Delay(100).Wait();
+				}
+			});
+		}
+
+		protected override void Dispose(bool disposing)
+		{
+			isDisposed = true;
+			base.Dispose(disposing);
+		}
+
+		// This is a C# port of https://github.com/andraaspar/bitmap-to-braille by Andraaspar
+
+		/// <summary>
+		/// Renders an image as unicode Braille.
+		/// </summary>
+		public class BitmapToBraille
+		{
+
+			public const int CHAR_WIDTH = 2;
+			public const int CHAR_HEIGHT = 4;
+
+			const string CHARS = " ⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿";
+
+			public int WidthPixels {get; }
+			public int HeightPixels { get; }
+
+			public Func<int,int,bool> PixelIsLit {get;}
+
+			public BitmapToBraille (int widthPixels, int heightPixels, Func<int, int, bool> pixelIsLit)
+			{
+				WidthPixels = widthPixels;
+				HeightPixels = heightPixels;
+				PixelIsLit = pixelIsLit;
+			}
+
+			public string GenerateImage() {
+				int imageHeightChars = (int) Math.Ceiling((double)HeightPixels / CHAR_HEIGHT);
+				int imageWidthChars = (int) Math.Ceiling((double)WidthPixels / CHAR_WIDTH);
+
+				var result = new StringBuilder();
+
+				for (int y = 0; y < imageHeightChars; y++) {
+					
+					for (int x = 0; x < imageWidthChars; x++) {
+						int baseX = x * CHAR_WIDTH;
+						int baseY = y * CHAR_HEIGHT;
+
+						int charIndex = 0;
+						int value = 1;
+
+						for (int charX = 0; charX < CHAR_WIDTH; charX++) {
+							for (int charY = 0; charY < CHAR_HEIGHT; charY++) {
+								int bitmapX = baseX + charX;
+								int bitmapY = baseY + charY;
+								bool pixelExists = bitmapX < WidthPixels && bitmapY < HeightPixels;
+
+								if (pixelExists && PixelIsLit(bitmapX, bitmapY)) {
+									charIndex += value;
+								}
+								value *= 2;
+							}
+						}
+
+						result.Append(CHARS[charIndex]);
+					}
+					result.Append('\n');
+				}
+				return result.ToString().TrimEnd();
+			}  
+		}
+
+		class ImageView : View {
+			private int frameCount;
+			private int currentFrame = 0;
+
+			private Image<Rgba32>[] fullResImages;
+			private Image<Rgba32>[] matchSizes;
+			private string[] brailleCache;
+
+			Rect oldSize = Rect.Empty;
+
+
+			internal void SetImage (Image<Rgba32> image)
+			{
+				frameCount = image.Frames.Count;
+
+				fullResImages = new Image<Rgba32>[frameCount];
+				matchSizes = new Image<Rgba32>[frameCount];
+				brailleCache = new string[frameCount];
+
+				for(int i=0;i<frameCount-1;i++)
+				{
+					fullResImages[i] = image.Frames.ExportFrame(0);
+				}
+				fullResImages[frameCount-1] = image;
+
+				this.SetNeedsDisplay ();
+			}
+			public void NextFrame()
+			{
+				currentFrame = (currentFrame+1)%frameCount;
+			}
+
+			public override void Redraw (Rect bounds)
+			{
+				base.Redraw (bounds);
+
+				if(oldSize != bounds)
+				{
+					// Invalidate cached images now size has changed
+					matchSizes = new Image<Rgba32>[frameCount];
+					brailleCache = new string[frameCount];
+					oldSize = bounds;
+				}
+
+				var imgScaled = matchSizes[currentFrame];
+				var braille = brailleCache[currentFrame];
+
+				if(imgScaled == null)
+				{
+					var imgFull = fullResImages[currentFrame];
+				
+					// keep aspect ratio
+					var newSize = Math.Min(bounds.Width,bounds.Height);
+
+					// generate one
+					matchSizes[currentFrame] = imgScaled = imgFull.Clone (
+						x => x.Resize (
+							 newSize * BitmapToBraille.CHAR_HEIGHT,
+							 newSize * BitmapToBraille.CHAR_HEIGHT));
+				}
+
+				if(braille == null)
+				{
+					brailleCache[currentFrame] = braille = GetBraille(matchSizes[currentFrame]);
+				}
+
+
+				var lines = braille.Split('\n');
+
+				for(int y = 0; y < lines.Length;y++)
+				{
+					var line = lines[y];
+					for(int x = 0;x<line.Length ;x++)
+					{
+						AddRune(x,y,line[x]);
+					}
+				}
+			}
+
+			private string GetBraille (Image<Rgba32> img)
+			{
+				var braille = new BitmapToBraille(
+					img.Width,
+					img.Height,
+					(x,y)=>IsLit(img,x,y));
+
+				return braille.GenerateImage();
+			}
+
+			private bool IsLit (Image<Rgba32> img, int x, int y)
+			{
+				var rgb = img[x,y];
+				return rgb.R + rgb.G + rgb.B > 50;
+			}
+		}
+	}
+}

+ 3 - 2
UICatalog/Scenarios/BasicColors.cs

@@ -13,13 +13,14 @@ namespace UICatalog.Scenarios {
 			var colors = System.Enum.GetValues (typeof (Color));
 			var colors = System.Enum.GetValues (typeof (Color));
 
 
 			foreach (Color bg in colors) {
 			foreach (Color bg in colors) {
+				Attribute attr = new Attribute (bg, colors.Length - 1 - bg);
 				var vl = new Label (bg.ToString (), TextDirection.TopBottom_LeftRight) {
 				var vl = new Label (bg.ToString (), TextDirection.TopBottom_LeftRight) {
 					X = vx,
 					X = vx,
 					Y = 0,
 					Y = 0,
 					Width = 1,
 					Width = 1,
 					Height = 13,
 					Height = 13,
 					VerticalTextAlignment = VerticalTextAlignment.Bottom,
 					VerticalTextAlignment = VerticalTextAlignment.Bottom,
-					ColorScheme = new ColorScheme () { Normal = new Attribute (bg, colors.Length - 1 - bg) }
+					ColorScheme = new ColorScheme () { Normal = attr }
 				};
 				};
 				Win.Add (vl);
 				Win.Add (vl);
 				var hl = new Label (bg.ToString ()) {
 				var hl = new Label (bg.ToString ()) {
@@ -28,7 +29,7 @@ namespace UICatalog.Scenarios {
 					Width = 13,
 					Width = 13,
 					Height = 1,
 					Height = 1,
 					TextAlignment = TextAlignment.Right,
 					TextAlignment = TextAlignment.Right,
-					ColorScheme = new ColorScheme () { Normal = new Attribute (bg, colors.Length - 1 - bg) }
+					ColorScheme = new ColorScheme () { Normal = attr }
 				};
 				};
 				Win.Add (hl);
 				Win.Add (hl);
 				vx++;
 				vx++;

+ 3 - 2
UICatalog/Scenarios/BordersComparisons.cs

@@ -12,9 +12,10 @@ namespace UICatalog.Scenarios {
 			var borderStyle = BorderStyle.Double;
 			var borderStyle = BorderStyle.Double;
 			var drawMarginFrame = false;
 			var drawMarginFrame = false;
 			var borderThickness = new Thickness (1, 2, 3, 4);
 			var borderThickness = new Thickness (1, 2, 3, 4);
-			var borderBrush = Colors.Base.HotFocus.Foreground;
+			var borderBrush = Color.BrightMagenta;
+			;
 			var padding = new Thickness (1, 2, 3, 4);
 			var padding = new Thickness (1, 2, 3, 4);
-			var background = Colors.Base.HotNormal.Foreground;
+			var background = Color.Cyan;
 			var effect3D = true;
 			var effect3D = true;
 
 
 			var win = new Window (new Rect (5, 5, 40, 20), "Test", 8,
 			var win = new Window (new Rect (5, 5, 40, 20), "Test", 8,

+ 212 - 0
UICatalog/Scenarios/LineDrawing.cs

@@ -0,0 +1,212 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection.Metadata.Ecma335;
+using Terminal.Gui;
+using Terminal.Gui.Graphs;
+
+namespace UICatalog.Scenarios {
+
+	[ScenarioMetadata (Name: "Line Drawing", Description: "Demonstrates LineCanvas.")]
+	[ScenarioCategory ("Controls")]
+	[ScenarioCategory ("Layout")]
+	public class LineDrawing : Scenario {
+
+		public override void Setup ()
+		{
+			var toolsWidth = 8;
+
+			var canvas = new DrawingArea {
+				Width = Dim.Fill (-toolsWidth),
+				Height = Dim.Fill ()
+			};
+
+			var tools = new ToolsView (toolsWidth) {
+				Y = 1,
+				X = Pos.AnchorEnd (toolsWidth + 1),
+				Height = Dim.Fill (),
+				Width = Dim.Fill ()
+			};
+
+
+			tools.ColorChanged += (c) => canvas.SetColor (c);
+			tools.SetStyle += (b) => canvas.BorderStyle = b;
+
+			Win.Add (canvas);
+			Win.Add (tools);
+			Win.Add (new Label (" -Tools-") { X = Pos.AnchorEnd (toolsWidth + 1) });
+		}
+
+		class ToolsView : View {
+
+			LineCanvas grid;
+			public event Action<Color> ColorChanged;
+			public event Action<BorderStyle> SetStyle;
+
+			Dictionary<Point, Color> swatches = new Dictionary<Point, Color> {
+				{ new Point(1,1),Color.Red},
+				{ new Point(3,1),Color.Green},
+				{ new Point(5,1),Color.BrightBlue},
+				{ new Point(7,1),Color.Black},
+				{ new Point(1,3),Color.White},
+			};
+
+			public ToolsView (int width)
+			{
+				grid = new LineCanvas ();
+
+				grid.AddLine (new Point (0, 0), int.MaxValue, Orientation.Vertical, BorderStyle.Single);
+				grid.AddLine (new Point (0, 0), width, Orientation.Horizontal, BorderStyle.Single);
+				grid.AddLine (new Point (width, 0), int.MaxValue, Orientation.Vertical, BorderStyle.Single);
+
+				grid.AddLine (new Point (0, 2), width, Orientation.Horizontal, BorderStyle.Single);
+
+				grid.AddLine (new Point (2, 0), int.MaxValue, Orientation.Vertical, BorderStyle.Single);
+				grid.AddLine (new Point (4, 0), int.MaxValue, Orientation.Vertical, BorderStyle.Single);
+				grid.AddLine (new Point (6, 0), int.MaxValue, Orientation.Vertical, BorderStyle.Single);
+
+				grid.AddLine (new Point (0, 4), width, Orientation.Horizontal, BorderStyle.Single);
+			}
+			public override void Redraw (Rect bounds)
+			{
+				base.Redraw (bounds);
+
+				Driver.SetAttribute (new Terminal.Gui.Attribute (Color.DarkGray, ColorScheme.Normal.Background));
+				
+				
+				foreach(var p in grid.GenerateImage(bounds))
+				{
+					this.AddRune(p.Key.X,p.Key.Y,p.Value);
+				}
+
+				foreach (var swatch in swatches) {
+					Driver.SetAttribute (new Terminal.Gui.Attribute (swatch.Value, ColorScheme.Normal.Background));
+					AddRune (swatch.Key.X, swatch.Key.Y, '█');
+				}
+
+				Driver.SetAttribute (new Terminal.Gui.Attribute (ColorScheme.Normal.Foreground, ColorScheme.Normal.Background));
+				AddRune (3, 3, Application.Driver.HDLine);
+				AddRune (5, 3, Application.Driver.HLine);
+				AddRune (7, 3, Application.Driver.ULRCorner);
+			}
+
+			public override bool OnMouseEvent (MouseEvent mouseEvent)
+			{
+				if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+					foreach (var swatch in swatches) {
+						if (mouseEvent.X == swatch.Key.X && mouseEvent.Y == swatch.Key.Y) {
+
+							ColorChanged?.Invoke (swatch.Value);
+							return true;
+						}
+					}
+
+					if (mouseEvent.X == 3 && mouseEvent.Y == 3) {
+
+						SetStyle?.Invoke (BorderStyle.Double);
+						return true;
+					}
+					if (mouseEvent.X == 5 && mouseEvent.Y == 3) {
+
+						SetStyle?.Invoke (BorderStyle.Single);
+						return true;
+					}
+					if (mouseEvent.X == 7 && mouseEvent.Y == 3) {
+
+						SetStyle?.Invoke (BorderStyle.Rounded);
+						return true;
+					}
+				}
+
+				return base.OnMouseEvent (mouseEvent);
+			}
+		}
+
+		class DrawingArea : View {
+			/// <summary>
+			/// Index into <see cref="canvases"/> by color.
+			/// </summary>
+			Dictionary<Color, int> colorLayers = new Dictionary<Color, int> ();
+			List<LineCanvas> canvases = new List<LineCanvas> ();
+			int currentColor;
+
+			Point? currentLineStart = null;
+
+			public BorderStyle BorderStyle { get; internal set; }
+
+			public DrawingArea ()
+			{
+				AddCanvas (Color.White);
+			}
+
+			private void AddCanvas (Color c)
+			{
+				if (colorLayers.ContainsKey (c)) {
+					return;
+				}
+
+				canvases.Add (new LineCanvas ());
+				colorLayers.Add (c, canvases.Count - 1);
+				currentColor = canvases.Count - 1;
+			}
+
+			public override void Redraw (Rect bounds)
+			{
+				base.Redraw (bounds);
+
+				foreach (var kvp in colorLayers) {
+
+					Driver.SetAttribute (new Terminal.Gui.Attribute (kvp.Key, ColorScheme.Normal.Background));
+
+					var canvas = canvases [kvp.Value];
+
+					foreach(var p in canvas.GenerateImage(bounds))
+					{
+						this.AddRune(p.Key.X,p.Key.Y,p.Value);
+					}
+				}
+			}
+			public override bool OnMouseEvent (MouseEvent mouseEvent)
+			{
+
+				if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) {
+					if (currentLineStart == null) {
+						currentLineStart = new Point (mouseEvent.X, mouseEvent.Y);
+					}
+				} else {
+					if (currentLineStart != null) {
+
+						var start = currentLineStart.Value;
+						var end = new Point (mouseEvent.X, mouseEvent.Y);
+						var orientation = Orientation.Vertical;
+						var length = end.Y - start.Y;
+
+						// if line is wider than it is tall switch to horizontal
+						if (Math.Abs (start.X - end.X) > Math.Abs (start.Y - end.Y)) {
+							orientation = Orientation.Horizontal;
+							length = end.X - start.X;
+						}
+
+
+						canvases [currentColor].AddLine (
+							start,
+							length,
+							orientation,
+							BorderStyle);
+
+						currentLineStart = null;
+						SetNeedsDisplay ();
+					}
+				}
+
+				return base.OnMouseEvent (mouseEvent);
+			}
+
+			internal void SetColor (Color c)
+			{
+				AddCanvas (c);
+				currentColor = colorLayers [c];
+			}
+
+		}
+	}
+}

+ 40 - 2
UICatalog/Scenarios/Notepad.cs

@@ -38,6 +38,8 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (1),
 				Height = Dim.Fill (1),
 			};
 			};
 
 
+			tabView.TabClicked += TabView_TabClicked;
+
 			tabView.Style.ShowBorder = true;
 			tabView.Style.ShowBorder = true;
 			tabView.ApplyStyleChanges ();
 			tabView.ApplyStyleChanges ();
 
 
@@ -63,6 +65,34 @@ namespace UICatalog.Scenarios {
 			New ();
 			New ();
 		}
 		}
 
 
+		private void TabView_TabClicked (object sender, TabView.TabMouseEventArgs e)
+		{
+			// we are only interested in right clicks
+			if(!e.MouseEvent.Flags.HasFlag(MouseFlags.Button3Clicked)) {
+				return;
+			}
+
+			MenuBarItem items;
+
+			if (e.Tab == null) {
+				items = new MenuBarItem (new MenuItem [] {
+					new MenuItem ($"Open", "", () => Open()),
+				});
+
+			} else {
+				items = new MenuBarItem (new MenuItem [] {
+					new MenuItem ($"Save", "", () => Save(e.Tab)),
+					new MenuItem ($"Close", "", () => Close(e.Tab)),
+				});
+			}
+
+
+			var contextMenu = new ContextMenu (e.MouseEvent.X + 1, e.MouseEvent.Y + 1, items);
+
+			contextMenu.Show ();
+			e.MouseEvent.Handled = true;
+		}
+
 		private void New ()
 		private void New ()
 		{
 		{
 			Open ("", null, $"new {numbeOfNewTabs++}");
 			Open ("", null, $"new {numbeOfNewTabs++}");
@@ -70,7 +100,11 @@ namespace UICatalog.Scenarios {
 
 
 		private void Close ()
 		private void Close ()
 		{
 		{
-			var tab = tabView.SelectedTab as OpenedFile;
+			Close (tabView.SelectedTab);
+		}
+		private void Close (TabView.Tab tabToClose)
+		{
+			var tab = tabToClose as OpenedFile;
 
 
 			if (tab == null) {
 			if (tab == null) {
 				return;
 				return;
@@ -158,7 +192,11 @@ namespace UICatalog.Scenarios {
 
 
 		public void Save ()
 		public void Save ()
 		{
 		{
-			var tab = tabView.SelectedTab as OpenedFile;
+			Save (tabView.SelectedTab);
+		}
+		public void Save (TabView.Tab tabToSave)
+		{
+			var tab = tabToSave as OpenedFile;
 
 
 			if (tab == null) {
 			if (tab == null) {
 				return;
 				return;

+ 353 - 0
UICatalog/Scenarios/Snake.cs

@@ -0,0 +1,353 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using Terminal.Gui;
+using Terminal.Gui.Graphs;
+using Attribute = Terminal.Gui.Attribute;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Snake", Description: "The game of apple eating.")]
+	[ScenarioCategory ("Colors")]
+	public class Snake : Scenario {
+		private bool isDisposed;
+
+		public override void Setup ()
+		{
+			base.Setup ();
+
+			var state = new SnakeState ();
+
+			state.Reset (60, 20);
+
+			var snakeView = new SnakeView (state) {
+				Width = state.Width,
+				Height = state.Height
+			};
+
+
+			Win.Add (snakeView);
+
+			Stopwatch sw = new Stopwatch ();
+
+			Task.Run (() => {
+				while (!isDisposed) {
+
+					sw.Restart ();
+
+					if (state.AdvanceState ()) {
+
+						// When updating from a Thread/Task always use Invoke
+						Application.MainLoop?.Invoke (() => {
+							snakeView.SetNeedsDisplay ();
+						});
+					}
+
+					var wait = state.SleepAfterAdvancingState - sw.ElapsedMilliseconds;
+
+					if (wait > 0) {
+						Task.Delay ((int)wait).Wait ();
+					}
+				}
+			});
+		}
+
+		protected override void Dispose (bool disposing)
+		{
+			isDisposed = true;
+			base.Dispose (disposing);
+		}
+
+		private class SnakeView : View {
+
+			private Attribute red = new Terminal.Gui.Attribute (Color.Red,Color.Black);
+			private Attribute white = new Terminal.Gui.Attribute (Color.White, Color.Black);
+
+			public SnakeState State { get; }
+
+			public SnakeView (SnakeState state)
+			{
+				State = state;
+				CanFocus = true;
+
+				ColorScheme = new ColorScheme {
+					Normal = white,
+					Focus = white,
+					HotNormal = white,
+					HotFocus = white,
+					Disabled = white
+				};
+			}
+
+			public override void Redraw (Rect bounds)
+			{
+				base.Redraw (bounds);
+
+				Driver.SetAttribute (white);
+				Clear ();
+
+				var canvas = new LineCanvas ();
+
+				canvas.AddLine (new Point (0, 0), State.Width - 1, Orientation.Horizontal, BorderStyle.Double);
+				canvas.AddLine (new Point (0, 0), State.Height - 1, Orientation.Vertical, BorderStyle.Double);
+				canvas.AddLine (new Point (0, State.Height - 1), State.Width - 1, Orientation.Horizontal, BorderStyle.Double);
+				canvas.AddLine (new Point (State.Width - 1, 0), State.Height - 1, Orientation.Vertical, BorderStyle.Double);
+
+				for (int i = 1; i < State.Snake.Count; i++) {
+
+					var pt1 = State.Snake [i - 1];
+					var pt2 = State.Snake [i];
+
+					var orientation = pt1.X == pt2.X ? Orientation.Vertical : Orientation.Horizontal;
+					var length = orientation == Orientation.Horizontal
+						? pt1.X > pt2.X ? 1 : -1
+						: pt1.Y > pt2.Y ? 1 : -1;
+
+					canvas.AddLine (
+						pt2,
+						length,
+						orientation,
+						BorderStyle.Single);
+
+				}
+
+				foreach(var p in canvas.GenerateImage (bounds)) {
+					AddRune (p.Key.X, p.Key.Y, p.Value);
+				}
+
+
+				Driver.SetAttribute (red);
+				AddRune (State.Apple.X, State.Apple.Y, 'A');
+				Driver.SetAttribute (white);
+			}
+			public override bool OnKeyDown (KeyEvent keyEvent)
+			{
+				if (keyEvent.Key == Key.CursorUp) {
+					State.PlannedDirection = Direction.Up;
+					return true;
+				}
+				if (keyEvent.Key == Key.CursorDown) {
+					State.PlannedDirection = Direction.Down;
+					return true;
+				}
+				if (keyEvent.Key == Key.CursorLeft) {
+					State.PlannedDirection = Direction.Left;
+					return true;
+				}
+				if (keyEvent.Key == Key.CursorRight) {
+					State.PlannedDirection = Direction.Right;
+					return true;
+				}
+
+				return false;
+			}
+		}
+		private class SnakeState {
+
+			public const int StartingLength = 10;
+			public const int AppleGrowRate = 5;
+			public const int StartingSpeed = 50;
+			public const int MaxSpeed = 20;
+
+			public int Width { get; private set; }
+			public int Height { get; private set; }
+
+			/// <summary>
+			/// Position of the snakes head
+			/// </summary>
+			public Point Head => Snake.Last ();
+
+			/// <summary>
+			/// Current position of the Apple that the snake has to eat.
+			/// </summary>
+			public Point Apple { get; private set; }
+
+			public Direction CurrentDirection { get; private set; }
+			public Direction PlannedDirection { get; set; }
+
+			public List<Point> Snake { get; private set; }
+
+			public int SleepAfterAdvancingState { get; private set; } = StartingSpeed;
+
+			int step;
+
+			internal bool AdvanceState ()
+			{
+				step++;
+
+				if (step < GetStepVelocity ()) {
+					return false;
+				}
+
+				step = 0;
+
+				UpdateDirection ();
+
+				var newHead = GetNewHeadPoint ();
+
+				Snake.RemoveAt (0);
+				Snake.Add (newHead);
+
+				if (IsDeath (newHead)) {
+					GameOver ();
+				}
+
+				if (newHead == Apple) {
+					GrowSnake (AppleGrowRate);
+					Apple = GetNewRandomApplePoint ();
+
+					var delta = 5;
+					if(SleepAfterAdvancingState < 40) {
+						delta = 3;
+					}
+					if (SleepAfterAdvancingState < 30) {
+						delta = 2;
+					}
+					SleepAfterAdvancingState = Math.Max (MaxSpeed, SleepAfterAdvancingState - delta);
+				}
+
+				return true;
+			}
+
+			private int GetStepVelocity ()
+			{
+				if (CurrentDirection == Direction.Left || CurrentDirection == Direction.Right) {
+					return 1;
+				}
+
+				return 2;
+			}
+
+			public void GrowSnake ()
+			{
+				var tail = Snake.First ();
+				Snake.Insert (0, tail);
+			}
+			public void GrowSnake (int amount)
+			{
+				for (int i = 0; i < amount; i++) {
+					GrowSnake ();
+				}
+			}
+
+			private void UpdateDirection ()
+			{
+				if (!AreOpposites (CurrentDirection, PlannedDirection)) {
+					CurrentDirection = PlannedDirection;
+				}
+			}
+
+			private bool AreOpposites (Direction a, Direction b)
+			{
+				switch (a) {
+				case Direction.Left: return b == Direction.Right;
+				case Direction.Right: return b == Direction.Left;
+				case Direction.Up: return b == Direction.Down;
+				case Direction.Down: return b == Direction.Up;
+				}
+
+				return false;
+			}
+
+			private void GameOver ()
+			{
+				Reset (Width, Height);
+			}
+
+			private Point GetNewHeadPoint ()
+			{
+				switch (CurrentDirection) {
+				case Direction.Left:
+					return new Point (Head.X - 1, Head.Y);
+
+				case Direction.Right:
+					return new Point (Head.X + 1, Head.Y);
+
+				case Direction.Up:
+					return new Point (Head.X, Head.Y - 1);
+
+				case Direction.Down:
+					return new Point (Head.X, Head.Y + 1);
+				}
+
+				throw new Exception ("Unknown direction");
+			}
+
+			/// <summary>
+			/// Restarts the game with the given canvas size
+			/// </summary>
+			/// <param name="width"></param>
+			/// <param name="height"></param>
+			internal void Reset (int width, int height)
+			{
+				if (width < 5 || height < 5) {
+					return;
+				}
+
+				Width = width;
+				Height = height;
+
+				var middle = new Point (width / 2, height / 2);
+
+				// Start snake with a length of 2
+				Snake = new List<Point> { middle, middle };
+				Apple = GetNewRandomApplePoint ();
+
+				SleepAfterAdvancingState = StartingSpeed;
+
+				GrowSnake (StartingLength);
+			}
+
+			private Point GetNewRandomApplePoint ()
+			{
+				Random r = new Random ();
+
+				for (int i = 0; i < 1000; i++) {
+					var x = r.Next (0, Width);
+					var y = r.Next (0, Height);
+
+					var p = new Point (x, y);
+
+					if (p == Head) {
+						continue;
+					}
+
+					if (IsDeath (p)) {
+						continue;
+					}
+
+					return p;
+				}
+
+				// Game is won or we are unable to generate a valid apple
+				// point after 1000 attempts.  Maybe screen size is very small
+				// or something.  Either way restart the game.
+				Reset (Width, Height);
+				return Apple;
+			}
+
+			private bool IsDeath (Point p)
+			{
+				if (p.X <= 0 || p.X >= Width - 1) {
+					return true;
+				}
+
+				if (p.Y <= 0 || p.Y >= Height - 1) {
+					return true;
+				}
+
+				if (Snake.Take (Snake.Count - 1).Contains (p))
+					return true;
+
+				return false;
+			}
+		}
+		private enum Direction {
+			Up,
+			Down,
+			Left,
+			Right
+		}
+	}
+}

BIN
UICatalog/Scenarios/Spinning_globe_dark_small.gif


+ 3 - 0
UICatalog/Scenarios/spinning-globe-attribution.txt

@@ -0,0 +1,3 @@
+Author: Wikiscient
+Date: 16 September 2011
+Original Url: https://commons.wikimedia.org/wiki/File:Spinning_globe.gif

+ 20 - 17
UICatalog/UICatalog.cs

@@ -72,6 +72,7 @@ namespace UICatalog {
 				_selectedScenario.Init (_colorScheme);
 				_selectedScenario.Init (_colorScheme);
 				_selectedScenario.Setup ();
 				_selectedScenario.Setup ();
 				_selectedScenario.Run ();
 				_selectedScenario.Run ();
+				_selectedScenario.Dispose ();
 				_selectedScenario = null;
 				_selectedScenario = null;
 				Application.Shutdown ();
 				Application.Shutdown ();
 				return;
 				return;
@@ -95,6 +96,7 @@ namespace UICatalog {
 				scenario.Init (_colorScheme);
 				scenario.Init (_colorScheme);
 				scenario.Setup ();
 				scenario.Setup ();
 				scenario.Run ();
 				scenario.Run ();
+				scenario.Dispose ();
 
 
 				// This call to Application.Shutdown brackets the Application.Init call
 				// This call to Application.Shutdown brackets the Application.Init call
 				// made by Scenario.Init() above
 				// made by Scenario.Init() above
@@ -118,6 +120,9 @@ namespace UICatalog {
 			// Run UI Catalog UI. When it exits, if _selectedScenario is != null then
 			// Run UI Catalog UI. When it exits, if _selectedScenario is != null then
 			// a Scenario was selected. Otherwise, the user wants to exit UI Catalog.
 			// a Scenario was selected. Otherwise, the user wants to exit UI Catalog.
 			Application.Init ();
 			Application.Init ();
+			
+			Application.EnableConsoleScrolling = _enableConsoleScrolling;
+			
 			Application.Run<UICatalogTopLevel> ();
 			Application.Run<UICatalogTopLevel> ();
 			Application.Shutdown ();
 			Application.Shutdown ();
 
 
@@ -139,7 +144,7 @@ namespace UICatalog {
 
 
 		static bool _useSystemConsole = false;
 		static bool _useSystemConsole = false;
 		static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
 		static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
-		static bool _heightAsBuffer = false;
+		static bool _enableConsoleScrolling = false;
 		static bool _isFirstRunning = true;
 		static bool _isFirstRunning = true;
 		static ColorScheme _colorScheme;
 		static ColorScheme _colorScheme;
 
 
@@ -149,7 +154,7 @@ namespace UICatalog {
 		/// </summary>
 		/// </summary>
 		class UICatalogTopLevel : Toplevel {
 		class UICatalogTopLevel : Toplevel {
 			public MenuItem miIsMouseDisabled;
 			public MenuItem miIsMouseDisabled;
-			public MenuItem miHeightAsBuffer;
+			public MenuItem miEnableConsoleScrolling;
 
 
 			public FrameView LeftPane;
 			public FrameView LeftPane;
 			public ListView CategoryListView;
 			public ListView CategoryListView;
@@ -164,7 +169,7 @@ namespace UICatalog {
 
 
 			public UICatalogTopLevel ()
 			public UICatalogTopLevel ()
 			{
 			{
-				ColorScheme = _colorScheme;
+				ColorScheme = _colorScheme = Colors.Base;
 				MenuBar = new MenuBar (new MenuBarItem [] {
 				MenuBar = new MenuBar (new MenuBarItem [] {
 					new MenuBarItem ("_File", new MenuItem [] {
 					new MenuBarItem ("_File", new MenuItem [] {
 						new MenuItem ("_Quit", "Quit UI Catalog", () => RequestStop(), null, null, Key.Q | Key.CtrlMask)
 						new MenuItem ("_Quit", "Quit UI Catalog", () => RequestStop(), null, null, Key.Q | Key.CtrlMask)
@@ -178,7 +183,7 @@ namespace UICatalog {
 							"About UI Catalog", () =>  MessageBox.Query ("About UI Catalog", _aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A),
 							"About UI Catalog", () =>  MessageBox.Query ("About UI Catalog", _aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A),
 					}),
 					}),
 				});
 				});
-				
+
 				Capslock = new StatusItem (Key.CharMask, "Caps", null);
 				Capslock = new StatusItem (Key.CharMask, "Caps", null);
 				Numlock = new StatusItem (Key.CharMask, "Num", null);
 				Numlock = new StatusItem (Key.CharMask, "Num", null);
 				Scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
 				Scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
@@ -272,14 +277,12 @@ namespace UICatalog {
 
 
 			void LoadedHandler ()
 			void LoadedHandler ()
 			{
 			{
-				Application.HeightAsBuffer = _heightAsBuffer;
-
 				if (_colorScheme == null) {
 				if (_colorScheme == null) {
 					ColorScheme = _colorScheme = Colors.Base;
 					ColorScheme = _colorScheme = Colors.Base;
 				}
 				}
 
 
 				miIsMouseDisabled.Checked = Application.IsMouseDisabled;
 				miIsMouseDisabled.Checked = Application.IsMouseDisabled;
-				miHeightAsBuffer.Checked = Application.HeightAsBuffer;
+				miEnableConsoleScrolling.Checked = Application.EnableConsoleScrolling;
 				DriverName.Title = $"Driver: {Driver.GetType ().Name}";
 				DriverName.Title = $"Driver: {Driver.GetType ().Name}";
 				OS.Title = $"OS: {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem} {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion}";
 				OS.Title = $"OS: {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem} {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion}";
 
 
@@ -316,7 +319,7 @@ namespace UICatalog {
 				List<MenuItem []> menuItems = new List<MenuItem []> ();
 				List<MenuItem []> menuItems = new List<MenuItem []> ();
 				menuItems.Add (CreateDiagnosticFlagsMenuItems ());
 				menuItems.Add (CreateDiagnosticFlagsMenuItems ());
 				menuItems.Add (new MenuItem [] { null });
 				menuItems.Add (new MenuItem [] { null });
-				menuItems.Add (CreateHeightAsBufferMenuItems ());
+				menuItems.Add (CreateEnableConsoleScrollingMenuItems ());
 				menuItems.Add (CreateDisabledEnabledMouseItems ());
 				menuItems.Add (CreateDisabledEnabledMouseItems ());
 				menuItems.Add (CreateKeybindingsMenuItems ());
 				menuItems.Add (CreateKeybindingsMenuItems ());
 				return menuItems;
 				return menuItems;
@@ -354,18 +357,18 @@ namespace UICatalog {
 				return menuItems.ToArray ();
 				return menuItems.ToArray ();
 			}
 			}
 
 
-			MenuItem [] CreateHeightAsBufferMenuItems ()
+			MenuItem [] CreateEnableConsoleScrollingMenuItems ()
 			{
 			{
 				List<MenuItem> menuItems = new List<MenuItem> ();
 				List<MenuItem> menuItems = new List<MenuItem> ();
-				miHeightAsBuffer = new MenuItem ();
-				miHeightAsBuffer.Title = "_Height As Buffer";
-				miHeightAsBuffer.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miHeightAsBuffer.Title.ToString ().Substring (1, 1) [0];
-				miHeightAsBuffer.CheckType |= MenuItemCheckStyle.Checked;
-				miHeightAsBuffer.Action += () => {
-					miHeightAsBuffer.Checked = !miHeightAsBuffer.Checked;
-					Application.HeightAsBuffer = miHeightAsBuffer.Checked;
+				miEnableConsoleScrolling = new MenuItem ();
+				miEnableConsoleScrolling.Title = "_Enable Console Scrolling";
+				miEnableConsoleScrolling.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miEnableConsoleScrolling.Title.ToString ().Substring (1, 1) [0];
+				miEnableConsoleScrolling.CheckType |= MenuItemCheckStyle.Checked;
+				miEnableConsoleScrolling.Action += () => {
+					miEnableConsoleScrolling.Checked = !miEnableConsoleScrolling.Checked;
+					Application.EnableConsoleScrolling = miEnableConsoleScrolling.Checked;
 				};
 				};
-				menuItems.Add (miHeightAsBuffer);
+				menuItems.Add (miEnableConsoleScrolling);
 
 
 				return menuItems.ToArray ();
 				return menuItems.ToArray ();
 			}
 			}

+ 5 - 1
UICatalog/UICatalog.csproj

@@ -5,7 +5,7 @@
     <LangVersion>8.0</LangVersion>
     <LangVersion>8.0</LangVersion>
     <StartupObject>UICatalog.UICatalogApp</StartupObject>
     <StartupObject>UICatalog.UICatalogApp</StartupObject>
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
-    <!-- In the source tree the version will always be 1.0 for all projects. -->
+    <!-- In the source tree the version will always be 2.0 for all projects. -->
     <!-- Do not modify these. -->
     <!-- Do not modify these. -->
     <AssemblyVersion>1.0</AssemblyVersion>
     <AssemblyVersion>1.0</AssemblyVersion>
     <FileVersion>1.0</FileVersion>
     <FileVersion>1.0</FileVersion>
@@ -19,6 +19,10 @@
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
+  <None Update="./Scenarios/Spinning_globe_dark_small.gif" CopyToOutputDirectory="PreserveNewest" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="SixLabors.ImageSharp" Version="3.0.0" />
     <PackageReference Include="CsvHelper" Version="30.0.1" />
     <PackageReference Include="CsvHelper" Version="30.0.1" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
   </ItemGroup>
   </ItemGroup>

+ 7 - 7
UnitTests/ApplicationTests.cs → UnitTests/Application/ApplicationTests.cs

@@ -9,7 +9,7 @@ using Xunit;
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	public class ApplicationTests {
 	public class ApplicationTests {
 		public ApplicationTests ()
 		public ApplicationTests ()
 		{
 		{
@@ -24,7 +24,7 @@ namespace Terminal.Gui.Core {
 			Assert.Null (Application.Driver);
 			Assert.Null (Application.Driver);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Current);
 			Assert.Null (Application.Current);
-			Assert.Throws<ArgumentNullException> (() => Application.HeightAsBuffer == true);
+			Assert.False (Application.EnableConsoleScrolling);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);
 			Assert.Null (Application.RootMouseEvent);
@@ -36,7 +36,7 @@ namespace Terminal.Gui.Core {
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.Current);
 			Assert.NotNull (Application.Current);
-			Assert.False (Application.HeightAsBuffer);
+			Assert.False (Application.EnableConsoleScrolling);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);
 			Assert.Null (Application.RootMouseEvent);
@@ -313,7 +313,7 @@ namespace Terminal.Gui.Core {
 		public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws ()
 		public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws ()
 		{
 		{
 			Init ();
 			Init ();
-			
+
 			Application.Driver = null;
 			Application.Driver = null;
 
 
 			Application.Iteration = () => {
 			Application.Iteration = () => {
@@ -333,8 +333,8 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		[Fact]
 		public void Run_T_NoInit_DoesNotThrow ()
 		public void Run_T_NoInit_DoesNotThrow ()
 		{
 		{
-			Application.ForceFakeConsole = true; 
-			
+			Application.ForceFakeConsole = true;
+
 			Application.Iteration = () => {
 			Application.Iteration = () => {
 				Application.RequestStop ();
 				Application.RequestStop ();
 			};
 			};
@@ -430,7 +430,7 @@ namespace Terminal.Gui.Core {
 		}
 		}
 
 
 		// TODO: Add tests for Run that test errorHandler
 		// TODO: Add tests for Run that test errorHandler
-		
+
 		#endregion
 		#endregion
 
 
 		#region ShutdownTests
 		#region ShutdownTests

+ 23 - 45
UnitTests/MainLoopTests.cs → UnitTests/Application/MainLoopTests.cs

@@ -13,7 +13,7 @@ using Xunit.Sdk;
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	/// <summary>
 	/// <summary>
 	/// Tests MainLoop using the FakeMainLoop.
 	/// Tests MainLoop using the FakeMainLoop.
 	/// </summary>
 	/// </summary>
@@ -171,9 +171,7 @@ namespace Terminal.Gui.Core {
 			var functionCalled = 0;
 			var functionCalled = 0;
 			Func<bool> fn1 = () => {
 			Func<bool> fn1 = () => {
 				functionCalled++;
 				functionCalled++;
-				if (functionCalled == 10) {
-					return false;
-				}
+				if (functionCalled == 10) 					return false;
 				return true;
 				return true;
 			};
 			};
 
 
@@ -181,9 +179,7 @@ namespace Terminal.Gui.Core {
 			var stopCount = 0;
 			var stopCount = 0;
 			Func<bool> fnStop = () => {
 			Func<bool> fnStop = () => {
 				stopCount++;
 				stopCount++;
-				if (stopCount == 20) {
-					ml.Stop ();
-				}
+				if (stopCount == 20) 					ml.Stop ();
 				return true;
 				return true;
 			};
 			};
 
 
@@ -212,9 +208,7 @@ namespace Terminal.Gui.Core {
 			var stopCount = 0;
 			var stopCount = 0;
 			Func<bool> fnStop = () => {
 			Func<bool> fnStop = () => {
 				stopCount++;
 				stopCount++;
-				if (stopCount == 10) {
-					ml.Stop ();
-				}
+				if (stopCount == 10) 					ml.Stop ();
 				return true;
 				return true;
 			};
 			};
 
 
@@ -237,9 +231,7 @@ namespace Terminal.Gui.Core {
 			var functionCalled = 0;
 			var functionCalled = 0;
 			Func<bool> fn = () => {
 			Func<bool> fn = () => {
 				functionCalled++;
 				functionCalled++;
-				if (functionCalled == 10) {
-					ml.Stop ();
-				}
+				if (functionCalled == 10) 					ml.Stop ();
 				return true;
 				return true;
 			};
 			};
 
 
@@ -258,7 +250,7 @@ namespace Terminal.Gui.Core {
 			var ms = 100;
 			var ms = 100;
 
 
 			var callbackCount = 0;
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				callbackCount++;
 				return true;
 				return true;
 			};
 			};
@@ -279,7 +271,7 @@ namespace Terminal.Gui.Core {
 			var ms = 100;
 			var ms = 100;
 
 
 			var callbackCount = 0;
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				callbackCount++;
 				ml.Stop ();
 				ml.Stop ();
 				return true;
 				return true;
@@ -300,11 +292,9 @@ namespace Terminal.Gui.Core {
 			object token1 = null, token2 = null;
 			object token1 = null, token2 = null;
 
 
 			var callbackCount = 0;
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				callbackCount++;
-				if (callbackCount == 2) {
-					ml.Stop ();
-				}
+				if (callbackCount == 2) 					ml.Stop ();
 				return true;
 				return true;
 			};
 			};
 
 
@@ -332,11 +322,9 @@ namespace Terminal.Gui.Core {
 			object token1 = null, token2 = null;
 			object token1 = null, token2 = null;
 
 
 			var callbackCount = 0;
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				callbackCount++;
-				if (callbackCount == 2) {
-					ml.Stop ();
-				}
+				if (callbackCount == 2) 					ml.Stop ();
 				return true;
 				return true;
 			};
 			};
 
 
@@ -369,7 +357,7 @@ namespace Terminal.Gui.Core {
 			var watch = new System.Diagnostics.Stopwatch ();
 			var watch = new System.Diagnostics.Stopwatch ();
 
 
 			var callbackCount = 0;
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				watch.Stop ();
 				watch.Stop ();
 				callbackCount++;
 				callbackCount++;
 				ml.Stop ();
 				ml.Stop ();
@@ -381,7 +369,7 @@ namespace Terminal.Gui.Core {
 			ml.Run ();
 			ml.Run ();
 			// +/- 100ms should be good enuf
 			// +/- 100ms should be good enuf
 			// https://github.com/xunit/assert.xunit/pull/25
 			// https://github.com/xunit/assert.xunit/pull/25
-			Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
+			Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
 
 
 			Assert.True (ml.RemoveTimeout (token));
 			Assert.True (ml.RemoveTimeout (token));
 			Assert.Equal (1, callbackCount);
 			Assert.Equal (1, callbackCount);
@@ -395,7 +383,7 @@ namespace Terminal.Gui.Core {
 			var watch = new System.Diagnostics.Stopwatch ();
 			var watch = new System.Diagnostics.Stopwatch ();
 
 
 			var callbackCount = 0;
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				callbackCount++;
 				if (callbackCount == 2) {
 				if (callbackCount == 2) {
 					watch.Stop ();
 					watch.Stop ();
@@ -409,7 +397,7 @@ namespace Terminal.Gui.Core {
 			ml.Run ();
 			ml.Run ();
 			// +/- 100ms should be good enuf
 			// +/- 100ms should be good enuf
 			// https://github.com/xunit/assert.xunit/pull/25
 			// https://github.com/xunit/assert.xunit/pull/25
-			Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
+			Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
 
 
 			Assert.True (ml.RemoveTimeout (token));
 			Assert.True (ml.RemoveTimeout (token));
 			Assert.Equal (2, callbackCount);
 			Assert.Equal (2, callbackCount);
@@ -425,15 +413,13 @@ namespace Terminal.Gui.Core {
 			var stopCount = 0;
 			var stopCount = 0;
 			Func<bool> fnStop = () => {
 			Func<bool> fnStop = () => {
 				stopCount++;
 				stopCount++;
-				if (stopCount == 10) {
-					ml.Stop ();
-				}
+				if (stopCount == 10) 					ml.Stop ();
 				return true;
 				return true;
 			};
 			};
 			ml.AddIdle (fnStop);
 			ml.AddIdle (fnStop);
 
 
 			var callbackCount = 0;
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				callbackCount++;
 				return true;
 				return true;
 			};
 			};
@@ -455,15 +441,13 @@ namespace Terminal.Gui.Core {
 			Func<bool> fnStop = () => {
 			Func<bool> fnStop = () => {
 				Thread.Sleep (10); // Sleep to enable timer to fire
 				Thread.Sleep (10); // Sleep to enable timer to fire
 				stopCount++;
 				stopCount++;
-				if (stopCount == 10) {
-					ml.Stop ();
-				}
+				if (stopCount == 10) 					ml.Stop ();
 				return true;
 				return true;
 			};
 			};
 			ml.AddIdle (fnStop);
 			ml.AddIdle (fnStop);
 
 
 			var callbackCount = 0;
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				callbackCount++;
 				return false;
 				return false;
 			};
 			};
@@ -541,10 +525,8 @@ namespace Terminal.Gui.Core {
 				Application.MainLoop.Invoke (() => {
 				Application.MainLoop.Invoke (() => {
 					tf.Text = $"index{r.Next ()}";
 					tf.Text = $"index{r.Next ()}";
 					Interlocked.Increment (ref tbCounter);
 					Interlocked.Increment (ref tbCounter);
-					if (target == tbCounter) {
-						// On last increment wake up the check
+					if (target == tbCounter) 						// On last increment wake up the check
 						_wakeUp.Set ();
 						_wakeUp.Set ();
-					}
 				});
 				});
 			});
 			});
 		}
 		}
@@ -554,9 +536,7 @@ namespace Terminal.Gui.Core {
 			for (int j = 0; j < numPasses; j++) {
 			for (int j = 0; j < numPasses; j++) {
 
 
 				_wakeUp.Reset ();
 				_wakeUp.Reset ();
-				for (var i = 0; i < numIncrements; i++) {
-					Launch (r, tf, (j + 1) * numIncrements);
-				}
+				for (var i = 0; i < numIncrements; i++) 					Launch (r, tf, (j + 1) * numIncrements);
 
 
 
 
 				while (tbCounter != (j + 1) * numIncrements) // Wait for tbCounter to reach expected value
 				while (tbCounter != (j + 1) * numIncrements) // Wait for tbCounter to reach expected value
@@ -594,7 +574,7 @@ namespace Terminal.Gui.Core {
 
 
 			await task; // Propagate exception if any occurred
 			await task; // Propagate exception if any occurred
 
 
-			Assert.Equal ((numIncrements * numPasses), tbCounter);
+			Assert.Equal (numIncrements * numPasses, tbCounter);
 		}
 		}
 
 
 		private static int total;
 		private static int total;
@@ -652,9 +632,7 @@ namespace Terminal.Gui.Core {
 					Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null)));
 					Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null)));
 					Assert.Equal (cancel, btn.Text);
 					Assert.Equal (cancel, btn.Text);
 					Assert.Equal (one, total);
 					Assert.Equal (one, total);
-				} else if (taskCompleted) {
-					Application.RequestStop ();
-				}
+				} else if (taskCompleted) 					Application.RequestStop ();
 			};
 			};
 
 
 			Application.Run ();
 			Application.Run ();

+ 4 - 5
UnitTests/RunStateTests.cs → UnitTests/Application/RunStateTests.cs

@@ -3,12 +3,13 @@ using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using Terminal.Gui;
 using Xunit;
 using Xunit;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	/// <summary>
 	/// <summary>
 	/// These tests focus on Application.RunState and the various ways it can be changed.
 	/// These tests focus on Application.RunState and the various ways it can be changed.
 	/// </summary>
 	/// </summary>
@@ -26,7 +27,7 @@ namespace Terminal.Gui.Core {
 		{
 		{
 			var rs = new Application.RunState (null);
 			var rs = new Application.RunState (null);
 			Assert.Null (rs.Toplevel);
 			Assert.Null (rs.Toplevel);
-			
+
 			var top = new Toplevel ();
 			var top = new Toplevel ();
 			rs = new Application.RunState (top);
 			rs = new Application.RunState (top);
 			Assert.Equal (top, rs.Toplevel);
 			Assert.Equal (top, rs.Toplevel);
@@ -72,9 +73,7 @@ namespace Terminal.Gui.Core {
 			Application.Shutdown ();
 			Application.Shutdown ();
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
 			// Validate there are no outstanding RunState-based instances left
 			// Validate there are no outstanding RunState-based instances left
-			foreach (var inst in Application.RunState.Instances) {
-				Assert.True (inst.WasDisposed);
-			}
+			foreach (var inst in Application.RunState.Instances) 				Assert.True (inst.WasDisposed);
 #endif
 #endif
 		}
 		}
 
 

+ 1 - 1
UnitTests/StackExtensionsTests.cs → UnitTests/Application/StackExtensionsTests.cs

@@ -2,7 +2,7 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	public class StackExtensionsTests {
 	public class StackExtensionsTests {
 		[Fact]
 		[Fact]
 		public void Stack_Toplevels_CreateToplevels ()
 		public void Stack_Toplevels_CreateToplevels ()

+ 1 - 1
UnitTests/SynchronizatonContextTests.cs → UnitTests/Application/SynchronizatonContextTests.cs

@@ -13,7 +13,7 @@ using Xunit.Sdk;
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	public class SyncrhonizationContextTests {
 	public class SyncrhonizationContextTests {
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]

+ 0 - 842
UnitTests/ConsoleDriverTests.cs

@@ -1,842 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using Terminal.Gui.Views;
-using Xunit;
-using Xunit.Abstractions;
-
-// Alias Console to MockConsole so we don't accidentally use Console
-using Console = Terminal.Gui.FakeConsole;
-
-namespace Terminal.Gui.ConsoleDrivers {
-	public class ConsoleDriverTests {
-		readonly ITestOutputHelper output;
-
-		public ConsoleDriverTests (ITestOutputHelper output)
-		{
-			this.output = output;
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		//[InlineData (typeof (NetDriver))]
-		//[InlineData (typeof (CursesDriver))]
-		//[InlineData (typeof (WindowsDriver))]
-		public void Init_Inits (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-			driver.Init (() => { });
-
-			Assert.Equal (80, Console.BufferWidth);
-			Assert.Equal (25, Console.BufferHeight);
-
-			// MockDriver is always 80x25
-			Assert.Equal (Console.BufferWidth, driver.Cols);
-			Assert.Equal (Console.BufferHeight, driver.Rows);
-			driver.End ();
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		//[InlineData (typeof (NetDriver))]
-		//[InlineData (typeof (CursesDriver))]
-		//[InlineData (typeof (WindowsDriver))]
-		public void End_Cleans_Up (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-			driver.Init (() => { });
-
-			FakeConsole.ForegroundColor = ConsoleColor.Red;
-			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
-
-			FakeConsole.BackgroundColor = ConsoleColor.Green;
-			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
-			driver.Move (2, 3);
-			Assert.Equal (2, Console.CursorLeft);
-			Assert.Equal (3, Console.CursorTop);
-
-			driver.End ();
-			Assert.Equal (0, Console.CursorLeft);
-			Assert.Equal (0, Console.CursorTop);
-			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
-			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		//[InlineData (typeof (NetDriver))]
-		//[InlineData (typeof (CursesDriver))]
-		//[InlineData (typeof (WindowsDriver))]
-		public void SetColors_Changes_Colors (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-			driver.Init (() => { });
-			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
-			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
-
-			Console.ForegroundColor = ConsoleColor.Red;
-			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
-
-			Console.BackgroundColor = ConsoleColor.Green;
-			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
-
-			Console.ResetColor ();
-			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
-			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
-			driver.End ();
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			var top = Application.Top;
-			var view = new View ();
-			var count = 0;
-			var wasKeyPressed = false;
-
-			view.KeyPress += (e) => {
-				wasKeyPressed = true;
-			};
-			top.Add (view);
-
-			Application.Iteration += () => {
-				count++;
-				if (count == 10) {
-					Application.RequestStop ();
-				}
-			};
-
-			Application.Run ();
-
-			Assert.False (wasKeyPressed);
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void FakeDriver_MockKeyPresses (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			var text = "MockKeyPresses";
-			var mKeys = new Stack<ConsoleKeyInfo> ();
-			foreach (var r in text.Reverse ()) {
-				var ck = char.IsLetter (r) ? (ConsoleKey)char.ToUpper (r) : (ConsoleKey)r;
-				var cki = new ConsoleKeyInfo (r, ck, false, false, false);
-				mKeys.Push (cki);
-			}
-			FakeConsole.MockKeyPresses = mKeys;
-
-			var top = Application.Top;
-			var view = new View ();
-			var rText = "";
-			var idx = 0;
-
-			view.KeyPress += (e) => {
-				Assert.Equal (text [idx], (char)e.KeyEvent.Key);
-				rText += (char)e.KeyEvent.Key;
-				Assert.Equal (rText, text.Substring (0, idx + 1));
-				e.Handled = true;
-				idx++;
-			};
-			top.Add (view);
-
-			Application.Iteration += () => {
-				if (mKeys.Count == 0) {
-					Application.RequestStop ();
-				}
-			};
-
-			Application.Run ();
-
-			Assert.Equal ("MockKeyPresses", rText);
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void SendKeys_Test (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			var top = Application.Top;
-			var view = new View ();
-			var shift = false; var alt = false; var control = false;
-			Key key = default;
-			Key lastKey = default;
-			List<Key> keyEnums = GetKeys ();
-			int i = 0;
-			int idxKey = 0;
-			var PushIterations = 0;
-			var PopIterations = 0;
-
-			List<Key> GetKeys ()
-			{
-				List<Key> keys = new List<Key> ();
-
-				foreach (Key k in Enum.GetValues (typeof (Key))) {
-					if ((uint)k <= 0xff) {
-						keys.Add (k);
-					} else if ((uint)k > 0xff) {
-						break;
-					}
-				}
-
-				return keys;
-			}
-
-			view.KeyPress += (e) => {
-				e.Handled = true;
-				PopIterations++;
-				var rMk = new KeyModifiers () {
-					Shift = e.KeyEvent.IsShift,
-					Alt = e.KeyEvent.IsAlt,
-					Ctrl = e.KeyEvent.IsCtrl
-				};
-				lastKey = ShortcutHelper.GetModifiersKey (new KeyEvent (e.KeyEvent.Key, rMk));
-				Assert.Equal (key, lastKey);
-			};
-			top.Add (view);
-
-			Application.Iteration += () => {
-				switch (i) {
-				case 0:
-					SendKeys ();
-					break;
-				case 1:
-					shift = true;
-					SendKeys ();
-					break;
-				case 2:
-					alt = true;
-					SendKeys ();
-					break;
-				case 3:
-					control = true;
-					SendKeys ();
-					break;
-				}
-				if (PushIterations == keyEnums.Count * 4) {
-					Application.RequestStop ();
-				}
-			};
-
-			void SendKeys ()
-			{
-				var k = shift && char.IsLetter ((char)keyEnums [idxKey]) && char.IsLower ((char)keyEnums [idxKey])
-					? (Key)char.ToUpper ((char)keyEnums [idxKey]) : keyEnums [idxKey];
-				var c = (char)k;
-				var ck = char.IsLetter (c) ? (ConsoleKey)char.ToUpper (c) : (ConsoleKey)c;
-				var mk = new KeyModifiers () {
-					Shift = shift,
-					Alt = alt,
-					Ctrl = control
-				};
-				key = ShortcutHelper.GetModifiersKey (new KeyEvent (k, mk));
-				Application.Driver.SendKeys (c, ck, shift, alt, control);
-				PushIterations++;
-				if (idxKey + 1 < keyEnums.Count) {
-					idxKey++;
-				} else {
-					idxKey = 0;
-					i++;
-				}
-			}
-
-			Application.Run ();
-
-			Assert.Equal (key, lastKey);
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void TerminalResized_Simulation (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-			var wasTerminalResized = false;
-			Application.Resized = (e) => {
-				wasTerminalResized = true;
-				Assert.Equal (120, e.Cols);
-				Assert.Equal (40, e.Rows);
-			};
-
-			Assert.Equal (80, Console.BufferWidth);
-			Assert.Equal (25, Console.BufferHeight);
-
-			// MockDriver is by default 80x25
-			Assert.Equal (Console.BufferWidth, driver.Cols);
-			Assert.Equal (Console.BufferHeight, driver.Rows);
-			Assert.False (wasTerminalResized);
-
-			// MockDriver will now be sets to 120x40
-			driver.SetBufferSize (120, 40);
-			Assert.Equal (120, Application.Driver.Cols);
-			Assert.Equal (40, Application.Driver.Rows);
-			Assert.True (wasTerminalResized);
-
-			// MockDriver will still be 120x40
-			wasTerminalResized = false;
-			Application.HeightAsBuffer = true;
-			driver.SetWindowSize (40, 20);
-			Assert.Equal (120, Application.Driver.Cols);
-			Assert.Equal (40, Application.Driver.Rows);
-			Assert.Equal (120, Console.BufferWidth);
-			Assert.Equal (40, Console.BufferHeight);
-			Assert.Equal (40, Console.WindowWidth);
-			Assert.Equal (20, Console.WindowHeight);
-			Assert.True (wasTerminalResized);
-
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void HeightAsBuffer_Is_False_Left_And_Top_Is_Always_Zero (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			Assert.False (Application.HeightAsBuffer);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			driver.SetWindowPosition (5, 5);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void HeightAsBuffer_Is_True_Left_Cannot_Be_Greater_Than_WindowWidth (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			Application.HeightAsBuffer = true;
-			Assert.True (Application.HeightAsBuffer);
-
-			driver.SetWindowPosition (81, 25);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void HeightAsBuffer_Is_True_Left_Cannot_Be_Greater_Than_BufferWidth_Minus_WindowWidth (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			Application.HeightAsBuffer = true;
-			Assert.True (Application.HeightAsBuffer);
-
-			driver.SetWindowPosition (81, 25);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			// MockDriver will now be sets to 120x25
-			driver.SetBufferSize (120, 25);
-			Assert.Equal (120, Application.Driver.Cols);
-			Assert.Equal (25, Application.Driver.Rows);
-			Assert.Equal (120, Console.BufferWidth);
-			Assert.Equal (25, Console.BufferHeight);
-			Assert.Equal (80, Console.WindowWidth);
-			Assert.Equal (25, Console.WindowHeight);
-			driver.SetWindowPosition (121, 25);
-			Assert.Equal (40, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			driver.SetWindowSize (90, 25);
-			Assert.Equal (120, Application.Driver.Cols);
-			Assert.Equal (25, Application.Driver.Rows);
-			Assert.Equal (120, Console.BufferWidth);
-			Assert.Equal (25, Console.BufferHeight);
-			Assert.Equal (90, Console.WindowWidth);
-			Assert.Equal (25, Console.WindowHeight);
-			driver.SetWindowPosition (121, 25);
-			Assert.Equal (30, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void HeightAsBuffer_Is_True_Top_Cannot_Be_Greater_Than_WindowHeight (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			Application.HeightAsBuffer = true;
-			Assert.True (Application.HeightAsBuffer);
-
-			driver.SetWindowPosition (80, 26);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void HeightAsBuffer_Is_True_Top_Cannot_Be_Greater_Than_BufferHeight_Minus_WindowHeight (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			Application.HeightAsBuffer = true;
-			Assert.True (Application.HeightAsBuffer);
-
-			driver.SetWindowPosition (80, 26);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			// MockDriver will now be sets to 80x40
-			driver.SetBufferSize (80, 40);
-			Assert.Equal (80, Application.Driver.Cols);
-			Assert.Equal (40, Application.Driver.Rows);
-			Assert.Equal (80, Console.BufferWidth);
-			Assert.Equal (40, Console.BufferHeight);
-			Assert.Equal (80, Console.WindowWidth);
-			Assert.Equal (25, Console.WindowHeight);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-			driver.SetWindowPosition (80, 40);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (15, Console.WindowTop);
-
-			driver.SetWindowSize (80, 20);
-			Assert.Equal (80, Application.Driver.Cols);
-			Assert.Equal (40, Application.Driver.Rows);
-			Assert.Equal (80, Console.BufferWidth);
-			Assert.Equal (40, Console.BufferHeight);
-			Assert.Equal (80, Console.WindowWidth);
-			Assert.Equal (20, Console.WindowHeight);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (15, Console.WindowTop);
-			driver.SetWindowPosition (80, 41);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (20, Console.WindowTop);
-
-			Application.Shutdown ();
-		}
-
-		[Fact]
-		public void Internal_Tests ()
-		{
-			var cs = new ColorScheme ();
-			Assert.Equal ("", cs.caller);
-		}
-
-		[Fact]
-		[AutoInitShutdown]
-		public void KeyModifiers_Resetting_At_New_Keystrokes ()
-		{
-			bool? okInitialFocused = null;
-			bool? cancelInitialFocused = null;
-			var okClicked = false;
-			var closing = false;
-			var cursorRight = false;
-			var endingKeyPress = false;
-			var closed = false;
-
-			var top = Application.Top;
-
-			var ok = new Button ("Ok");
-			ok.Clicked += () => {
-				if (!okClicked) {
-					okClicked = true;
-					Application.RequestStop ();
-				}
-			};
-
-			var cancel = new Button ("Cancel");
-
-			var d = new Dialog ("Quit", cancel, ok);
-			d.KeyPress += (e) => {
-				if (e.KeyEvent.Key == (Key.Q | Key.CtrlMask)) {
-					if (!okClicked && !closing) {
-						okInitialFocused = ok.HasFocus;
-						cancelInitialFocused = cancel.HasFocus;
-						closing = true;
-						var mKeys = new Stack<ConsoleKeyInfo> ();
-						var cki = new ConsoleKeyInfo ('\0', ConsoleKey.Enter, false, false, false);
-						mKeys.Push (cki);
-						cki = new ConsoleKeyInfo ('\0', ConsoleKey.RightArrow, false, false, false);
-						mKeys.Push (cki);
-						FakeConsole.MockKeyPresses = mKeys;
-					}
-					e.Handled = true;
-				} else if (e.KeyEvent.Key == Key.CursorRight) {
-					if (!cursorRight) {
-						cursorRight = true;
-					} else if (ok.HasFocus) {
-						e.Handled = endingKeyPress = true;
-					}
-				}
-			};
-			d.Loaded += () => {
-				var mKeys = new Stack<ConsoleKeyInfo> ();
-				var cki = new ConsoleKeyInfo ('q', ConsoleKey.Q, false, false, true);
-				mKeys.Push (cki);
-				FakeConsole.MockKeyPresses = mKeys;
-			};
-			d.Closed += (_) => {
-				if (okClicked && closing) {
-					closed = true;
-				}
-			};
-
-			top.Ready += () => Application.Run (d);
-
-			Application.Iteration += () => {
-				if (closed) {
-					Application.RequestStop ();
-				}
-			};
-
-			Application.Run ();
-
-			Assert.False (okInitialFocused);
-			Assert.True (cancelInitialFocused);
-			Assert.True (okClicked);
-			Assert.True (closing);
-			Assert.True (cursorRight);
-			Assert.True (endingKeyPress);
-			Assert.True (closed);
-			Assert.Empty (FakeConsole.MockKeyPresses);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AddRune_On_Clip_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
-		{
-			var tv = new TextView () {
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-				Text = @"これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。"
-			};
-			var win = new Window ("ワイドルーン") { Width = Dim.Fill (), Height = Dim.Fill () };
-			win.Add (tv);
-			Application.Top.Add (win);
-			var lbl = new Label ("ワイドルーン。");
-			var dg = new Dialog ("テスト", 14, 4, new Button ("選ぶ"));
-			dg.Add (lbl);
-			Application.Begin (Application.Top);
-			Application.Begin (dg);
-			((FakeDriver)Application.Driver).SetBufferSize (30, 10);
-
-			var expected = @"
-┌ ワイドルーン ──────────────┐
-│これは広いルーンラインです。│
-│これは広いルーンラインです。│
-│これは ┌ テスト ────┐ です。│
-│これは │ワイドルーン│ です。│
-│これは │  [ 選ぶ ]  │ です。│
-│これは └────────────┘ です。│
-│これは広いルーンラインです。│
-│これは広いルーンラインです。│
-└────────────────────────────┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 30, 10), pos);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void Write_Do_Not_Change_On_ProcessKey ()
-		{
-			var win = new Window ();
-			Application.Begin (win);
-			((FakeDriver)Application.Driver).SetBufferSize (20, 8);
-
-			System.Threading.Tasks.Task.Run (() => {
-				System.Threading.Tasks.Task.Delay (500).Wait ();
-				Application.MainLoop.Invoke (() => {
-					var lbl = new Label ("Hello World") { X = Pos.Center () };
-					var dlg = new Dialog ("Test", new Button ("Ok"));
-					dlg.Add (lbl);
-					Application.Begin (dlg);
-
-					var expected = @"
-┌──────────────────┐
-│┌ Test ─────────┐ │
-││  Hello World  │ │
-││               │ │
-││               │ │
-││    [ Ok ]     │ │
-│└───────────────┘ │
-└──────────────────┘
-";
-
-					var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-					Assert.Equal (new Rect (0, 0, 20, 8), pos);
-
-					Assert.True (dlg.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())));
-					dlg.Redraw (dlg.Bounds);
-
-					expected = @"
-┌──────────────────┐
-│┌ Test ─────────┐ │
-││  Hello World  │ │
-││               │ │
-││               │ │
-││    [ Ok ]     │ │
-│└───────────────┘ │
-└──────────────────┘
-";
-
-					pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-					Assert.Equal (new Rect (0, 0, 20, 8), pos);
-
-					win.RequestStop ();
-				});
-			});
-
-			Application.Run (win);
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (0x0000001F, 0x241F)]
-		[InlineData (0x0000007F, 0x247F)]
-		[InlineData (0x0000009F, 0x249F)]
-		[InlineData (0x0001001A, 0x1001A)]
-		public void MakePrintable_Converts_Control_Chars_To_Proper_Unicode (uint code, uint expected)
-		{
-			var actual = ConsoleDriver.MakePrintable (code);
-
-			Assert.Equal (expected, actual.Value);
-		}
-
-		[Theory]
-		[InlineData (0x20)]
-		[InlineData (0x7E)]
-		[InlineData (0xA0)]
-		[InlineData (0x010020)]
-		public void MakePrintable_Does_Not_Convert_Ansi_Chars_To_Unicode (uint code)
-		{
-			var actual = ConsoleDriver.MakePrintable (code);
-
-			Assert.Equal (code, actual.Value);
-		}
-
-		/// <summary>
-		/// Sometimes when using remote tools EventKeyRecord sends 'virtual keystrokes'.
-		/// These are indicated with the wVirtualKeyCode of 231. When we see this code
-		/// then we need to look to the unicode character (UnicodeChar) instead of the key
-		/// when telling the rest of the framework what button was pressed. For full details
-		/// see: https://github.com/gui-cs/Terminal.Gui/issues/2008
-		/// </summary>
-		[Theory, AutoInitShutdown]
-		[ClassData (typeof (PacketTest))]
-		public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey, uint initialScanCode, Key expectedRemapping, uint expectedVirtualKey, uint expectedScanCode)
-		{
-			ConsoleModifiers modifiers = new ConsoleModifiers ();
-			if (shift) {
-				modifiers |= ConsoleModifiers.Shift;
-			}
-			if (alt) {
-				modifiers |= ConsoleModifiers.Alt;
-			}
-			if (control) {
-				modifiers |= ConsoleModifiers.Control;
-			}
-			var mappedConsoleKey = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode, out uint outputChar);
-
-			if ((scanCode > 0 || mappedConsoleKey == 0) && mappedConsoleKey == initialVirtualKey) {
-				Assert.Equal (mappedConsoleKey, initialVirtualKey);
-			} else {
-				Assert.Equal (mappedConsoleKey, outputChar < 0xff ? (uint)(outputChar & 0xff | 0xff << 8) : outputChar);
-			}
-			Assert.Equal (scanCode, initialScanCode);
-
-			var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (mappedConsoleKey, modifiers, out uint consoleKey, out scanCode);
-
-			//if (scanCode > 0 && consoleKey == keyChar && consoleKey > 48 && consoleKey > 57 && consoleKey < 65 && consoleKey > 91) {
-			if (scanCode > 0 && keyChar == 0 && consoleKey == mappedConsoleKey) {
-				Assert.Equal (0, (double)keyChar);
-			} else {
-				Assert.Equal (keyChar, unicodeCharacter);
-			}
-			Assert.Equal (consoleKey, expectedVirtualKey);
-			Assert.Equal (scanCode, expectedScanCode);
-
-			var top = Application.Top;
-
-			top.KeyPress += (e) => {
-				var after = ShortcutHelper.GetModifiersKey (e.KeyEvent);
-				Assert.Equal (expectedRemapping, after);
-				e.Handled = true;
-				Application.RequestStop ();
-			};
-
-			var iterations = -1;
-
-			Application.Iteration += () => {
-				iterations++;
-				if (iterations == 0) {
-					Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control);
-				}
-			};
-
-			Application.Run ();
-			Application.Shutdown ();
-		}
-
-		public class PacketTest : IEnumerable, IEnumerable<object []> {
-			public IEnumerator<object []> GetEnumerator ()
-			{
-				yield return new object [] { 'a', false, false, false, 'A', 30, Key.a, 'A', 30 };
-				yield return new object [] { 'A', true, false, false, 'A', 30, Key.A | Key.ShiftMask, 'A', 30 };
-				yield return new object [] { 'A', true, true, false, 'A', 30, Key.A | Key.ShiftMask | Key.AltMask, 'A', 30 };
-				yield return new object [] { 'A', true, true, true, 'A', 30, Key.A | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 'A', 30 };
-				yield return new object [] { 'z', false, false, false, 'Z', 44, Key.z, 'Z', 44 };
-				yield return new object [] { 'Z', true, false, false, 'Z', 44, Key.Z | Key.ShiftMask, 'Z', 44 };
-				yield return new object [] { 'Z', true, true, false, 'Z', 44, Key.Z | Key.ShiftMask | Key.AltMask, 'Z', 44 };
-				yield return new object [] { 'Z', true, true, true, 'Z', 44, Key.Z | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 'Z', 44 };
-				yield return new object [] { '英', false, false, false, '\0', 0, (Key)'英', '\0', 0 };
-				yield return new object [] { '英', true, false, false, '\0', 0, (Key)'英' | Key.ShiftMask, '\0', 0 };
-				yield return new object [] { '英', true, true, false, '\0', 0, (Key)'英' | Key.ShiftMask | Key.AltMask, '\0', 0 };
-				yield return new object [] { '英', true, true, true, '\0', 0, (Key)'英' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '\0', 0 };
-				yield return new object [] { '+', false, false, false, 187, 26, (Key)'+', 187, 26 };
-				yield return new object [] { '*', true, false, false, 187, 26, (Key)'*' | Key.ShiftMask, 187, 26 };
-				yield return new object [] { '+', true, true, false, 187, 26, (Key)'+' | Key.ShiftMask | Key.AltMask, 187, 26 };
-				yield return new object [] { '+', true, true, true, 187, 26, (Key)'+' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 187, 26 };
-				yield return new object [] { '1', false, false, false, '1', 2, Key.D1, '1', 2 };
-				yield return new object [] { '!', true, false, false, '1', 2, (Key)'!' | Key.ShiftMask, '1', 2 };
-				yield return new object [] { '1', true, true, false, '1', 2, Key.D1 | Key.ShiftMask | Key.AltMask, '1', 2 };
-				yield return new object [] { '1', true, true, true, '1', 2, Key.D1 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '1', 2 };
-				yield return new object [] { '1', false, true, true, '1', 2, Key.D1 | Key.AltMask | Key.CtrlMask, '1', 2 };
-				yield return new object [] { '2', false, false, false, '2', 3, Key.D2, '2', 3 };
-				yield return new object [] { '"', true, false, false, '2', 3, (Key)'"' | Key.ShiftMask, '2', 3 };
-				yield return new object [] { '2', true, true, false, '2', 3, Key.D2 | Key.ShiftMask | Key.AltMask, '2', 3 };
-				yield return new object [] { '2', true, true, true, '2', 3, Key.D2 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '2', 3 };
-				yield return new object [] { '@', false, true, true, '2', 3, (Key)'@' | Key.AltMask | Key.CtrlMask, '2', 3 };
-				yield return new object [] { '3', false, false, false, '3', 4, Key.D3, '3', 4 };
-				yield return new object [] { '#', true, false, false, '3', 4, (Key)'#' | Key.ShiftMask, '3', 4 };
-				yield return new object [] { '3', true, true, false, '3', 4, Key.D3 | Key.ShiftMask | Key.AltMask, '3', 4 };
-				yield return new object [] { '3', true, true, true, '3', 4, Key.D3 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '3', 4 };
-				yield return new object [] { '£', false, true, true, '3', 4, (Key)'£' | Key.AltMask | Key.CtrlMask, '3', 4 };
-				yield return new object [] { '4', false, false, false, '4', 5, Key.D4, '4', 5 };
-				yield return new object [] { '$', true, false, false, '4', 5, (Key)'$' | Key.ShiftMask, '4', 5 };
-				yield return new object [] { '4', true, true, false, '4', 5, Key.D4 | Key.ShiftMask | Key.AltMask, '4', 5 };
-				yield return new object [] { '4', true, true, true, '4', 5, Key.D4 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '4', 5 };
-				yield return new object [] { '§', false, true, true, '4', 5, (Key)'§' | Key.AltMask | Key.CtrlMask, '4', 5 };
-				yield return new object [] { '5', false, false, false, '5', 6, Key.D5, '5', 6 };
-				yield return new object [] { '%', true, false, false, '5', 6, (Key)'%' | Key.ShiftMask, '5', 6 };
-				yield return new object [] { '5', true, true, false, '5', 6, Key.D5 | Key.ShiftMask | Key.AltMask, '5', 6 };
-				yield return new object [] { '5', true, true, true, '5', 6, Key.D5 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '5', 6 };
-				yield return new object [] { '€', false, true, true, '5', 6, (Key)'€' | Key.AltMask | Key.CtrlMask, '5', 6 };
-				yield return new object [] { '6', false, false, false, '6', 7, Key.D6, '6', 7 };
-				yield return new object [] { '&', true, false, false, '6', 7, (Key)'&' | Key.ShiftMask, '6', 7 };
-				yield return new object [] { '6', true, true, false, '6', 7, Key.D6 | Key.ShiftMask | Key.AltMask, '6', 7 };
-				yield return new object [] { '6', true, true, true, '6', 7, Key.D6 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '6', 7 };
-				yield return new object [] { '6', false, true, true, '6', 7, Key.D6 | Key.AltMask | Key.CtrlMask, '6', 7 };
-				yield return new object [] { '7', false, false, false, '7', 8, Key.D7, '7', 8 };
-				yield return new object [] { '/', true, false, false, '7', 8, (Key)'/' | Key.ShiftMask, '7', 8 };
-				yield return new object [] { '7', true, true, false, '7', 8, Key.D7 | Key.ShiftMask | Key.AltMask, '7', 8 };
-				yield return new object [] { '7', true, true, true, '7', 8, Key.D7 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '7', 8 };
-				yield return new object [] { '{', false, true, true, '7', 8, (Key)'{' | Key.AltMask | Key.CtrlMask, '7', 8 };
-				yield return new object [] { '8', false, false, false, '8', 9, Key.D8, '8', 9 };
-				yield return new object [] { '(', true, false, false, '8', 9, (Key)'(' | Key.ShiftMask, '8', 9 };
-				yield return new object [] { '8', true, true, false, '8', 9, Key.D8 | Key.ShiftMask | Key.AltMask, '8', 9 };
-				yield return new object [] { '8', true, true, true, '8', 9, Key.D8 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '8', 9 };
-				yield return new object [] { '[', false, true, true, '8', 9, (Key)'[' | Key.AltMask | Key.CtrlMask, '8', 9 };
-				yield return new object [] { '9', false, false, false, '9', 10, Key.D9, '9', 10 };
-				yield return new object [] { ')', true, false, false, '9', 10, (Key)')' | Key.ShiftMask, '9', 10 };
-				yield return new object [] { '9', true, true, false, '9', 10, Key.D9 | Key.ShiftMask | Key.AltMask, '9', 10 };
-				yield return new object [] { '9', true, true, true, '9', 10, Key.D9 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '9', 10 };
-				yield return new object [] { ']', false, true, true, '9', 10, (Key)']' | Key.AltMask | Key.CtrlMask, '9', 10 };
-				yield return new object [] { '0', false, false, false, '0', 11, Key.D0, '0', 11 };
-				yield return new object [] { '=', true, false, false, '0', 11, (Key)'=' | Key.ShiftMask, '0', 11 };
-				yield return new object [] { '0', true, true, false, '0', 11, Key.D0 | Key.ShiftMask | Key.AltMask, '0', 11 };
-				yield return new object [] { '0', true, true, true, '0', 11, Key.D0 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '0', 11 };
-				yield return new object [] { '}', false, true, true, '0', 11, (Key)'}' | Key.AltMask | Key.CtrlMask, '0', 11 };
-				yield return new object [] { '\'', false, false, false, 219, 12, (Key)'\'', 219, 12 };
-				yield return new object [] { '?', true, false, false, 219, 12, (Key)'?' | Key.ShiftMask, 219, 12 };
-				yield return new object [] { '\'', true, true, false, 219, 12, (Key)'\'' | Key.ShiftMask | Key.AltMask, 219, 12 };
-				yield return new object [] { '\'', true, true, true, 219, 12, (Key)'\'' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 219, 12 };
-				yield return new object [] { '«', false, false, false, 221, 13, (Key)'«', 221, 13 };
-				yield return new object [] { '»', true, false, false, 221, 13, (Key)'»' | Key.ShiftMask, 221, 13 };
-				yield return new object [] { '«', true, true, false, 221, 13, (Key)'«' | Key.ShiftMask | Key.AltMask, 221, 13 };
-				yield return new object [] { '«', true, true, true, 221, 13, (Key)'«' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 221, 13 };
-				yield return new object [] { 'á', false, false, false, 'á', 0, (Key)'á', 'A', 30 };
-				yield return new object [] { 'Á', true, false, false, 'Á', 0, (Key)'Á' | Key.ShiftMask, 'A', 30 };
-				yield return new object [] { 'à', false, false, false, 'à', 0, (Key)'à', 'A', 30 };
-				yield return new object [] { 'À', true, false, false, 'À', 0, (Key)'À' | Key.ShiftMask, 'A', 30 };
-				yield return new object [] { 'é', false, false, false, 'é', 0, (Key)'é', 'E', 18 };
-				yield return new object [] { 'É', true, false, false, 'É', 0, (Key)'É' | Key.ShiftMask, 'E', 18 };
-				yield return new object [] { 'è', false, false, false, 'è', 0, (Key)'è', 'E', 18 };
-				yield return new object [] { 'È', true, false, false, 'È', 0, (Key)'È' | Key.ShiftMask, 'E', 18 };
-				yield return new object [] { 'í', false, false, false, 'í', 0, (Key)'í', 'I', 23 };
-				yield return new object [] { 'Í', true, false, false, 'Í', 0, (Key)'Í' | Key.ShiftMask, 'I', 23 };
-				yield return new object [] { 'ì', false, false, false, 'ì', 0, (Key)'ì', 'I', 23 };
-				yield return new object [] { 'Ì', true, false, false, 'Ì', 0, (Key)'Ì' | Key.ShiftMask, 'I', 23 };
-				yield return new object [] { 'ó', false, false, false, 'ó', 0, (Key)'ó', 'O', 24 };
-				yield return new object [] { 'Ó', true, false, false, 'Ó', 0, (Key)'Ó' | Key.ShiftMask, 'O', 24 };
-				yield return new object [] { 'ò', false, false, false, 'Ó', 0, (Key)'ò', 'O', 24 };
-				yield return new object [] { 'Ò', true, false, false, 'Ò', 0, (Key)'Ò' | Key.ShiftMask, 'O', 24 };
-				yield return new object [] { 'ú', false, false, false, 'ú', 0, (Key)'ú', 'U', 22 };
-				yield return new object [] { 'Ú', true, false, false, 'Ú', 0, (Key)'Ú' | Key.ShiftMask, 'U', 22 };
-				yield return new object [] { 'ù', false, false, false, 'ù', 0, (Key)'ù', 'U', 22 };
-				yield return new object [] { 'Ù', true, false, false, 'Ù', 0, (Key)'Ù' | Key.ShiftMask, 'U', 22 };
-				yield return new object [] { 'ö', false, false, false, 'ó', 0, (Key)'ö', 'O', 24 };
-				yield return new object [] { 'Ö', true, false, false, 'Ó', 0, (Key)'Ö' | Key.ShiftMask, 'O', 24 };
-				yield return new object [] { '<', false, false, false, 226, 86, (Key)'<', 226, 86 };
-				yield return new object [] { '>', true, false, false, 226, 86, (Key)'>' | Key.ShiftMask, 226, 86 };
-				yield return new object [] { '<', true, true, false, 226, 86, (Key)'<' | Key.ShiftMask | Key.AltMask, 226, 86 };
-				yield return new object [] { '<', true, true, true, 226, 86, (Key)'<' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 226, 86 };
-				yield return new object [] { 'ç', false, false, false, 192, 39, (Key)'ç', 192, 39 };
-				yield return new object [] { 'Ç', true, false, false, 192, 39, (Key)'Ç' | Key.ShiftMask, 192, 39 };
-				yield return new object [] { 'ç', true, true, false, 192, 39, (Key)'ç' | Key.ShiftMask | Key.AltMask, 192, 39 };
-				yield return new object [] { 'ç', true, true, true, 192, 39, (Key)'ç' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 192, 39 };
-				yield return new object [] { '¨', false, true, true, 187, 26, (Key)'¨' | Key.AltMask | Key.CtrlMask, 187, 26 };
-				yield return new object [] { (uint)Key.PageUp, false, false, false, 33, 73, Key.PageUp, 33, 73 };
-				yield return new object [] { (uint)Key.PageUp, true, false, false, 33, 73, Key.PageUp | Key.ShiftMask, 33, 73 };
-				yield return new object [] { (uint)Key.PageUp, true, true, false, 33, 73, Key.PageUp | Key.ShiftMask | Key.AltMask, 33, 73 };
-				yield return new object [] { (uint)Key.PageUp, true, true, true, 33, 73, Key.PageUp | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 33, 73 };
-			}
-
-			IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
-		}
-	}
-}

+ 79 - 17
UnitTests/BorderTests.cs → UnitTests/Core/BorderTests.cs

@@ -1,15 +1,19 @@
 using System;
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Reflection.Emit;
 using Xunit;
 using Xunit;
+using Xunit.Abstractions;
 using Rune = System.Rune;
 using Rune = System.Rune;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.CoreTests {
 	public class BorderTests {
 	public class BorderTests {
-		[Fact]
-		[AutoInitShutdown]
+		readonly ITestOutputHelper output;
+
+		public BorderTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		[Fact, AutoInitShutdown]
 		public void Constructor_Defaults ()
 		public void Constructor_Defaults ()
 		{
 		{
 			var b = new Border ();
 			var b = new Border ();
@@ -45,8 +49,7 @@ namespace Terminal.Gui.Core {
 			Assert.False (b.DrawMarginFrame);
 			Assert.False (b.DrawMarginFrame);
 		}
 		}
 
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void ActualWidth_ActualHeight ()
 		public void ActualWidth_ActualHeight ()
 		{
 		{
 			var v = new View (new Rect (5, 10, 60, 20), "", new Border ());
 			var v = new View (new Rect (5, 10, 60, 20), "", new Border ());
@@ -81,8 +84,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Thickness (5, 5, 5, 5), b.GetSumThickness ());
 			Assert.Equal (new Thickness (5, 5, 5, 5), b.GetSumThickness ());
 		}
 		}
 
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void DrawContent_With_Child_Border ()
 		public void DrawContent_With_Child_Border ()
 		{
 		{
 			var top = Application.Top;
 			var top = Application.Top;
@@ -226,7 +228,12 @@ namespace Terminal.Gui.Core {
 
 
 					var color = (Attribute)driver.Contents [r, c, 1];
 					var color = (Attribute)driver.Contents [r, c, 1];
 					var rune = (Rune)driver.Contents [r, c, 0];
 					var rune = (Rune)driver.Contents [r, c, 0];
-					Assert.Equal (Color.Black, color.Background);
+					if (r == frame.Y - drawMarginFrame || r == frame.Bottom + drawMarginFrame - 1
+						|| c == frame.X - drawMarginFrame || c == frame.Right + drawMarginFrame - 1) {
+						Assert.Equal (Color.BrightGreen, color.Background);
+					} else {
+						Assert.Equal (Color.Black, color.Background);
+					}
 					if (c == frame.X - drawMarginFrame && r == frame.Y - drawMarginFrame) {
 					if (c == frame.X - drawMarginFrame && r == frame.Y - drawMarginFrame) {
 						Assert.Equal (uLCorner, rune);
 						Assert.Equal (uLCorner, rune);
 					} else if (c == frame.Right && r == frame.Y - drawMarginFrame) {
 					} else if (c == frame.Right && r == frame.Y - drawMarginFrame) {
@@ -303,8 +310,7 @@ namespace Terminal.Gui.Core {
 			}
 			}
 		}
 		}
 
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void DrawContent_With_Parent_Border ()
 		public void DrawContent_With_Parent_Border ()
 		{
 		{
 			var top = Application.Top;
 			var top = Application.Top;
@@ -456,7 +462,12 @@ namespace Terminal.Gui.Core {
 
 
 					var color = (Attribute)driver.Contents [r, c, 1];
 					var color = (Attribute)driver.Contents [r, c, 1];
 					var rune = (Rune)driver.Contents [r, c, 0];
 					var rune = (Rune)driver.Contents [r, c, 0];
-					Assert.Equal (Color.Black, color.Background);
+					if (r == frame.Y + sumThickness.Top || r == frame.Bottom - sumThickness.Bottom - 1
+						|| c == frame.X + sumThickness.Left || c == frame.Right - sumThickness.Right - 1) {
+						Assert.Equal (Color.BrightGreen, color.Background);
+					} else {
+						Assert.Equal (Color.Black, color.Background);
+					}
 					if (c == frame.X + sumThickness.Left && r == frame.Y + sumThickness.Top) {
 					if (c == frame.X + sumThickness.Left && r == frame.Y + sumThickness.Top) {
 						Assert.Equal (uLCorner, rune);
 						Assert.Equal (uLCorner, rune);
 					} else if (c == frame.Right - drawMarginFrame - sumThickness.Right
 					} else if (c == frame.Right - drawMarginFrame - sumThickness.Right
@@ -540,8 +551,7 @@ namespace Terminal.Gui.Core {
 			}
 			}
 		}
 		}
 
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void BorderOnControlWithNoChildren ()
 		public void BorderOnControlWithNoChildren ()
 		{
 		{
 			var label = new TextField ("Loading...") {
 			var label = new TextField ("Loading...") {
@@ -557,5 +567,57 @@ namespace Terminal.Gui.Core {
 
 
 			Assert.Null (Record.Exception (() => label.Redraw (label.Bounds)));
 			Assert.Null (Record.Exception (() => label.Redraw (label.Bounds)));
 		}
 		}
+
+		[Fact, AutoInitShutdown]
+		public void BorderStyle_And_DrawMarginFrame_Gets_Sets ()
+		{
+			var lblTop = new Label ("At 0,0");
+			var lblFrame = new Label ("Centered") { X = Pos.Center (), Y = Pos.Center () };
+			var frame = new FrameView () { Y = 1, Width = 20, Height = 3 };
+			var lblFill = new Label () { Width = Dim.Fill(),Height = Dim.Fill(), Visible = false };
+			var fillText = new System.Text.StringBuilder ();
+			for (int i = 0; i < frame.Bounds.Height; i++) {
+				if (i > 0) {
+					fillText.AppendLine ("");
+				}
+				for (int j = 0; j < frame.Bounds.Width; j++) {
+					fillText.Append ("█");
+				}
+			}
+			lblFill.Text = fillText.ToString ();
+			frame.Add (lblFill, lblFrame);
+			var lblBottom = new Label ("At 0,4") { Y = 4 };
+			Application.Top.Add (lblTop, frame, lblBottom);
+			Application.Begin (Application.Top);
+
+			Assert.Equal (BorderStyle.Single, frame.Border.BorderStyle);
+			Assert.True (frame.Border.DrawMarginFrame);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0              
+┌──────────────────┐
+│     Centered     │
+└──────────────────┘
+At 0,4              ", output);
+
+			frame.Border.BorderStyle = BorderStyle.None;
+			Application.Refresh ();
+			Assert.True (frame.Border.DrawMarginFrame);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0        
+              
+      Centered
+              
+At 0,4        ", output);
+
+			frame.Border.DrawMarginFrame = false;
+			lblFill.Visible = true;
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0              
+████████████████████
+██████Centered██████
+████████████████████
+At 0,4              ", output);
+		}
 	}
 	}
 }
 }

+ 78 - 0
UnitTests/Core/EscSeqReqTests.cs

@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Terminal.Gui.CoreTests {
+	public class EscSeqReqTests {
+		[Fact]
+		public void Constructor_Defaults ()
+		{
+			var escSeqReq = new EscSeqReqProc ();
+			Assert.NotNull (escSeqReq.EscSeqReqStats);
+			Assert.Empty (escSeqReq.EscSeqReqStats);
+		}
+
+		[Fact]
+		public void Add_Tests ()
+		{
+			var escSeqReq = new EscSeqReqProc ();
+			escSeqReq.Add ("t");
+			Assert.Single (escSeqReq.EscSeqReqStats);
+			Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating);
+			Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumRequests);
+			Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumOutstanding);
+
+			escSeqReq.Add ("t", 2);
+			Assert.Single (escSeqReq.EscSeqReqStats);
+			Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating);
+			Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumRequests);
+			Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumOutstanding);
+
+			escSeqReq = new EscSeqReqProc ();
+			escSeqReq.Add ("t", 2);
+			Assert.Single (escSeqReq.EscSeqReqStats);
+			Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating);
+			Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumRequests);
+			Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumOutstanding);
+
+			escSeqReq.Add ("t", 3);
+			Assert.Single (escSeqReq.EscSeqReqStats);
+			Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating);
+			Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumRequests);
+			Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumOutstanding);
+		}
+
+		[Fact]
+		public void Remove_Tests ()
+		{
+			var escSeqReq = new EscSeqReqProc ();
+			escSeqReq.Add ("t");
+			escSeqReq.Remove ("t");
+			Assert.Empty (escSeqReq.EscSeqReqStats);
+
+			escSeqReq.Add ("t", 2);
+			escSeqReq.Remove ("t");
+			Assert.Single (escSeqReq.EscSeqReqStats);
+			Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating);
+			Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumRequests);
+			Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumOutstanding);
+
+			escSeqReq.Remove ("t");
+			Assert.Empty (escSeqReq.EscSeqReqStats);
+		}
+
+		[Fact]
+		public void Requested_Tests ()
+		{
+			var escSeqReq = new EscSeqReqProc ();
+			Assert.False (escSeqReq.Requested ("t"));
+
+			escSeqReq.Add ("t");
+			Assert.False (escSeqReq.Requested ("r"));
+			Assert.True (escSeqReq.Requested ("t"));
+		}
+	}
+}

+ 870 - 0
UnitTests/Core/EscSeqUtilsTests.cs

@@ -0,0 +1,870 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Xunit;
+
+namespace Terminal.Gui.CoreTests {
+	public class EscSeqUtilsTests {
+		[Fact]
+		public void Defaults_Values ()
+		{
+			Assert.Equal ('\x1b', EscSeqUtils.KeyEsc);
+			Assert.Equal ("\x1b[", EscSeqUtils.KeyCSI);
+			Assert.Equal ("\x1b[?1003h", EscSeqUtils.CSI_EnableAnyEventMouse);
+			Assert.Equal ("\x1b[?1006h", EscSeqUtils.CSI_EnableSgrExtModeMouse);
+			Assert.Equal ("\x1b[?1015h", EscSeqUtils.CSI_EnableUrxvtExtModeMouse);
+			Assert.Equal ("\x1b[?1003l", EscSeqUtils.CSI_DisableAnyEventMouse);
+			Assert.Equal ("\x1b[?1006l", EscSeqUtils.CSI_DisableSgrExtModeMouse);
+			Assert.Equal ("\x1b[?1015l", EscSeqUtils.CSI_DisableUrxvtExtModeMouse);
+			Assert.Equal ("\x1b[?1003h\x1b[?1015h\u001b[?1006h", EscSeqUtils.EnableMouseEvents);
+			Assert.Equal ("\x1b[?1003l\x1b[?1015l\u001b[?1006l", EscSeqUtils.DisableMouseEvents);
+		}
+
+		[Fact]
+		public void GetConsoleInputKey_ConsoleKeyInfo ()
+		{
+			var cki = new ConsoleKeyInfo ('r', 0, false, false, false);
+			var expectedCki = new ConsoleKeyInfo ('r', 0, false, false, false);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('r', 0, true, false, false);
+			expectedCki = new ConsoleKeyInfo ('r', 0, true, false, false);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('r', 0, false, true, false);
+			expectedCki = new ConsoleKeyInfo ('r', 0, false, true, false);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('r', 0, false, false, true);
+			expectedCki = new ConsoleKeyInfo ('r', 0, false, false, true);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('r', 0, true, true, false);
+			expectedCki = new ConsoleKeyInfo ('r', 0, true, true, false);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('r', 0, false, true, true);
+			expectedCki = new ConsoleKeyInfo ('r', 0, false, true, true);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('r', 0, true, true, true);
+			expectedCki = new ConsoleKeyInfo ('r', 0, true, true, true);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('\u0012', 0, false, false, false);
+			expectedCki = new ConsoleKeyInfo ('R', ConsoleKey.R, false, false, true);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('\0', (ConsoleKey)64, false, false, true);
+			expectedCki = new ConsoleKeyInfo (' ', ConsoleKey.Spacebar, false, false, true);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('\r', 0, false, false, false);
+			expectedCki = new ConsoleKeyInfo ('\r', ConsoleKey.Enter, false, false, false);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('\u007f', 0, false, false, false);
+			expectedCki = new ConsoleKeyInfo ('\u007f', ConsoleKey.Backspace, false, false, false);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+
+			cki = new ConsoleKeyInfo ('R', 0, false, false, false);
+			expectedCki = new ConsoleKeyInfo ('R', 0, false, false, false);
+			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+		}
+
+		[Fact]
+		public void ResizeArray_ConsoleKeyInfo ()
+		{
+			ConsoleKeyInfo [] expectedCkInfos = null;
+			var cki = new ConsoleKeyInfo ('\u001b', ConsoleKey.Escape, false, false, false);
+			expectedCkInfos = EscSeqUtils.ResizeArray (cki, expectedCkInfos);
+			Assert.Single (expectedCkInfos);
+			Assert.Equal (cki, expectedCkInfos [0]);
+		}
+
+		private EscSeqReqProc escSeqReqProc;
+		private ConsoleKeyInfo newConsoleKeyInfo;
+		private ConsoleKey key;
+		private ConsoleKeyInfo [] cki;
+		private ConsoleModifiers mod;
+		private string c1Control, code, terminating;
+		private string [] values;
+		private bool isKeyMouse;
+		private bool isReq;
+		private List<MouseFlags> mouseFlags;
+		Point pos;
+		private MouseFlags arg1;
+		private Point arg2;
+		private bool actionStarted;
+
+		[Fact, AutoInitShutdown]
+		public void DecodeEscSeq_Tests ()
+		{
+			// ESC
+			cki = new ConsoleKeyInfo [] { new ConsoleKeyInfo ('\u001b', 0, false, false, false) };
+			var expectedCki = new ConsoleKeyInfo ('\u001b', ConsoleKey.Escape, false, false, false);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.Escape, key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("ESC", c1Control);
+			Assert.Null (code);
+			Assert.Null (values);
+			Assert.Null (terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('\u0012', 0, false, false, false)
+			};
+			expectedCki = new ConsoleKeyInfo ('\u0012', ConsoleKey.R, false, true, true);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.R, key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("ESC", c1Control);
+			Assert.Null (code);
+			Assert.Null (values);
+			Assert.Equal ("\u0012", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('r', 0, false, false, false)
+			};
+			expectedCki = new ConsoleKeyInfo ('R', ConsoleKey.R, false, true, false);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.R, key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("ESC", c1Control);
+			Assert.Null (code);
+			Assert.Null (values);
+			Assert.Equal ("r", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			// SS3
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('O', 0, false, false, false),
+				new ConsoleKeyInfo ('R', 0, false, false, false)
+			};
+			expectedCki = new ConsoleKeyInfo ('\0', ConsoleKey.F3, false, false, false);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.F3, key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("SS3", c1Control);
+			Assert.Null (code);
+			Assert.Single (values);
+			Assert.Null (values [0]);
+			Assert.Equal ("R", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			// CSI
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('1', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo ('R', 0, false, false, false)
+			};
+			expectedCki = new ConsoleKeyInfo ('\0', ConsoleKey.F3, true, false, false);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.F3, key);
+			Assert.Equal (ConsoleModifiers.Shift, mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Null (code);
+			Assert.Equal (2, values.Length);
+			Assert.Equal ("1", values [0]);
+			Assert.Equal ("2", values [^1]);
+			Assert.Equal ("R", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('1', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('R', 0, false, false, false)
+			};
+			expectedCki = new ConsoleKeyInfo ('\0', ConsoleKey.F3, false, true, false);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.F3, key);
+			Assert.Equal (ConsoleModifiers.Alt, mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Null (code);
+			Assert.Equal (2, values.Length);
+			Assert.Equal ("1", values [0]);
+			Assert.Equal ("3", values [^1]);
+			Assert.Equal ("R", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('1', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('4', 0, false, false, false),
+				new ConsoleKeyInfo ('R', 0, false, false, false)
+			};
+			expectedCki = new ConsoleKeyInfo ('\0', ConsoleKey.F3, true, true, false);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.F3, key);
+			Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt, mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Null (code);
+			Assert.Equal (2, values.Length);
+			Assert.Equal ("1", values [0]);
+			Assert.Equal ("4", values [^1]);
+			Assert.Equal ("R", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('1', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('5', 0, false, false, false),
+				new ConsoleKeyInfo ('R', 0, false, false, false)
+			};
+			expectedCki = new ConsoleKeyInfo ('\0', ConsoleKey.F3, false, false, true);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.F3, key);
+			Assert.Equal (ConsoleModifiers.Control, mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Null (code);
+			Assert.Equal (2, values.Length);
+			Assert.Equal ("1", values [0]);
+			Assert.Equal ("5", values [^1]);
+			Assert.Equal ("R", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('1', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('6', 0, false, false, false),
+				new ConsoleKeyInfo ('R', 0, false, false, false)
+			};
+			expectedCki = new ConsoleKeyInfo ('\0', ConsoleKey.F3, true, false, true);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.F3, key);
+			Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Control, mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Null (code);
+			Assert.Equal (2, values.Length);
+			Assert.Equal ("1", values [0]);
+			Assert.Equal ("6", values [^1]);
+			Assert.Equal ("R", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('1', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('7', 0, false, false, false),
+				new ConsoleKeyInfo ('R', 0, false, false, false)
+			};
+			expectedCki = new ConsoleKeyInfo ('\0', ConsoleKey.F3, false, true, true);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.F3, key);
+			Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Null (code);
+			Assert.Equal (2, values.Length);
+			Assert.Equal ("1", values [0]);
+			Assert.Equal ("7", values [^1]);
+			Assert.Equal ("R", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('1', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('8', 0, false, false, false),
+				new ConsoleKeyInfo ('R', 0, false, false, false)
+			};
+			expectedCki = new ConsoleKeyInfo ('\0', ConsoleKey.F3, true, true, true);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (ConsoleKey.F3, key);
+			Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control, mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Null (code);
+			Assert.Equal (2, values.Length);
+			Assert.Equal ("1", values [0]);
+			Assert.Equal ("8", values [^1]);
+			Assert.Equal ("R", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('M', 0, false, false, false)
+			};
+			expectedCki = default;
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (0, (int)key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Equal ("<", code);
+			Assert.Equal (3, values.Length);
+			Assert.Equal ("0", values [0]);
+			Assert.Equal ("2", values [1]);
+			Assert.Equal ("3", values [^1]);
+			Assert.Equal ("M", terminating);
+			Assert.True (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1Pressed }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('m', 0, false, false, false)
+			};
+			expectedCki = default;
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (0, (int)key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Equal ("<", code);
+			Assert.Equal (3, values.Length);
+			Assert.Equal ("0", values [0]);
+			Assert.Equal ("2", values [1]);
+			Assert.Equal ("3", values [^1]);
+			Assert.Equal ("m", terminating);
+			Assert.True (isKeyMouse);
+			Assert.Equal (2, mouseFlags.Count);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1Released, MouseFlags.Button1Clicked }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('M', 0, false, false, false)
+			};
+			expectedCki = default;
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (0, (int)key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Equal ("<", code);
+			Assert.Equal (3, values.Length);
+			Assert.Equal ("0", values [0]);
+			Assert.Equal ("2", values [1]);
+			Assert.Equal ("3", values [^1]);
+			Assert.Equal ("M", terminating);
+			Assert.True (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1DoubleClicked }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+			Assert.False (isReq);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('M', 0, false, false, false)
+			};
+			expectedCki = default;
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (0, (int)key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Equal ("<", code);
+			Assert.Equal (3, values.Length);
+			Assert.Equal ("0", values [0]);
+			Assert.Equal ("2", values [1]);
+			Assert.Equal ("3", values [^1]);
+			Assert.Equal ("M", terminating);
+			Assert.True (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1TripleClicked }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+			Assert.False (isReq);
+
+			var view = new View () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				WantContinuousButtonPressed = true
+			};
+			Application.Top.Add (view);
+			Application.Begin (Application.Top);
+
+			ReflectionTools.InvokePrivate (
+				typeof (Application),
+				"ProcessMouseEvent",
+				new MouseEvent () {
+					X = 0,
+					Y = 0,
+					Flags = 0
+				});
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('M', 0, false, false, false)
+			};
+			expectedCki = default;
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (0, (int)key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Equal ("<", code);
+			Assert.Equal (3, values.Length);
+			Assert.Equal ("0", values [0]);
+			Assert.Equal ("2", values [1]);
+			Assert.Equal ("3", values [^1]);
+			Assert.Equal ("M", terminating);
+			Assert.True (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1Pressed }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+			Assert.False (isReq);
+
+			Application.Iteration += () => {
+				if (actionStarted) {
+					// set Application.WantContinuousButtonPressedView to null
+					view.WantContinuousButtonPressed = false;
+					ReflectionTools.InvokePrivate (
+						typeof (Application),
+						"ProcessMouseEvent",
+						new MouseEvent () {
+							X = 0,
+							Y = 0,
+							Flags = 0
+						});
+
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+
+			Assert.Null (Application.WantContinuousButtonPressedView);
+
+			Assert.Equal (MouseFlags.Button1Pressed, arg1);
+			Assert.Equal (new Point (1, 2), arg2);
+
+			ClearAll ();
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('m', 0, false, false, false)
+			};
+			expectedCki = default;
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Null (escSeqReqProc);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (0, (int)key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Equal ("<", code);
+			Assert.Equal (3, values.Length);
+			Assert.Equal ("0", values [0]);
+			Assert.Equal ("2", values [1]);
+			Assert.Equal ("3", values [^1]);
+			Assert.Equal ("m", terminating);
+			Assert.True (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1Released }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+			Assert.False (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+
+			ClearAll ();
+
+			Assert.Null (escSeqReqProc);
+			escSeqReqProc = new EscSeqReqProc ();
+			escSeqReqProc.Add ("t");
+
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('8', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('1', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo ('t', 0, false, false, false)
+			};
+			expectedCki = default;
+			Assert.Single (escSeqReqProc.EscSeqReqStats);
+			Assert.Equal ("t", escSeqReqProc.EscSeqReqStats [^1].Terminating);
+			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
+			Assert.Empty (escSeqReqProc.EscSeqReqStats);
+			Assert.Equal (expectedCki, newConsoleKeyInfo);
+			Assert.Equal (0, (int)key);
+			Assert.Equal (0, (int)mod);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Null (code);
+			Assert.Equal (3, values.Length);
+			Assert.Equal ("8", values [0]);
+			Assert.Equal ("10", values [1]);
+			Assert.Equal ("20", values [^1]);
+			Assert.Equal ("t", terminating);
+			Assert.False (isKeyMouse);
+			Assert.Equal (new List<MouseFlags> () { 0 }, mouseFlags);
+			Assert.Equal (Point.Empty, pos);
+			Assert.True (isReq);
+			Assert.Equal (0, (int)arg1);
+			Assert.Equal (Point.Empty, arg2);
+		}
+
+		private void ClearAll ()
+		{
+			escSeqReqProc = default;
+			newConsoleKeyInfo = default;
+			key = default;
+			cki = default;
+			mod = default;
+			c1Control = default;
+			code = default;
+			terminating = default;
+			values = default;
+			isKeyMouse = default;
+			isReq = default;
+			mouseFlags = default;
+			pos = default;
+			arg1 = default;
+			arg2 = default;
+		}
+
+		private void ProcessContinuousButtonPressed (MouseFlags arg1, Point arg2)
+		{
+			this.arg1 = arg1;
+			this.arg2 = arg2;
+			actionStarted = true;
+		}
+
+		[Fact]
+		public void GetEscapeResult_Tests ()
+		{
+			char [] kChars = new char [] { '\u001b', '[', '5', ';', '1', '0', 'r' };
+			(c1Control, code, values, terminating) = EscSeqUtils.GetEscapeResult (kChars);
+			Assert.Equal ("CSI", c1Control);
+			Assert.Null (code);
+			Assert.Equal (2, values.Length);
+			Assert.Equal ("5", values [0]);
+			Assert.Equal ("10", values [^1]);
+			Assert.Equal ("r", terminating);
+		}
+
+		[Fact]
+		public void GetC1ControlChar_Tests ()
+		{
+			Assert.Equal ("IND", EscSeqUtils.GetC1ControlChar ('D'));
+			Assert.Equal ("NEL", EscSeqUtils.GetC1ControlChar ('E'));
+			Assert.Equal ("HTS", EscSeqUtils.GetC1ControlChar ('H'));
+			Assert.Equal ("RI", EscSeqUtils.GetC1ControlChar ('M'));
+			Assert.Equal ("SS2", EscSeqUtils.GetC1ControlChar ('N'));
+			Assert.Equal ("SS3", EscSeqUtils.GetC1ControlChar ('O'));
+			Assert.Equal ("DCS", EscSeqUtils.GetC1ControlChar ('P'));
+			Assert.Equal ("SPA", EscSeqUtils.GetC1ControlChar ('V'));
+			Assert.Equal ("EPA", EscSeqUtils.GetC1ControlChar ('W'));
+			Assert.Equal ("SOS", EscSeqUtils.GetC1ControlChar ('X'));
+			Assert.Equal ("DECID", EscSeqUtils.GetC1ControlChar ('Z'));
+			Assert.Equal ("CSI", EscSeqUtils.GetC1ControlChar ('['));
+			Assert.Equal ("ST", EscSeqUtils.GetC1ControlChar ('\\'));
+			Assert.Equal ("OSC", EscSeqUtils.GetC1ControlChar (']'));
+			Assert.Equal ("PM", EscSeqUtils.GetC1ControlChar ('^'));
+			Assert.Equal ("APC", EscSeqUtils.GetC1ControlChar ('_'));
+			Assert.Equal ("", EscSeqUtils.GetC1ControlChar ('\0'));
+		}
+
+		[Fact]
+		public void GetConsoleModifiers_Tests ()
+		{
+			Assert.Equal (ConsoleModifiers.Shift, EscSeqUtils.GetConsoleModifiers ("2"));
+			Assert.Equal (ConsoleModifiers.Alt, EscSeqUtils.GetConsoleModifiers ("3"));
+			Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt, EscSeqUtils.GetConsoleModifiers ("4"));
+			Assert.Equal (ConsoleModifiers.Control, EscSeqUtils.GetConsoleModifiers ("5"));
+			Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Control, EscSeqUtils.GetConsoleModifiers ("6"));
+			Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, EscSeqUtils.GetConsoleModifiers ("7"));
+			Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control, EscSeqUtils.GetConsoleModifiers ("8"));
+			Assert.Equal (0, (int)EscSeqUtils.GetConsoleModifiers (""));
+		}
+
+		[Fact]
+		public void GetConsoleKey_Tests ()
+		{
+			ConsoleModifiers mod = 0;
+			Assert.Equal (ConsoleKey.UpArrow, EscSeqUtils.GetConsoleKey ('A', "", ref mod));
+			Assert.Equal (ConsoleKey.DownArrow, EscSeqUtils.GetConsoleKey ('B', "", ref mod));
+			Assert.Equal (key = ConsoleKey.RightArrow, EscSeqUtils.GetConsoleKey ('C', "", ref mod));
+			Assert.Equal (ConsoleKey.LeftArrow, EscSeqUtils.GetConsoleKey ('D', "", ref mod));
+			Assert.Equal (ConsoleKey.End, EscSeqUtils.GetConsoleKey ('F', "", ref mod));
+			Assert.Equal (ConsoleKey.Home, EscSeqUtils.GetConsoleKey ('H', "", ref mod));
+			Assert.Equal (ConsoleKey.F1, EscSeqUtils.GetConsoleKey ('P', "", ref mod));
+			Assert.Equal (ConsoleKey.F2, EscSeqUtils.GetConsoleKey ('Q', "", ref mod));
+			Assert.Equal (ConsoleKey.F3, EscSeqUtils.GetConsoleKey ('R', "", ref mod));
+			Assert.Equal (ConsoleKey.F4, EscSeqUtils.GetConsoleKey ('S', "", ref mod));
+			Assert.Equal (ConsoleKey.Tab, EscSeqUtils.GetConsoleKey ('Z', "", ref mod));
+			Assert.Equal (ConsoleModifiers.Shift, mod);
+			Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('\0', "", ref mod));
+			Assert.Equal (ConsoleKey.Insert, EscSeqUtils.GetConsoleKey ('~', "2", ref mod));
+			Assert.Equal (ConsoleKey.Delete, EscSeqUtils.GetConsoleKey ('~', "3", ref mod));
+			Assert.Equal (ConsoleKey.PageUp, EscSeqUtils.GetConsoleKey ('~', "5", ref mod));
+			Assert.Equal (ConsoleKey.PageDown, EscSeqUtils.GetConsoleKey ('~', "6", ref mod));
+			Assert.Equal (ConsoleKey.F5, EscSeqUtils.GetConsoleKey ('~', "15", ref mod));
+			Assert.Equal (ConsoleKey.F6, EscSeqUtils.GetConsoleKey ('~', "17", ref mod));
+			Assert.Equal (ConsoleKey.F7, EscSeqUtils.GetConsoleKey ('~', "18", ref mod));
+			Assert.Equal (ConsoleKey.F8, EscSeqUtils.GetConsoleKey ('~', "19", ref mod));
+			Assert.Equal (ConsoleKey.F9, EscSeqUtils.GetConsoleKey ('~', "20", ref mod));
+			Assert.Equal (ConsoleKey.F10, EscSeqUtils.GetConsoleKey ('~', "21", ref mod));
+			Assert.Equal (ConsoleKey.F11, EscSeqUtils.GetConsoleKey ('~', "23", ref mod));
+			Assert.Equal (ConsoleKey.F12, EscSeqUtils.GetConsoleKey ('~', "24", ref mod));
+			Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('~', "", ref mod));
+		}
+
+		[Fact]
+		public void GetKeyCharArray_Tests ()
+		{
+			var cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo('[', 0, false, false, false),
+				new ConsoleKeyInfo('5', 0, false, false, false),
+				new ConsoleKeyInfo(';', 0, false, false, false),
+				new ConsoleKeyInfo('1', 0, false, false, false),
+				new ConsoleKeyInfo('0', 0, false, false, false),
+				new ConsoleKeyInfo('r', 0, false, false, false),
+			};
+
+			Assert.Equal (new char [] { '\u001b', '[', '5', ';', '1', '0', 'r' }, EscSeqUtils.GetKeyCharArray (cki));
+		}
+
+		[Fact, AutoInitShutdown]
+		public void GetMouse_Tests ()
+		{
+			var cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('M', 0, false, false, false)
+			};
+			EscSeqUtils.GetMouse (cki, out List<MouseFlags> mouseFlags, out Point pos, ProcessContinuousButtonPressed);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1Pressed }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('m', 0, false, false, false)
+			};
+			EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed);
+			Assert.Equal (2, mouseFlags.Count);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1Released, MouseFlags.Button1Clicked }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('M', 0, false, false, false)
+			};
+			EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1DoubleClicked }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('M', 0, false, false, false)
+			};
+			EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1TripleClicked }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+
+			cki = new ConsoleKeyInfo [] {
+				new ConsoleKeyInfo ('\u001b', 0, false, false, false),
+				new ConsoleKeyInfo ('[', 0, false, false, false),
+				new ConsoleKeyInfo ('<', 0, false, false, false),
+				new ConsoleKeyInfo ('0', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('2', 0, false, false, false),
+				new ConsoleKeyInfo (';', 0, false, false, false),
+				new ConsoleKeyInfo ('3', 0, false, false, false),
+				new ConsoleKeyInfo ('m', 0, false, false, false)
+			};
+			EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed);
+			Assert.Equal (new List<MouseFlags> () { MouseFlags.Button1Released }, mouseFlags);
+			Assert.Equal (new Point (1, 2), pos);
+		}
+
+		[Fact]
+		public void GetParentProcess_Tests ()
+		{
+			if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+				Assert.NotNull (EscSeqUtils.GetParentProcess (Process.GetCurrentProcess ()));
+			} else {
+				Assert.Null (EscSeqUtils.GetParentProcess (Process.GetCurrentProcess ()));
+			}
+		}
+	}
+}

+ 346 - 0
UnitTests/Core/LineCanvasTests.cs

@@ -0,0 +1,346 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Terminal.Gui.Graphs;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.CoreTests {
+	public class LineCanvasTests {
+
+		readonly ITestOutputHelper output;
+
+		public LineCanvasTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_Dot ()
+		{
+			var v = GetCanvas (out var canvas);
+			canvas.AddLine (new Point (0, 0), 0, Orientation.Horizontal, BorderStyle.Single);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+.";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[InlineData (BorderStyle.Single)]
+		[InlineData (BorderStyle.Rounded)]
+		[Theory, AutoInitShutdown]
+		public void TestLineCanvas_Horizontal (BorderStyle style)
+		{
+			var v = GetCanvas (out var canvas);
+			canvas.AddLine (new Point (0, 0), 1, Orientation.Horizontal, style);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+──";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_Horizontal_Double ()
+		{
+			var v = GetCanvas (out var canvas);
+			canvas.AddLine (new Point (0, 0), 1, Orientation.Horizontal, BorderStyle.Double);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@" 
+══";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[InlineData (BorderStyle.Single)]
+		[InlineData (BorderStyle.Rounded)]
+		[Theory, AutoInitShutdown]
+		public void TestLineCanvas_Vertical (BorderStyle style)
+		{
+			var v = GetCanvas (out var canvas);
+			canvas.AddLine (new Point (0, 0), 1, Orientation.Vertical, style);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+│
+│";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_Vertical_Double ()
+		{
+			var v = GetCanvas (out var canvas);
+			canvas.AddLine (new Point (0, 0), 1, Orientation.Vertical, BorderStyle.Double);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+║
+║";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		/// <summary>
+		/// This test demonstrates that corners are only drawn when lines overlap.
+		/// Not when they terminate adjacent to one another.
+		/// </summary>
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_Corner_NoOverlap ()
+		{
+			var v = GetCanvas (out var canvas);
+			canvas.AddLine (new Point (0, 0), 1, Orientation.Horizontal, BorderStyle.Single);
+			canvas.AddLine (new Point (0, 1), 1, Orientation.Vertical, BorderStyle.Single);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+──
+│
+│";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+		/// <summary>
+		/// This test demonstrates how to correctly trigger a corner.  By
+		/// overlapping the lines in the same cell
+		/// </summary>
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_Corner_Correct ()
+		{
+			var v = GetCanvas (out var canvas);
+			canvas.AddLine (new Point (0, 0), 1, Orientation.Horizontal, BorderStyle.Single);
+			canvas.AddLine (new Point (0, 0), 2, Orientation.Vertical, BorderStyle.Single);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+┌─
+│
+│";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_Window ()
+		{
+			var v = GetCanvas (out var canvas);
+
+			// outer box
+			canvas.AddLine (new Point (0, 0), 9, Orientation.Horizontal, BorderStyle.Single);
+			canvas.AddLine (new Point (9, 0), 4, Orientation.Vertical, BorderStyle.Single);
+			canvas.AddLine (new Point (9, 4), -9, Orientation.Horizontal, BorderStyle.Single);
+			canvas.AddLine (new Point (0, 4), -4, Orientation.Vertical, BorderStyle.Single);
+
+
+			canvas.AddLine (new Point (5, 0), 4, Orientation.Vertical, BorderStyle.Single);
+			canvas.AddLine (new Point (0, 2), 9, Orientation.Horizontal, BorderStyle.Single);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+┌────┬───┐
+│    │   │
+├────┼───┤
+│    │   │
+└────┴───┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		/// <summary>
+		/// Demonstrates when <see cref="BorderStyle.Rounded"/> corners are used. Notice how
+		/// not all lines declare rounded.  If there are 1+ lines intersecting and a corner is
+		/// to be used then if any of them are rounded a rounded corner is used.
+		/// </summary>
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_Window_Rounded ()
+		{
+			var v = GetCanvas (out var canvas);
+
+			// outer box
+			canvas.AddLine (new Point (0, 0), 9, Orientation.Horizontal, BorderStyle.Rounded);
+
+			// BorderStyle.Single is ignored because corner overlaps with the above line which is Rounded
+			// this results in a rounded corner being used.
+			canvas.AddLine (new Point (9, 0), 4, Orientation.Vertical, BorderStyle.Single);
+			canvas.AddLine (new Point (9, 4), -9, Orientation.Horizontal, BorderStyle.Rounded);
+			canvas.AddLine (new Point (0, 4), -4, Orientation.Vertical, BorderStyle.Single);
+
+			// These lines say rounded but they will result in the T sections which are never rounded.
+			canvas.AddLine (new Point (5, 0), 4, Orientation.Vertical, BorderStyle.Rounded);
+			canvas.AddLine (new Point (0, 2), 9, Orientation.Horizontal, BorderStyle.Rounded);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+╭────┬───╮
+│    │   │
+├────┼───┤
+│    │   │
+╰────┴───╯";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_Window_Double ()
+		{
+			var v = GetCanvas (out var canvas);
+
+			// outer box
+			canvas.AddLine (new Point (0, 0), 9, Orientation.Horizontal, BorderStyle.Double);
+			canvas.AddLine (new Point (9, 0), 4, Orientation.Vertical, BorderStyle.Double);
+			canvas.AddLine (new Point (9, 4), -9, Orientation.Horizontal, BorderStyle.Double);
+			canvas.AddLine (new Point (0, 4), -4, Orientation.Vertical, BorderStyle.Double);
+
+
+			canvas.AddLine (new Point (5, 0), 4, Orientation.Vertical, BorderStyle.Double);
+			canvas.AddLine (new Point (0, 2), 9, Orientation.Horizontal, BorderStyle.Double);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+╔════╦═══╗
+║    ║   ║
+╠════╬═══╣
+║    ║   ║
+╚════╩═══╝";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+
+		[Theory, AutoInitShutdown]
+		[InlineData (BorderStyle.Single)]
+		[InlineData (BorderStyle.Rounded)]
+		public void TestLineCanvas_Window_DoubleTop_SingleSides (BorderStyle thinStyle)
+		{
+			var v = GetCanvas (out var canvas);
+
+			// outer box
+			canvas.AddLine (new Point (0, 0), 9, Orientation.Horizontal, BorderStyle.Double);
+			canvas.AddLine (new Point (9, 0), 4, Orientation.Vertical, thinStyle);
+			canvas.AddLine (new Point (9, 4), -9, Orientation.Horizontal, BorderStyle.Double);
+			canvas.AddLine (new Point (0, 4), -4, Orientation.Vertical, thinStyle);
+
+
+			canvas.AddLine (new Point (5, 0), 4, Orientation.Vertical, thinStyle);
+			canvas.AddLine (new Point (0, 2), 9, Orientation.Horizontal, BorderStyle.Double);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+╒════╤═══╕
+│    │   │
+╞════╪═══╡
+│    │   │
+╘════╧═══╛
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Theory, AutoInitShutdown]
+		[InlineData (BorderStyle.Single)]
+		[InlineData (BorderStyle.Rounded)]
+		public void TestLineCanvas_Window_SingleTop_DoubleSides (BorderStyle thinStyle)
+		{
+			var v = GetCanvas (out var canvas);
+
+			// outer box
+			canvas.AddLine (new Point (0, 0), 9, Orientation.Horizontal, thinStyle);
+			canvas.AddLine (new Point (9, 0), 4, Orientation.Vertical, BorderStyle.Double);
+			canvas.AddLine (new Point (9, 4), -9, Orientation.Horizontal, thinStyle);
+			canvas.AddLine (new Point (0, 4), -4, Orientation.Vertical, BorderStyle.Double);
+
+
+			canvas.AddLine (new Point (5, 0), 4, Orientation.Vertical, BorderStyle.Double);
+			canvas.AddLine (new Point (0, 2), 9, Orientation.Horizontal, thinStyle);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"    
+╓────╥───╖
+║    ║   ║
+╟────╫───╢
+║    ║   ║
+╙────╨───╜
+
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_LeaveMargin_Top1_Left1 ()
+		{
+			// Draw at 1,1 within client area of View (i.e. leave a top and left margin of 1)
+			var v = GetCanvas (out var canvas, 1, 1);
+
+			// outer box
+			canvas.AddLine (new Point (0, 0), 8, Orientation.Horizontal, BorderStyle.Single);
+			canvas.AddLine (new Point (8, 0), 3, Orientation.Vertical, BorderStyle.Single);
+			canvas.AddLine (new Point (8, 3), -8, Orientation.Horizontal, BorderStyle.Single);
+			canvas.AddLine (new Point (0, 3), -3, Orientation.Vertical, BorderStyle.Single);
+
+
+			canvas.AddLine (new Point (5, 0), 3, Orientation.Vertical, BorderStyle.Single);
+			canvas.AddLine (new Point (0, 2), 8, Orientation.Horizontal, BorderStyle.Single);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"
+ ┌────┬──┐
+ │    │  │
+ ├────┼──┤
+ └────┴──┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+
+		/// <summary>
+		/// Creates a new <see cref="View"/> into which a <see cref="LineCanvas"/> is rendered
+		/// at <see cref="View.DrawContentComplete"/> time.
+		/// </summary>
+		/// <param name="canvas">The <see cref="LineCanvas"/> you can draw into.</param>
+		/// <param name="offsetX">How far to offset drawing in X</param>
+		/// <param name="offsetY">How far to offset drawing in Y</param>
+		/// <returns></returns>
+		private View GetCanvas (out LineCanvas canvas, int offsetX = 0, int offsetY = 0)
+		{
+			var v = new View {
+				Width = 10,
+				Height = 5,
+				Bounds = new Rect (0, 0, 10, 5)
+			};
+
+			var canvasCopy = canvas = new LineCanvas ();
+			v.DrawContentComplete += (r) => {
+					foreach(var p in canvasCopy.GenerateImage(v.Bounds))
+					{
+						v.AddRune(
+							offsetX + p.Key.X,
+							offsetY + p.Key.Y,
+							p.Value);
+					}
+				};
+
+			return v;
+		}
+	}
+}

+ 1 - 6
UnitTests/ResponderTests.cs → UnitTests/Core/ResponderTests.cs

@@ -1,14 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Terminal.Gui;
 using Xunit;
 using Xunit;
-using static Terminal.Gui.Core.ViewTests;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.CoreTests {
 	public class ResponderTests {
 	public class ResponderTests {
 		[Fact]
 		[Fact]
 		public void New_Initializes ()
 		public void New_Initializes ()

+ 70 - 6
UnitTests/AttributeTests.cs → UnitTests/Drivers/AttributeTests.cs

@@ -7,7 +7,7 @@ using Xunit;
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui.ConsoleDrivers {
+namespace Terminal.Gui.DriverTests {
 	public class AttributeTests {
 	public class AttributeTests {
 		[Fact]
 		[Fact]
 		public void Constuctors_Constuct ()
 		public void Constuctors_Constuct ()
@@ -85,7 +85,34 @@ namespace Terminal.Gui.ConsoleDrivers {
 		}
 		}
 
 
 		[Fact]
 		[Fact]
-		public void Make_Asserts_IfNotInit ()
+		public void Implicit_Assign_NoDriver ()
+		{
+
+			var attr = new Attribute ();
+
+			var fg = new Color ();
+			fg = Color.Red;
+
+			var bg = new Color ();
+			bg = Color.Blue;
+
+			// Test conversion to int
+			attr = new Attribute (fg, bg);
+			int value_implicit = (int)attr.Value;
+			Assert.False (attr.Initialized);
+
+			Assert.Equal (-1, value_implicit);
+			Assert.False (attr.Initialized);
+
+			// Test conversion from int
+			attr = -1;
+			Assert.Equal (-1, attr.Value);
+			Assert.False (attr.Initialized);
+
+		}
+
+		[Fact]
+		public void Make_SetsNotInitialized_NoDriver ()
 		{
 		{
 			var fg = new Color ();
 			var fg = new Color ();
 			fg = Color.Red;
 			fg = Color.Red;
@@ -93,7 +120,9 @@ namespace Terminal.Gui.ConsoleDrivers {
 			var bg = new Color ();
 			var bg = new Color ();
 			bg = Color.Blue;
 			bg = Color.Blue;
 
 
-			Assert.Throws<InvalidOperationException> (() => Attribute.Make (fg, bg));
+			var a = Attribute.Make (fg, bg);
+
+			Assert.False (a.Initialized);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -109,8 +138,8 @@ namespace Terminal.Gui.ConsoleDrivers {
 			var bg = new Color ();
 			var bg = new Color ();
 			bg = Color.Blue;
 			bg = Color.Blue;
 
 
-			var attr =  Attribute.Make (fg, bg);
-
+			var attr = Attribute.Make (fg, bg);
+			Assert.True (attr.Initialized);
 			Assert.Equal (fg, attr.Foreground);
 			Assert.Equal (fg, attr.Foreground);
 			Assert.Equal (bg, attr.Background);
 			Assert.Equal (bg, attr.Background);
 
 
@@ -119,7 +148,23 @@ namespace Terminal.Gui.ConsoleDrivers {
 		}
 		}
 
 
 		[Fact]
 		[Fact]
-		public void Get_Asserts_IfNotInit ()
+		public void Make_Creates_NoDriver ()
+		{
+
+			var fg = new Color ();
+			fg = Color.Red;
+
+			var bg = new Color ();
+			bg = Color.Blue;
+
+			var attr = Attribute.Make (fg, bg);
+			Assert.False (attr.Initialized);
+			Assert.Equal (fg, attr.Foreground);
+			Assert.Equal (bg, attr.Background);
+		}
+
+		[Fact]
+		public void Get_Asserts_NoDriver ()
 		{
 		{
 			Assert.Throws<InvalidOperationException> (() => Attribute.Get ());
 			Assert.Throws<InvalidOperationException> (() => Attribute.Get ());
 		}
 		}
@@ -163,5 +208,24 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Assert.Equal (Color.Red, fg);
 			Assert.Equal (Color.Red, fg);
 			Assert.Equal (Color.Green, bg);
 			Assert.Equal (Color.Green, bg);
 		}
 		}
+
+		[Fact]
+		public void IsValid_Tests ()
+		{
+			var attr = new Attribute ();
+			Assert.True (attr.HasValidColors);
+
+			attr = new Attribute (Color.Red, Color.Green);
+			Assert.True (attr.HasValidColors);
+
+			attr = new Attribute (Color.Red, (Color)(-1));
+			Assert.False (attr.HasValidColors);
+
+			attr = new Attribute ((Color)(-1), Color.Green);
+			Assert.False (attr.HasValidColors);
+
+			attr = new Attribute ((Color)(-1), (Color)(-1));
+			Assert.False (attr.HasValidColors);
+		}
 	}
 	}
 }
 }

+ 11 - 23
UnitTests/ClipboardTests.cs → UnitTests/Drivers/ClipboardTests.cs

@@ -1,11 +1,12 @@
 using System;
 using System;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
+using Terminal.Gui;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 using static AutoInitShutdownAttribute;
 using static AutoInitShutdownAttribute;
 
 
-namespace Terminal.Gui.ConsoleDrivers {
+namespace Terminal.Gui.DriverTests {
 	public class ClipboardTests {
 	public class ClipboardTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 
@@ -101,11 +102,8 @@ namespace Terminal.Gui.ConsoleDrivers {
 		[Fact, AutoInitShutdown (useFakeClipboard: false)]
 		[Fact, AutoInitShutdown (useFakeClipboard: false)]
 		public void IsSupported_Get ()
 		public void IsSupported_Get ()
 		{
 		{
-			if (Clipboard.IsSupported) {
-				Assert.True (Clipboard.IsSupported);
-			} else {
-				Assert.False (Clipboard.IsSupported);
-			}
+			if (Clipboard.IsSupported) 				Assert.True (Clipboard.IsSupported);
+else 				Assert.False (Clipboard.IsSupported);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown (useFakeClipboard: false)]
 		[Fact, AutoInitShutdown (useFakeClipboard: false)]
@@ -131,21 +129,15 @@ namespace Terminal.Gui.ConsoleDrivers {
 		public void TrySetClipboardData_Sets_The_OS_Clipboard ()
 		public void TrySetClipboardData_Sets_The_OS_Clipboard ()
 		{
 		{
 			var clipText = "The TrySetClipboardData_Sets_The_OS_Clipboard unit test pasted this to the OS clipboard.";
 			var clipText = "The TrySetClipboardData_Sets_The_OS_Clipboard unit test pasted this to the OS clipboard.";
-			if (Clipboard.IsSupported) {
-				Assert.True (Clipboard.TrySetClipboardData (clipText));
-			} else {
-				Assert.False (Clipboard.TrySetClipboardData (clipText));
-			}
+			if (Clipboard.IsSupported) 				Assert.True (Clipboard.TrySetClipboardData (clipText));
+else 				Assert.False (Clipboard.TrySetClipboardData (clipText));
 
 
 			Application.Iteration += () => Application.RequestStop ();
 			Application.Iteration += () => Application.RequestStop ();
 
 
 			Application.Run ();
 			Application.Run ();
 
 
-			if (Clipboard.IsSupported) {
-				Assert.Equal (clipText, Clipboard.Contents);
-			} else {
-				Assert.NotEqual (clipText, Clipboard.Contents);
-			}
+			if (Clipboard.IsSupported) 				Assert.Equal (clipText, Clipboard.Contents);
+else 				Assert.NotEqual (clipText, Clipboard.Contents);
 		}
 		}
 
 
 
 
@@ -217,9 +209,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 
 
 			Application.Run ();
 			Application.Run ();
 
 
-			if (!failed) {
-				Assert.Equal (clipText, getClipText);
-			}
+			if (!failed) 				Assert.Equal (clipText, getClipText);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown (useFakeClipboard: false)]
 		[Fact, AutoInitShutdown (useFakeClipboard: false)]
@@ -275,9 +265,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 
 
 			Application.Run ();
 			Application.Run ();
 
 
-			if (!failed) {
-				Assert.Equal (clipText, clipReadText.TrimEnd ());
-			}
+			if (!failed) 				Assert.Equal (clipText, clipReadText.TrimEnd ());
 
 
 		}
 		}
 
 
@@ -292,7 +280,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 			try {
 			try {
 				var (_, result) = ClipboardProcessRunner.Process ("bash", $"-c \"which xclip\"");
 				var (_, result) = ClipboardProcessRunner.Process ("bash", $"-c \"which xclip\"");
 				return result.TrimEnd () != "";
 				return result.TrimEnd () != "";
-			} catch (System.Exception) {
+			} catch (Exception) {
 				return false;
 				return false;
 			}
 			}
 		}
 		}

+ 77 - 0
UnitTests/Drivers/ColorTests.cs

@@ -0,0 +1,77 @@
+using System;
+using Xunit;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.DriverTests {
+	public class ColorTests {
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		//[InlineData (typeof (NetDriver))]
+		//[InlineData (typeof (CursesDriver))]
+		//[InlineData (typeof (WindowsDriver))]
+		public void SetColors_Changes_Colors (Type driverType)
+		{
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+			driver.Init (() => { });
+			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
+			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
+
+			Console.ForegroundColor = ConsoleColor.Red;
+			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
+
+			Console.BackgroundColor = ConsoleColor.Green;
+			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
+
+			Console.ResetColor ();
+			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
+			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
+			driver.End ();
+
+			// Shutdown must be called to safely clean up Application if Init has been called
+			Application.Shutdown ();
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ColorScheme_New ()
+		{
+			var scheme = new ColorScheme ();
+			var lbl = new Label ();
+			lbl.ColorScheme = scheme;
+			lbl.Redraw (lbl.Bounds);
+		}
+
+		[Fact]
+		public void TestAllColors ()
+		{
+			var colors = System.Enum.GetValues (typeof (Color));
+			Attribute [] attrs = new Attribute [colors.Length];
+
+			int idx = 0;
+			foreach (Color bg in colors) {
+				attrs [idx] = new Attribute (bg, colors.Length - 1 - bg);
+				idx++;
+			}
+			Assert.Equal (16, attrs.Length);
+			Assert.Equal (new Attribute (Color.Black, Color.White), attrs [0]);
+			Assert.Equal (new Attribute (Color.Blue, Color.BrightYellow), attrs [1]);
+			Assert.Equal (new Attribute (Color.Green, Color.BrightMagenta), attrs [2]);
+			Assert.Equal (new Attribute (Color.Cyan, Color.BrightRed), attrs [3]);
+			Assert.Equal (new Attribute (Color.Red, Color.BrightCyan), attrs [4]);
+			Assert.Equal (new Attribute (Color.Magenta, Color.BrightGreen), attrs [5]);
+			Assert.Equal (new Attribute (Color.Brown, Color.BrightBlue), attrs [6]);
+			Assert.Equal (new Attribute (Color.Gray, Color.DarkGray), attrs [7]);
+			Assert.Equal (new Attribute (Color.DarkGray, Color.Gray), attrs [8]);
+			Assert.Equal (new Attribute (Color.BrightBlue, Color.Brown), attrs [9]);
+			Assert.Equal (new Attribute (Color.BrightGreen, Color.Magenta), attrs [10]);
+			Assert.Equal (new Attribute (Color.BrightCyan, Color.Red), attrs [11]);
+			Assert.Equal (new Attribute (Color.BrightRed, Color.Cyan), attrs [12]);
+			Assert.Equal (new Attribute (Color.BrightMagenta, Color.Green), attrs [13]);
+			Assert.Equal (new Attribute (Color.BrightYellow, Color.Blue), attrs [14]);
+			Assert.Equal (new Attribute (Color.White, Color.Black), attrs [^1]);
+		}
+	}
+}

+ 616 - 0
UnitTests/Drivers/ConsoleDriverTests.cs

@@ -0,0 +1,616 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+using Xunit.Abstractions;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.DriverTests {
+	public class ConsoleDriverTests {
+		readonly ITestOutputHelper output;
+
+		public ConsoleDriverTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		//[InlineData (typeof (NetDriver))]
+		//[InlineData (typeof (CursesDriver))]
+		//[InlineData (typeof (WindowsDriver))]
+		public void Init_Inits (Type driverType)
+		{
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+			driver.Init (() => { });
+
+			Assert.Equal (80, Console.BufferWidth);
+			Assert.Equal (25, Console.BufferHeight);
+
+			// MockDriver is always 80x25
+			Assert.Equal (Console.BufferWidth, driver.Cols);
+			Assert.Equal (Console.BufferHeight, driver.Rows);
+			driver.End ();
+
+			// Shutdown must be called to safely clean up Application if Init has been called
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		//[InlineData (typeof (NetDriver))]
+		//[InlineData (typeof (CursesDriver))]
+		//[InlineData (typeof (WindowsDriver))]
+		public void End_Cleans_Up (Type driverType)
+		{
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+			driver.Init (() => { });
+
+			Console.ForegroundColor = ConsoleColor.Red;
+			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
+
+			Console.BackgroundColor = ConsoleColor.Green;
+			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
+			driver.Move (2, 3);
+			Assert.Equal (2, Console.CursorLeft);
+			Assert.Equal (3, Console.CursorTop);
+
+			driver.End ();
+			Assert.Equal (0, Console.CursorLeft);
+			Assert.Equal (0, Console.CursorTop);
+			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
+			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
+
+			// Shutdown must be called to safely clean up Application if Init has been called
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses (Type driverType)
+		{
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+
+			var top = Application.Top;
+			var view = new View ();
+			var count = 0;
+			var wasKeyPressed = false;
+
+			view.KeyPress += (e) => {
+				wasKeyPressed = true;
+			};
+			top.Add (view);
+
+			Application.Iteration += () => {
+				count++;
+				if (count == 10) Application.RequestStop ();
+			};
+
+			Application.Run ();
+
+			Assert.False (wasKeyPressed);
+
+			// Shutdown must be called to safely clean up Application if Init has been called
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void FakeDriver_MockKeyPresses (Type driverType)
+		{
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+
+			var text = "MockKeyPresses";
+			var mKeys = new Stack<ConsoleKeyInfo> ();
+			foreach (var r in text.Reverse ()) {
+				var ck = char.IsLetter (r) ? (ConsoleKey)char.ToUpper (r) : (ConsoleKey)r;
+				var cki = new ConsoleKeyInfo (r, ck, false, false, false);
+				mKeys.Push (cki);
+			}
+			Console.MockKeyPresses = mKeys;
+
+			var top = Application.Top;
+			var view = new View ();
+			var rText = "";
+			var idx = 0;
+
+			view.KeyPress += (e) => {
+				Assert.Equal (text [idx], (char)e.KeyEvent.Key);
+				rText += (char)e.KeyEvent.Key;
+				Assert.Equal (rText, text.Substring (0, idx + 1));
+				e.Handled = true;
+				idx++;
+			};
+			top.Add (view);
+
+			Application.Iteration += () => {
+				if (mKeys.Count == 0) Application.RequestStop ();
+			};
+
+			Application.Run ();
+
+			Assert.Equal ("MockKeyPresses", rText);
+
+			// Shutdown must be called to safely clean up Application if Init has been called
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void TerminalResized_Simulation (Type driverType)
+		{
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+			var wasTerminalResized = false;
+			Application.Resized = (e) => {
+				wasTerminalResized = true;
+				Assert.Equal (120, e.Cols);
+				Assert.Equal (40, e.Rows);
+			};
+
+			Assert.Equal (80, Console.BufferWidth);
+			Assert.Equal (25, Console.BufferHeight);
+
+			// MockDriver is by default 80x25
+			Assert.Equal (Console.BufferWidth, driver.Cols);
+			Assert.Equal (Console.BufferHeight, driver.Rows);
+			Assert.False (wasTerminalResized);
+
+			// MockDriver will now be sets to 120x40
+			driver.SetBufferSize (120, 40);
+			Assert.Equal (120, Application.Driver.Cols);
+			Assert.Equal (40, Application.Driver.Rows);
+			Assert.True (wasTerminalResized);
+
+			// MockDriver will still be 120x40
+			wasTerminalResized = false;
+			Application.EnableConsoleScrolling = true;
+			driver.SetWindowSize (40, 20);
+			Assert.Equal (120, Application.Driver.Cols);
+			Assert.Equal (40, Application.Driver.Rows);
+			Assert.Equal (120, Console.BufferWidth);
+			Assert.Equal (40, Console.BufferHeight);
+			Assert.Equal (40, Console.WindowWidth);
+			Assert.Equal (20, Console.WindowHeight);
+			Assert.True (wasTerminalResized);
+
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void EnableConsoleScrolling_Is_False_Left_And_Top_Is_Always_Zero (Type driverType)
+		{
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+
+			Assert.False (Application.EnableConsoleScrolling);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (0, Console.WindowTop);
+
+			driver.SetWindowPosition (5, 5);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (0, Console.WindowTop);
+
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void EnableConsoleScrolling_Is_True_Left_Cannot_Be_Greater_Than_WindowWidth (Type driverType)
+		{
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+
+			Application.EnableConsoleScrolling = true;
+			Assert.True (Application.EnableConsoleScrolling);
+
+			driver.SetWindowPosition (81, 25);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (0, Console.WindowTop);
+
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void EnableConsoleScrolling_Is_True_Left_Cannot_Be_Greater_Than_BufferWidth_Minus_WindowWidth (Type driverType)
+		{
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+
+			Application.EnableConsoleScrolling = true;
+			Assert.True (Application.EnableConsoleScrolling);
+
+			driver.SetWindowPosition (81, 25);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (0, Console.WindowTop);
+
+			// MockDriver will now be sets to 120x25
+			driver.SetBufferSize (120, 25);
+			Assert.Equal (120, Application.Driver.Cols);
+			Assert.Equal (25, Application.Driver.Rows);
+			Assert.Equal (120, Console.BufferWidth);
+			Assert.Equal (25, Console.BufferHeight);
+			Assert.Equal (80, Console.WindowWidth);
+			Assert.Equal (25, Console.WindowHeight);
+			driver.SetWindowPosition (121, 25);
+			Assert.Equal (40, Console.WindowLeft);
+			Assert.Equal (0, Console.WindowTop);
+
+			driver.SetWindowSize (90, 25);
+			Assert.Equal (120, Application.Driver.Cols);
+			Assert.Equal (25, Application.Driver.Rows);
+			Assert.Equal (120, Console.BufferWidth);
+			Assert.Equal (25, Console.BufferHeight);
+			Assert.Equal (90, Console.WindowWidth);
+			Assert.Equal (25, Console.WindowHeight);
+			driver.SetWindowPosition (121, 25);
+			Assert.Equal (30, Console.WindowLeft);
+			Assert.Equal (0, Console.WindowTop);
+
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void EnableConsoleScrolling_Is_True_Top_Cannot_Be_Greater_Than_WindowHeight (Type driverType)
+		{
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+
+			Application.EnableConsoleScrolling = true;
+			Assert.True (Application.EnableConsoleScrolling);
+
+			driver.SetWindowPosition (80, 26);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (0, Console.WindowTop);
+
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void EnableConsoleScrolling_Is_True_Top_Cannot_Be_Greater_Than_BufferHeight_Minus_WindowHeight (Type driverType)
+		{
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+
+			Application.EnableConsoleScrolling = true;
+			Assert.True (Application.EnableConsoleScrolling);
+
+			driver.SetWindowPosition (80, 26);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (0, Console.WindowTop);
+
+			// MockDriver will now be sets to 80x40
+			driver.SetBufferSize (80, 40);
+			Assert.Equal (80, Application.Driver.Cols);
+			Assert.Equal (40, Application.Driver.Rows);
+			Assert.Equal (80, Console.BufferWidth);
+			Assert.Equal (40, Console.BufferHeight);
+			Assert.Equal (80, Console.WindowWidth);
+			Assert.Equal (25, Console.WindowHeight);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (0, Console.WindowTop);
+			driver.SetWindowPosition (80, 40);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (15, Console.WindowTop);
+
+			driver.SetWindowSize (80, 20);
+			Assert.Equal (80, Application.Driver.Cols);
+			Assert.Equal (40, Application.Driver.Rows);
+			Assert.Equal (80, Console.BufferWidth);
+			Assert.Equal (40, Console.BufferHeight);
+			Assert.Equal (80, Console.WindowWidth);
+			Assert.Equal (20, Console.WindowHeight);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (15, Console.WindowTop);
+			driver.SetWindowPosition (80, 41);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (20, Console.WindowTop);
+
+			Application.Shutdown ();
+		}
+		
+		[Fact, AutoInitShutdown]
+		public void AddRune_On_Clip_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
+		{
+			var tv = new TextView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = @"これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。"
+			};
+			var win = new Window ("ワイドルーン") { Width = Dim.Fill (), Height = Dim.Fill () };
+			win.Add (tv);
+			Application.Top.Add (win);
+			var lbl = new Label ("ワイドルーン。");
+			var dg = new Dialog ("テスト", 14, 4, new Button ("選ぶ"));
+			dg.Add (lbl);
+			Application.Begin (Application.Top);
+			Application.Begin (dg);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 10);
+
+			var expected = @"
+┌ ワイドルーン ──────────────┐
+│これは広いルーンラインです。│
+│これは広いルーンラインです。│
+│これは ┌ テスト ────┐ です。│
+│これは │ワイドルーン│ です。│
+│これは │  [ 選ぶ ]  │ です。│
+│これは └────────────┘ です。│
+│これは広いルーンラインです。│
+│これは広いルーンラインです。│
+└────────────────────────────┘
+";
+
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 10), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Write_Do_Not_Change_On_ProcessKey ()
+		{
+			var win = new Window ();
+			Application.Begin (win);
+			((FakeDriver)Application.Driver).SetBufferSize (20, 8);
+
+			System.Threading.Tasks.Task.Run (() => {
+				System.Threading.Tasks.Task.Delay (500).Wait ();
+				Application.MainLoop.Invoke (() => {
+					var lbl = new Label ("Hello World") { X = Pos.Center () };
+					var dlg = new Dialog ("Test", new Button ("Ok"));
+					dlg.Add (lbl);
+					Application.Begin (dlg);
+
+					var expected = @"
+┌──────────────────┐
+│┌ Test ─────────┐ │
+││  Hello World  │ │
+││               │ │
+││               │ │
+││    [ Ok ]     │ │
+│└───────────────┘ │
+└──────────────────┘
+";
+
+					var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+					Assert.Equal (new Rect (0, 0, 20, 8), pos);
+
+					Assert.True (dlg.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())));
+					dlg.Redraw (dlg.Bounds);
+
+					expected = @"
+┌──────────────────┐
+│┌ Test ─────────┐ │
+││  Hello World  │ │
+││               │ │
+││               │ │
+││    [ Ok ]     │ │
+│└───────────────┘ │
+└──────────────────┘
+";
+
+					pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+					Assert.Equal (new Rect (0, 0, 20, 8), pos);
+
+					win.RequestStop ();
+				});
+			});
+
+			Application.Run (win);
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (0x0000001F, 0x241F)]
+		[InlineData (0x0000007F, 0x247F)]
+		[InlineData (0x0000009F, 0x249F)]
+		[InlineData (0x0001001A, 0x1001A)]
+		public void MakePrintable_Converts_Control_Chars_To_Proper_Unicode (uint code, uint expected)
+		{
+			var actual = ConsoleDriver.MakePrintable (code);
+
+			Assert.Equal (expected, actual.Value);
+		}
+
+		[Theory]
+		[InlineData (0x20)]
+		[InlineData (0x7E)]
+		[InlineData (0xA0)]
+		[InlineData (0x010020)]
+		public void MakePrintable_Does_Not_Convert_Ansi_Chars_To_Unicode (uint code)
+		{
+			var actual = ConsoleDriver.MakePrintable (code);
+
+			Assert.Equal (code, actual.Value);
+		}
+
+		private static object packetLock = new object ();
+		
+		/// <summary>
+		/// Sometimes when using remote tools EventKeyRecord sends 'virtual keystrokes'.
+		/// These are indicated with the wVirtualKeyCode of 231. When we see this code
+		/// then we need to look to the unicode character (UnicodeChar) instead of the key
+		/// when telling the rest of the framework what button was pressed. For full details
+		/// see: https://github.com/gui-cs/Terminal.Gui/issues/2008
+		/// </summary>
+		[Theory, AutoInitShutdown]
+		[ClassData (typeof (PacketTest))]
+		public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey, uint initialScanCode, Key expectedRemapping, uint expectedVirtualKey, uint expectedScanCode)
+		{
+			var modifiers = new ConsoleModifiers ();
+			if (shift) modifiers |= ConsoleModifiers.Shift;
+			if (alt) modifiers |= ConsoleModifiers.Alt;
+			if (control) modifiers |= ConsoleModifiers.Control;
+			var mappedConsoleKey = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode, out uint outputChar);
+
+			if ((scanCode > 0 || mappedConsoleKey == 0) && mappedConsoleKey == initialVirtualKey) Assert.Equal (mappedConsoleKey, initialVirtualKey);
+			else Assert.Equal (mappedConsoleKey, outputChar < 0xff ? outputChar & 0xff | 0xff << 8 : outputChar);
+			Assert.Equal (scanCode, initialScanCode);
+
+			var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (mappedConsoleKey, modifiers, out uint consoleKey, out scanCode);
+
+			//if (scanCode > 0 && consoleKey == keyChar && consoleKey > 48 && consoleKey > 57 && consoleKey < 65 && consoleKey > 91) {
+			if (scanCode > 0 && keyChar == 0 && consoleKey == mappedConsoleKey) Assert.Equal (0, (double)keyChar);
+			else Assert.Equal (keyChar, unicodeCharacter);
+			Assert.Equal (consoleKey, expectedVirtualKey);
+			Assert.Equal (scanCode, expectedScanCode);
+
+			var top = Application.Top;
+
+			top.KeyPress += (e) => {
+				var after = ShortcutHelper.GetModifiersKey (e.KeyEvent);
+				Assert.Equal (expectedRemapping, after);
+				e.Handled = true;
+				Application.RequestStop ();
+			};
+
+			var iterations = -1;
+
+			Application.Iteration += () => {
+				iterations++;
+				if (iterations == 0) Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control);
+			};
+
+
+			lock (packetLock) {
+				Application.Run ();
+				Application.Shutdown ();
+			}
+		}
+
+		public class PacketTest : IEnumerable, IEnumerable<object []> {
+			public IEnumerator<object []> GetEnumerator ()
+			{
+				lock (packetLock) {
+					yield return new object [] { 'a', false, false, false, 'A', 30, Key.a, 'A', 30 };
+					yield return new object [] { 'A', true, false, false, 'A', 30, Key.A | Key.ShiftMask, 'A', 30 };
+					yield return new object [] { 'A', true, true, false, 'A', 30, Key.A | Key.ShiftMask | Key.AltMask, 'A', 30 };
+					yield return new object [] { 'A', true, true, true, 'A', 30, Key.A | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 'A', 30 };
+					yield return new object [] { 'z', false, false, false, 'Z', 44, Key.z, 'Z', 44 };
+					yield return new object [] { 'Z', true, false, false, 'Z', 44, Key.Z | Key.ShiftMask, 'Z', 44 };
+					yield return new object [] { 'Z', true, true, false, 'Z', 44, Key.Z | Key.ShiftMask | Key.AltMask, 'Z', 44 };
+					yield return new object [] { 'Z', true, true, true, 'Z', 44, Key.Z | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 'Z', 44 };
+					yield return new object [] { '英', false, false, false, '\0', 0, (Key)'英', '\0', 0 };
+					yield return new object [] { '英', true, false, false, '\0', 0, (Key)'英' | Key.ShiftMask, '\0', 0 };
+					yield return new object [] { '英', true, true, false, '\0', 0, (Key)'英' | Key.ShiftMask | Key.AltMask, '\0', 0 };
+					yield return new object [] { '英', true, true, true, '\0', 0, (Key)'英' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '\0', 0 };
+					yield return new object [] { '+', false, false, false, 187, 26, (Key)'+', 187, 26 };
+					yield return new object [] { '*', true, false, false, 187, 26, (Key)'*' | Key.ShiftMask, 187, 26 };
+					yield return new object [] { '+', true, true, false, 187, 26, (Key)'+' | Key.ShiftMask | Key.AltMask, 187, 26 };
+					yield return new object [] { '+', true, true, true, 187, 26, (Key)'+' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 187, 26 };
+					yield return new object [] { '1', false, false, false, '1', 2, Key.D1, '1', 2 };
+					yield return new object [] { '!', true, false, false, '1', 2, (Key)'!' | Key.ShiftMask, '1', 2 };
+					yield return new object [] { '1', true, true, false, '1', 2, Key.D1 | Key.ShiftMask | Key.AltMask, '1', 2 };
+					yield return new object [] { '1', true, true, true, '1', 2, Key.D1 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '1', 2 };
+					yield return new object [] { '1', false, true, true, '1', 2, Key.D1 | Key.AltMask | Key.CtrlMask, '1', 2 };
+					yield return new object [] { '2', false, false, false, '2', 3, Key.D2, '2', 3 };
+					yield return new object [] { '"', true, false, false, '2', 3, (Key)'"' | Key.ShiftMask, '2', 3 };
+					yield return new object [] { '2', true, true, false, '2', 3, Key.D2 | Key.ShiftMask | Key.AltMask, '2', 3 };
+					yield return new object [] { '2', true, true, true, '2', 3, Key.D2 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '2', 3 };
+					yield return new object [] { '@', false, true, true, '2', 3, (Key)'@' | Key.AltMask | Key.CtrlMask, '2', 3 };
+					yield return new object [] { '3', false, false, false, '3', 4, Key.D3, '3', 4 };
+					yield return new object [] { '#', true, false, false, '3', 4, (Key)'#' | Key.ShiftMask, '3', 4 };
+					yield return new object [] { '3', true, true, false, '3', 4, Key.D3 | Key.ShiftMask | Key.AltMask, '3', 4 };
+					yield return new object [] { '3', true, true, true, '3', 4, Key.D3 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '3', 4 };
+					yield return new object [] { '£', false, true, true, '3', 4, (Key)'£' | Key.AltMask | Key.CtrlMask, '3', 4 };
+					yield return new object [] { '4', false, false, false, '4', 5, Key.D4, '4', 5 };
+					yield return new object [] { '$', true, false, false, '4', 5, (Key)'$' | Key.ShiftMask, '4', 5 };
+					yield return new object [] { '4', true, true, false, '4', 5, Key.D4 | Key.ShiftMask | Key.AltMask, '4', 5 };
+					yield return new object [] { '4', true, true, true, '4', 5, Key.D4 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '4', 5 };
+					yield return new object [] { '§', false, true, true, '4', 5, (Key)'§' | Key.AltMask | Key.CtrlMask, '4', 5 };
+					yield return new object [] { '5', false, false, false, '5', 6, Key.D5, '5', 6 };
+					yield return new object [] { '%', true, false, false, '5', 6, (Key)'%' | Key.ShiftMask, '5', 6 };
+					yield return new object [] { '5', true, true, false, '5', 6, Key.D5 | Key.ShiftMask | Key.AltMask, '5', 6 };
+					yield return new object [] { '5', true, true, true, '5', 6, Key.D5 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '5', 6 };
+					yield return new object [] { '€', false, true, true, '5', 6, (Key)'€' | Key.AltMask | Key.CtrlMask, '5', 6 };
+					yield return new object [] { '6', false, false, false, '6', 7, Key.D6, '6', 7 };
+					yield return new object [] { '&', true, false, false, '6', 7, (Key)'&' | Key.ShiftMask, '6', 7 };
+					yield return new object [] { '6', true, true, false, '6', 7, Key.D6 | Key.ShiftMask | Key.AltMask, '6', 7 };
+					yield return new object [] { '6', true, true, true, '6', 7, Key.D6 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '6', 7 };
+					yield return new object [] { '6', false, true, true, '6', 7, Key.D6 | Key.AltMask | Key.CtrlMask, '6', 7 };
+					yield return new object [] { '7', false, false, false, '7', 8, Key.D7, '7', 8 };
+					yield return new object [] { '/', true, false, false, '7', 8, (Key)'/' | Key.ShiftMask, '7', 8 };
+					yield return new object [] { '7', true, true, false, '7', 8, Key.D7 | Key.ShiftMask | Key.AltMask, '7', 8 };
+					yield return new object [] { '7', true, true, true, '7', 8, Key.D7 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '7', 8 };
+					yield return new object [] { '{', false, true, true, '7', 8, (Key)'{' | Key.AltMask | Key.CtrlMask, '7', 8 };
+					yield return new object [] { '8', false, false, false, '8', 9, Key.D8, '8', 9 };
+					yield return new object [] { '(', true, false, false, '8', 9, (Key)'(' | Key.ShiftMask, '8', 9 };
+					yield return new object [] { '8', true, true, false, '8', 9, Key.D8 | Key.ShiftMask | Key.AltMask, '8', 9 };
+					yield return new object [] { '8', true, true, true, '8', 9, Key.D8 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '8', 9 };
+					yield return new object [] { '[', false, true, true, '8', 9, (Key)'[' | Key.AltMask | Key.CtrlMask, '8', 9 };
+					yield return new object [] { '9', false, false, false, '9', 10, Key.D9, '9', 10 };
+					yield return new object [] { ')', true, false, false, '9', 10, (Key)')' | Key.ShiftMask, '9', 10 };
+					yield return new object [] { '9', true, true, false, '9', 10, Key.D9 | Key.ShiftMask | Key.AltMask, '9', 10 };
+					yield return new object [] { '9', true, true, true, '9', 10, Key.D9 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '9', 10 };
+					yield return new object [] { ']', false, true, true, '9', 10, (Key)']' | Key.AltMask | Key.CtrlMask, '9', 10 };
+					yield return new object [] { '0', false, false, false, '0', 11, Key.D0, '0', 11 };
+					yield return new object [] { '=', true, false, false, '0', 11, (Key)'=' | Key.ShiftMask, '0', 11 };
+					yield return new object [] { '0', true, true, false, '0', 11, Key.D0 | Key.ShiftMask | Key.AltMask, '0', 11 };
+					yield return new object [] { '0', true, true, true, '0', 11, Key.D0 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '0', 11 };
+					yield return new object [] { '}', false, true, true, '0', 11, (Key)'}' | Key.AltMask | Key.CtrlMask, '0', 11 };
+					yield return new object [] { '\'', false, false, false, 219, 12, (Key)'\'', 219, 12 };
+					yield return new object [] { '?', true, false, false, 219, 12, (Key)'?' | Key.ShiftMask, 219, 12 };
+					yield return new object [] { '\'', true, true, false, 219, 12, (Key)'\'' | Key.ShiftMask | Key.AltMask, 219, 12 };
+					yield return new object [] { '\'', true, true, true, 219, 12, (Key)'\'' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 219, 12 };
+					yield return new object [] { '«', false, false, false, 221, 13, (Key)'«', 221, 13 };
+					yield return new object [] { '»', true, false, false, 221, 13, (Key)'»' | Key.ShiftMask, 221, 13 };
+					yield return new object [] { '«', true, true, false, 221, 13, (Key)'«' | Key.ShiftMask | Key.AltMask, 221, 13 };
+					yield return new object [] { '«', true, true, true, 221, 13, (Key)'«' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 221, 13 };
+					yield return new object [] { 'á', false, false, false, 'á', 0, (Key)'á', 'A', 30 };
+					yield return new object [] { 'Á', true, false, false, 'Á', 0, (Key)'Á' | Key.ShiftMask, 'A', 30 };
+					yield return new object [] { 'à', false, false, false, 'à', 0, (Key)'à', 'A', 30 };
+					yield return new object [] { 'À', true, false, false, 'À', 0, (Key)'À' | Key.ShiftMask, 'A', 30 };
+					yield return new object [] { 'é', false, false, false, 'é', 0, (Key)'é', 'E', 18 };
+					yield return new object [] { 'É', true, false, false, 'É', 0, (Key)'É' | Key.ShiftMask, 'E', 18 };
+					yield return new object [] { 'è', false, false, false, 'è', 0, (Key)'è', 'E', 18 };
+					yield return new object [] { 'È', true, false, false, 'È', 0, (Key)'È' | Key.ShiftMask, 'E', 18 };
+					yield return new object [] { 'í', false, false, false, 'í', 0, (Key)'í', 'I', 23 };
+					yield return new object [] { 'Í', true, false, false, 'Í', 0, (Key)'Í' | Key.ShiftMask, 'I', 23 };
+					yield return new object [] { 'ì', false, false, false, 'ì', 0, (Key)'ì', 'I', 23 };
+					yield return new object [] { 'Ì', true, false, false, 'Ì', 0, (Key)'Ì' | Key.ShiftMask, 'I', 23 };
+					yield return new object [] { 'ó', false, false, false, 'ó', 0, (Key)'ó', 'O', 24 };
+					yield return new object [] { 'Ó', true, false, false, 'Ó', 0, (Key)'Ó' | Key.ShiftMask, 'O', 24 };
+					yield return new object [] { 'ò', false, false, false, 'Ó', 0, (Key)'ò', 'O', 24 };
+					yield return new object [] { 'Ò', true, false, false, 'Ò', 0, (Key)'Ò' | Key.ShiftMask, 'O', 24 };
+					yield return new object [] { 'ú', false, false, false, 'ú', 0, (Key)'ú', 'U', 22 };
+					yield return new object [] { 'Ú', true, false, false, 'Ú', 0, (Key)'Ú' | Key.ShiftMask, 'U', 22 };
+					yield return new object [] { 'ù', false, false, false, 'ù', 0, (Key)'ù', 'U', 22 };
+					yield return new object [] { 'Ù', true, false, false, 'Ù', 0, (Key)'Ù' | Key.ShiftMask, 'U', 22 };
+					yield return new object [] { 'ö', false, false, false, 'ó', 0, (Key)'ö', 'O', 24 };
+					yield return new object [] { 'Ö', true, false, false, 'Ó', 0, (Key)'Ö' | Key.ShiftMask, 'O', 24 };
+					yield return new object [] { '<', false, false, false, 226, 86, (Key)'<', 226, 86 };
+					yield return new object [] { '>', true, false, false, 226, 86, (Key)'>' | Key.ShiftMask, 226, 86 };
+					yield return new object [] { '<', true, true, false, 226, 86, (Key)'<' | Key.ShiftMask | Key.AltMask, 226, 86 };
+					yield return new object [] { '<', true, true, true, 226, 86, (Key)'<' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 226, 86 };
+					yield return new object [] { 'ç', false, false, false, 192, 39, (Key)'ç', 192, 39 };
+					yield return new object [] { 'Ç', true, false, false, 192, 39, (Key)'Ç' | Key.ShiftMask, 192, 39 };
+					yield return new object [] { 'ç', true, true, false, 192, 39, (Key)'ç' | Key.ShiftMask | Key.AltMask, 192, 39 };
+					yield return new object [] { 'ç', true, true, true, 192, 39, (Key)'ç' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 192, 39 };
+					yield return new object [] { '¨', false, true, true, 187, 26, (Key)'¨' | Key.AltMask | Key.CtrlMask, 187, 26 };
+					yield return new object [] { (uint)Key.PageUp, false, false, false, 33, 73, Key.PageUp, 33, 73 };
+					yield return new object [] { (uint)Key.PageUp, true, false, false, 33, 73, Key.PageUp | Key.ShiftMask, 33, 73 };
+					yield return new object [] { (uint)Key.PageUp, true, true, false, 33, 73, Key.PageUp | Key.ShiftMask | Key.AltMask, 33, 73 };
+					yield return new object [] { (uint)Key.PageUp, true, true, true, 33, 73, Key.PageUp | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 33, 73 };
+				}
+			}
+
+			IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
+		}
+	}
+}

+ 2 - 1
UnitTests/KeyTests.cs → UnitTests/Drivers/KeyTests.cs

@@ -1,7 +1,8 @@
 using System;
 using System;
+using Terminal.Gui;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.DriverTests {
 	public class KeyTests {
 	public class KeyTests {
 		enum SimpleEnum { Zero, One, Two, Three, Four, Five }
 		enum SimpleEnum { Zero, One, Two, Three, Four, Five }
 
 

+ 2 - 2
UnitTests/ContextMenuTests.cs → UnitTests/Menus/ContextMenuTests.cs

@@ -2,9 +2,9 @@
 using System.Threading;
 using System.Threading;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
-using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
+//using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.MenuTests {
 	public class ContextMenuTests {
 	public class ContextMenuTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 

+ 2 - 2
UnitTests/MenuTests.cs → UnitTests/Menus/MenuTests.cs

@@ -3,9 +3,9 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
-using static Terminal.Gui.Views.MenuTests;
+//using static Terminal.Gui.ViewTests.MenuTests;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.MenuTests {
 	public class MenuTests {
 	public class MenuTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 

+ 6 - 6
UnitTests/TestHelpers.cs

@@ -37,9 +37,9 @@ public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
 	/// <param name="fakeClipboardIsSupportedAlwaysTrue">Only valid if <paramref name="autoInit"/> is true.
 	/// <param name="fakeClipboardIsSupportedAlwaysTrue">Only valid if <paramref name="autoInit"/> is true.
 	/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
 	/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
 	public AutoInitShutdownAttribute (bool autoInit = true, bool autoShutdown = true,
 	public AutoInitShutdownAttribute (bool autoInit = true, bool autoShutdown = true,
-		Type consoleDriverType = null, 
-		bool useFakeClipboard = false, 
-		bool fakeClipboardAlwaysThrowsNotSupportedException = false, 
+		Type consoleDriverType = null,
+		bool useFakeClipboard = false,
+		bool fakeClipboardAlwaysThrowsNotSupportedException = false,
 		bool fakeClipboardIsSupportedAlwaysTrue = false)
 		bool fakeClipboardIsSupportedAlwaysTrue = false)
 	{
 	{
 		//Assert.True (autoInit == false && consoleDriverType == null);
 		//Assert.True (autoInit == false && consoleDriverType == null);
@@ -54,7 +54,7 @@ public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
 
 
 	static bool _init = false;
 	static bool _init = false;
 	bool AutoInit { get; }
 	bool AutoInit { get; }
-	bool AutoShutdown { get;  }
+	bool AutoShutdown { get; }
 	Type DriverType;
 	Type DriverType;
 
 
 	public override void Before (MethodInfo methodUnderTest)
 	public override void Before (MethodInfo methodUnderTest)
@@ -251,7 +251,7 @@ class TestHelpers {
 
 
 				var match = expectedColors.Where (e => e.Value == val).ToList ();
 				var match = expectedColors.Where (e => e.Value == val).ToList ();
 				if (match.Count == 0) {
 				if (match.Count == 0) {
-					throw new Exception ($"Unexpected color {DescribeColor (val)} was used at row {r} and col {c} (indexes start at 0).  Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => c.Value))})");
+					throw new Exception ($"Unexpected color {DescribeColor (val)} was used at row {r} and col {c} (indexes start at 0).  Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => DescribeColor (c.Value)))})");
 				} else if (match.Count > 1) {
 				} else if (match.Count > 1) {
 					throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value");
 					throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value");
 				}
 				}
@@ -260,7 +260,7 @@ class TestHelpers {
 				var userExpected = line [c];
 				var userExpected = line [c];
 
 
 				if (colorUsed != userExpected) {
 				if (colorUsed != userExpected) {
-					throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0).  Color index used was {DescribeColor (colorUsed)} but test expected {DescribeColor (userExpected)} (these are indexes into the expectedColors array)");
+					throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0).  Color index used was {colorUsed} ({DescribeColor (val)}) but test expected {userExpected} ({DescribeColor (expectedColors [int.Parse (userExpected.ToString ())].Value)}) (these are indexes into the expectedColors array)");
 				}
 				}
 			}
 			}
 
 

+ 1 - 1
UnitTests/CollectionNavigatorTests.cs → UnitTests/Text/CollectionNavigatorTests.cs

@@ -2,7 +2,7 @@
 using System.Threading;
 using System.Threading;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TextTests {
 	public class CollectionNavigatorTests {
 	public class CollectionNavigatorTests {
 		static string [] simpleStrings = new string []{
 		static string [] simpleStrings = new string []{
 		    "appricot", // 0
 		    "appricot", // 0

+ 63 - 34
UnitTests/TextFormatterTests.cs → UnitTests/Text/TextFormatterTests.cs

@@ -2,14 +2,14 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
-using Terminal.Gui.Views;
+using Terminal.Gui;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TextTests {
 	public class TextFormatterTests {
 	public class TextFormatterTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 
@@ -394,7 +394,7 @@ namespace Terminal.Gui.Core {
 			bool supportFirstUpperCase = true;
 			bool supportFirstUpperCase = true;
 
 
 			var text = ustring.Empty;
 			var text = ustring.Empty;
-			Rune hotKeySpecifier = (Rune)0;
+			var hotKeySpecifier = (Rune)0;
 			int hotPos = 0;
 			int hotPos = 0;
 			Key hotKey = Key.Unknown;
 			Key hotKey = Key.Unknown;
 			bool result = false;
 			bool result = false;
@@ -437,7 +437,7 @@ namespace Terminal.Gui.Core {
 			bool supportFirstUpperCase = true;
 			bool supportFirstUpperCase = true;
 
 
 			var text = ustring.Empty;
 			var text = ustring.Empty;
-			Rune hotKeySpecifier = (Rune)0;
+			var hotKeySpecifier = (Rune)0;
 			int hotPos = 0;
 			int hotPos = 0;
 			Key hotKey = Key.Unknown;
 			Key hotKey = Key.Unknown;
 			bool result = false;
 			bool result = false;
@@ -2162,9 +2162,7 @@ namespace Terminal.Gui.Core {
 			var height = 8;
 			var height = 8;
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			var breakLines = "";
 			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
+			foreach (var line in wrappedLines) 				breakLines += $"{line}{Environment.NewLine}";
 			var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () };
 			var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 
 
@@ -2202,9 +2200,7 @@ namespace Terminal.Gui.Core {
 			var height = 3;
 			var height = 3;
 			var wrappedLines = TextFormatter.WordWrap (text, height, true);
 			var wrappedLines = TextFormatter.WordWrap (text, height, true);
 			var breakLines = "";
 			var breakLines = "";
-			for (int i = 0; i < wrappedLines.Count; i++) {
-				breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}";
-			}
+			for (int i = 0; i < wrappedLines.Count; i++) 				breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}";
 			var label = new Label (breakLines) {
 			var label = new Label (breakLines) {
 				TextDirection = TextDirection.TopBottom_LeftRight,
 				TextDirection = TextDirection.TopBottom_LeftRight,
 				Width = Dim.Fill (),
 				Width = Dim.Fill (),
@@ -2241,9 +2237,7 @@ namespace Terminal.Gui.Core {
 			var height = 8;
 			var height = 8;
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			var breakLines = "";
 			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
+			foreach (var line in wrappedLines) 				breakLines += $"{line}{Environment.NewLine}";
 			var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () };
 			var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 
 
@@ -2282,9 +2276,7 @@ namespace Terminal.Gui.Core {
 			var height = 4;
 			var height = 4;
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			var breakLines = "";
 			var breakLines = "";
-			for (int i = 0; i < wrappedLines.Count; i++) {
-				breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}";
-			}
+			for (int i = 0; i < wrappedLines.Count; i++) 				breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}";
 			var label = new Label (breakLines) {
 			var label = new Label (breakLines) {
 				TextDirection = TextDirection.TopBottom_LeftRight,
 				TextDirection = TextDirection.TopBottom_LeftRight,
 				Width = Dim.Fill (),
 				Width = Dim.Fill (),
@@ -2433,13 +2425,13 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (ustring.Make (new Rune [] { 't', tag, 's', 't' }), tf.ReplaceHotKeyWithTag (text, hotPos));
 			Assert.Equal (ustring.Make (new Rune [] { 't', tag, 's', 't' }), tf.ReplaceHotKeyWithTag (text, hotPos));
 
 
 			var result = tf.ReplaceHotKeyWithTag (text, hotPos);
 			var result = tf.ReplaceHotKeyWithTag (text, hotPos);
-			Assert.Equal ('e', (uint)(result.ToRunes () [1]));
+			Assert.Equal ('e', result.ToRunes () [1]);
 
 
 			text = "Ok";
 			text = "Ok";
 			tag = 'O';
 			tag = 'O';
 			hotPos = 0;
 			hotPos = 0;
 			Assert.Equal (ustring.Make (new Rune [] { tag, 'k' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
 			Assert.Equal (ustring.Make (new Rune [] { tag, 'k' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
-			Assert.Equal ('O', (uint)(result.ToRunes () [0]));
+			Assert.Equal ('O', result.ToRunes () [0]);
 
 
 			text = "[◦ Ok ◦]";
 			text = "[◦ Ok ◦]";
 			text = ustring.Make (new Rune [] { '[', '◦', ' ', 'O', 'k', ' ', '◦', ']' });
 			text = ustring.Make (new Rune [] { '[', '◦', ' ', 'O', 'k', ' ', '◦', ']' });
@@ -2449,13 +2441,13 @@ namespace Terminal.Gui.Core {
 			tag = 'O';
 			tag = 'O';
 			hotPos = 3;
 			hotPos = 3;
 			Assert.Equal (ustring.Make (new Rune [] { '[', '◦', ' ', tag, 'k', ' ', '◦', ']' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
 			Assert.Equal (ustring.Make (new Rune [] { '[', '◦', ' ', tag, 'k', ' ', '◦', ']' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
-			Assert.Equal ('O', (uint)(result.ToRunes () [3]));
+			Assert.Equal ('O', result.ToRunes () [3]);
 
 
 			text = "^k";
 			text = "^k";
 			tag = '^';
 			tag = '^';
 			hotPos = 0;
 			hotPos = 0;
 			Assert.Equal (ustring.Make (new Rune [] { tag, 'k' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
 			Assert.Equal (ustring.Make (new Rune [] { tag, 'k' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
-			Assert.Equal ('^', (uint)(result.ToRunes () [0]));
+			Assert.Equal ('^', result.ToRunes () [0]);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -2814,37 +2806,37 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		[Fact]
 		public void System_Rune_ColumnWidth ()
 		public void System_Rune_ColumnWidth ()
 		{
 		{
-			var c = new System.Rune ('a');
+			var c = new Rune ('a');
 			Assert.Equal (1, Rune.ColumnWidth (c));
 			Assert.Equal (1, Rune.ColumnWidth (c));
 			Assert.Equal (1, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
 			Assert.Equal (1, ustring.Make (c).Length);
 
 
-			c = new System.Rune ('b');
+			c = new Rune ('b');
 			Assert.Equal (1, Rune.ColumnWidth (c));
 			Assert.Equal (1, Rune.ColumnWidth (c));
 			Assert.Equal (1, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
 			Assert.Equal (1, ustring.Make (c).Length);
 
 
-			c = new System.Rune (123);
+			c = new Rune (123);
 			Assert.Equal (1, Rune.ColumnWidth (c));
 			Assert.Equal (1, Rune.ColumnWidth (c));
 			Assert.Equal (1, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
 			Assert.Equal (1, ustring.Make (c).Length);
 
 
-			c = new System.Rune ('\u1150');
+			c = new Rune ('\u1150');
 			Assert.Equal (2, Rune.ColumnWidth (c));      // 0x1150	ᅐ	Unicode Technical Report #11
 			Assert.Equal (2, Rune.ColumnWidth (c));      // 0x1150	ᅐ	Unicode Technical Report #11
 			Assert.Equal (2, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (2, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (3, ustring.Make (c).Length);
 			Assert.Equal (3, ustring.Make (c).Length);
 
 
-			c = new System.Rune ('\u1161');
+			c = new Rune ('\u1161');
 			Assert.Equal (0, Rune.ColumnWidth (c));      // 0x1161	ᅡ	column width of 0
 			Assert.Equal (0, Rune.ColumnWidth (c));      // 0x1161	ᅡ	column width of 0
 			Assert.Equal (0, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (0, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (3, ustring.Make (c).Length);
 			Assert.Equal (3, ustring.Make (c).Length);
 
 
-			c = new System.Rune (31);
+			c = new Rune (31);
 			Assert.Equal (-1, Rune.ColumnWidth (c));        // non printable character
 			Assert.Equal (-1, Rune.ColumnWidth (c));        // non printable character
 			Assert.Equal (0, ustring.Make (c).ConsoleWidth);// ConsoleWidth only returns zero or greater than zero
 			Assert.Equal (0, ustring.Make (c).ConsoleWidth);// ConsoleWidth only returns zero or greater than zero
 			Assert.Equal (1, ustring.Make (c).Length);
 			Assert.Equal (1, ustring.Make (c).Length);
 
 
-			c = new System.Rune (127);
+			c = new Rune (127);
 			Assert.Equal (-1, Rune.ColumnWidth (c));       // non printable character
 			Assert.Equal (-1, Rune.ColumnWidth (c));       // non printable character
 			Assert.Equal (0, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (0, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
 			Assert.Equal (1, ustring.Make (c).Length);
@@ -2896,9 +2888,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("nd", list1 [10].ToString ());
 			Assert.Equal ("nd", list1 [10].ToString ());
 			Assert.Equal ("Line", list1 [11].ToString ());
 			Assert.Equal ("Line", list1 [11].ToString ());
 			Assert.Equal ("- 2.", list1 [^1].ToString ());
 			Assert.Equal ("- 2.", list1 [^1].ToString ());
-			foreach (var txt in list1) {
-				wrappedText1 += txt;
-			}
+			foreach (var txt in list1) 				wrappedText1 += txt;
 			Assert.Equal (" Asentencehaswords.  This isthesecondLine- 2.", wrappedText1);
 			Assert.Equal (" Asentencehaswords.  This isthesecondLine- 2.", wrappedText1);
 
 
 			// With preserveTrailingSpaces = true.
 			// With preserveTrailingSpaces = true.
@@ -2920,9 +2910,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("Line", list2 [13].ToString ());
 			Assert.Equal ("Line", list2 [13].ToString ());
 			Assert.Equal (" - ", list2 [14].ToString ());
 			Assert.Equal (" - ", list2 [14].ToString ());
 			Assert.Equal ("2. ", list2 [^1].ToString ());
 			Assert.Equal ("2. ", list2 [^1].ToString ());
-			foreach (var txt in list2) {
-				wrappedText2 += txt;
-			}
+			foreach (var txt in list2) 				wrappedText2 += txt;
 			Assert.Equal (" A sentence has words.  This is the second Line - 2. ", wrappedText2);
 			Assert.Equal (" A sentence has words.  This is the second Line - 2. ", wrappedText2);
 		}
 		}
 
 
@@ -3430,7 +3418,7 @@ This TextFormatter (tf2) is rewritten.
 		[Fact]
 		[Fact]
 		public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes ()
 		public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes ()
 		{
 		{
-			List<ustring> text = new List<ustring> () { "Hello", "World" };
+			var text = new List<ustring> () { "Hello", "World" };
 			Assert.Equal (2, TextFormatter.GetSumMaxCharWidth (text));
 			Assert.Equal (2, TextFormatter.GetSumMaxCharWidth (text));
 			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
 			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
 			text = new List<ustring> () { "こんにちは", "世界" };
 			text = new List<ustring> () { "こんにちは", "世界" };
@@ -4259,5 +4247,46 @@ This TextFormatter (tf2) is rewritten.
 0111000000
 0111000000
 0000000000", expectedColors);
 0000000000", expectedColors);
 		}
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Colors_On_TextAlignment_Right_And_Bottom ()
+		{
+			var labelRight = new Label ("Test") {
+				Width = 6,
+				Height = 1,
+				TextAlignment = TextAlignment.Right,
+				ColorScheme = Colors.Base
+			};
+			var labelBottom = new Label ("Test", TextDirection.TopBottom_LeftRight) {
+				Y = 1,
+				Width = 1,
+				Height = 6,
+				VerticalTextAlignment = VerticalTextAlignment.Bottom,
+				ColorScheme = Colors.Base
+			};
+			var top = Application.Top;
+			top.Add (labelRight, labelBottom);
+
+			Application.Begin (top);
+			((FakeDriver)Application.Driver).SetBufferSize (7, 7);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+  Test
+      
+      
+T     
+e     
+s     
+t     ", output);
+
+			TestHelpers.AssertDriverColorsAre (@"
+000000
+0
+0
+0
+0
+0
+0", new Attribute [] { Colors.Base.Normal });
+		}
 	}
 	}
 }
 }

+ 34 - 34
UnitTests/DialogTests.cs → UnitTests/TopLevels/DialogTests.cs

@@ -9,7 +9,7 @@ using System.Globalization;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 using NStack;
 using NStack;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.TopLevelTests {
 
 
 	public class DialogTests {
 	public class DialogTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
@@ -29,7 +29,7 @@ namespace Terminal.Gui.Views {
 		[AutoInitShutdown]
 		[AutoInitShutdown]
 		public void ButtonAlignment_One ()
 		public void ButtonAlignment_One ()
 		{
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 
 
 			var title = "1234";
 			var title = "1234";
@@ -37,8 +37,8 @@ namespace Terminal.Gui.Views {
 			var btnText = "ok";
 			var btnText = "ok";
 			var buttonRow = $"{d.VLine}   {d.LeftBracket} {btnText} {d.RightBracket}   {d.VLine}";
 			var buttonRow = $"{d.VLine}   {d.LeftBracket} {btnText} {d.RightBracket}   {d.VLine}";
 			var width = buttonRow.Length;
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 
 
 			d.SetBufferSize (width, 3);
 			d.SetBufferSize (width, 3);
 
 
@@ -74,7 +74,7 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 			// E.g "|[ yes ][ no ]|"
 			// E.g "|[ yes ][ no ]|"
@@ -85,8 +85,8 @@ namespace Terminal.Gui.Views {
 
 
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}";
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}";
 			var width = buttonRow.Length;
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 
 
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
@@ -123,7 +123,7 @@ namespace Terminal.Gui.Views {
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 			bool firstIteration = false;
 			bool firstIteration = false;
 
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 			// E.g "|[ yes ][ no ]|"
 			// E.g "|[ yes ][ no ]|"
@@ -134,8 +134,8 @@ namespace Terminal.Gui.Views {
 
 
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}";
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}";
 			var width = buttonRow.Length;
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 
 
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
@@ -191,7 +191,7 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 			// E.g "|[ yes ][ no ][ maybe ]|"
 			// E.g "|[ yes ][ no ][ maybe ]|"
@@ -204,8 +204,8 @@ namespace Terminal.Gui.Views {
 
 
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {btn3} {d.VLine}";
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {btn3} {d.VLine}";
 			var width = buttonRow.Length;
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 
 
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
@@ -241,7 +241,7 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 
 
@@ -257,8 +257,8 @@ namespace Terminal.Gui.Views {
 
 
 			var buttonRow = $"{d.VLine} {btn1} {btn2} {btn3} {btn4} {d.VLine}";
 			var buttonRow = $"{d.VLine} {btn1} {btn2} {btn3} {btn4} {d.VLine}";
 			var width = buttonRow.Length;
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
 			// Default - Center
 			// Default - Center
@@ -294,7 +294,7 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 
 
@@ -349,7 +349,7 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 
 
@@ -368,8 +368,8 @@ namespace Terminal.Gui.Views {
 			//                         12345                           123456
 			//                         12345                           123456
 			var buttonRow = $"{d.VLine}     {btn1} {btn2} {btn3} {btn4}      {d.VLine}";
 			var buttonRow = $"{d.VLine}     {btn1} {btn2} {btn3} {btn4}      {d.VLine}";
 			var width = ustring.Make (buttonRow).ConsoleWidth;
 			var width = ustring.Make (buttonRow).ConsoleWidth;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 			d.SetBufferSize (width, 3);
 			d.SetBufferSize (width, 3);
 
 
 			// Default - Center
 			// Default - Center
@@ -405,7 +405,7 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 
 
@@ -423,8 +423,8 @@ namespace Terminal.Gui.Views {
 			//                         12345                          123456
 			//                         12345                          123456
 			var buttonRow = $"{d.VLine}     {btn1} {btn2} {btn3} {btn4}      {d.VLine}";
 			var buttonRow = $"{d.VLine}     {btn1} {btn2} {btn3} {btn4}      {d.VLine}";
 			var width = buttonRow.Length;
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
 			// Default - Center
 			// Default - Center
@@ -460,14 +460,14 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 
 
 			var buttonRow = $"{d.VLine}        {d.VLine}";
 			var buttonRow = $"{d.VLine}        {d.VLine}";
 			var width = buttonRow.Length;
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null);
@@ -482,15 +482,15 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 			var btnText = "ok";
 			var btnText = "ok";
 			var buttonRow = $"{d.VLine}   {d.LeftBracket} {btnText} {d.RightBracket}   {d.VLine}";
 			var buttonRow = $"{d.VLine}   {d.LeftBracket} {btnText} {d.RightBracket}   {d.VLine}";
 
 
 			var width = buttonRow.Length;
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText));
@@ -504,7 +504,7 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			Application.RunState runstate = null;
 			Application.RunState runstate = null;
 
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 			var btn1Text = "yes";
 			var btn1Text = "yes";
@@ -516,8 +516,8 @@ namespace Terminal.Gui.Views {
 			var width = $@"{d.VLine} {btn1} {btn2} {d.VLine}".Length;
 			var width = $@"{d.VLine} {btn1} {btn2} {d.VLine}".Length;
 			d.SetBufferSize (width, 3);
 			d.SetBufferSize (width, 3);
 
 
-			var topRow = $"{d.ULCorner} {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}{d.URCorner}";
-			var bottomRow = $"{d.LLCorner}{new String (d.HLine.ToString () [0], width - 2)}{d.LRCorner}";
+			var topRow = $"{d.ULCorner} {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}{d.URCorner}";
+			var bottomRow = $"{d.LLCorner}{new string (d.HLine.ToString () [0], width - 2)}{d.LRCorner}";
 
 
 			// Default (center)
 			// Default (center)
 			var dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Center };
 			var dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Center };
@@ -550,7 +550,7 @@ namespace Terminal.Gui.Views {
 			// Right
 			// Right
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Right };
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Right };
 			runstate = Application.Begin (dlg);
 			runstate = Application.Begin (dlg);
-			buttonRow = $"{d.VLine}{new String (' ', width - btn1.Length - 2)}{btn1}{d.VLine}";
+			buttonRow = $"{d.VLine}{new string (' ', width - btn1.Length - 2)}{btn1}{d.VLine}";
 			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 
 
 			// Now add a second button
 			// Now add a second button
@@ -564,7 +564,7 @@ namespace Terminal.Gui.Views {
 			// Left
 			// Left
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Left };
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Left };
 			runstate = Application.Begin (dlg);
 			runstate = Application.Begin (dlg);
-			buttonRow = $"{d.VLine}{btn1}{new String (' ', width - btn1.Length - 2)}{d.VLine}";
+			buttonRow = $"{d.VLine}{btn1}{new string (' ', width - btn1.Length - 2)}{d.VLine}";
 			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 
 
 			// Now add a second button
 			// Now add a second button

+ 16 - 37
UnitTests/MdiTests.cs → UnitTests/TopLevels/MdiTests.cs

@@ -3,12 +3,13 @@ using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using Terminal.Gui;
 using Xunit;
 using Xunit;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TopLevelTests {
 	public class MdiTests {
 	public class MdiTests {
 		public MdiTests ()
 		public MdiTests ()
 		{
 		{
@@ -94,17 +95,11 @@ namespace Terminal.Gui.Core {
 
 
 			Application.Iteration += () => {
 			Application.Iteration += () => {
 				Assert.Null (Application.MdiChildes);
 				Assert.Null (Application.MdiChildes);
-				if (iterations == 4) {
-					Assert.True (Application.Current == d);
-				} else if (iterations == 3) {
-					Assert.True (Application.Current == top4);
-				} else if (iterations == 2) {
-					Assert.True (Application.Current == top3);
-				} else if (iterations == 1) {
-					Assert.True (Application.Current == top2);
-				} else {
-					Assert.True (Application.Current == top1);
-				}
+				if (iterations == 4) 					Assert.True (Application.Current == d);
+else if (iterations == 3) 					Assert.True (Application.Current == top4);
+else if (iterations == 2) 					Assert.True (Application.Current == top3);
+else if (iterations == 1) 					Assert.True (Application.Current == top2);
+else 					Assert.True (Application.Current == top1);
 				Application.RequestStop (top1);
 				Application.RequestStop (top1);
 				iterations--;
 				iterations--;
 			};
 			};
@@ -171,9 +166,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (d.Running);
 					Assert.False (d.Running);
 				} else {
 				} else {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 					Assert.Equal (iterations, Application.MdiChildes.Count);
-					for (int i = 0; i < iterations; i++) {
-						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
-					}
+					for (int i = 0; i < iterations; i++) 						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
 				}
 				}
 				iterations--;
 				iterations--;
 			};
 			};
@@ -231,9 +224,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (d.Running);
 					Assert.False (d.Running);
 				} else {
 				} else {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 					Assert.Equal (iterations, Application.MdiChildes.Count);
-					for (int i = 0; i < iterations; i++) {
-						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
-					}
+					for (int i = 0; i < iterations; i++) 						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
 				}
 				}
 				iterations--;
 				iterations--;
 			};
 			};
@@ -292,9 +283,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (d.Running);
 					Assert.False (d.Running);
 				} else {
 				} else {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 					Assert.Equal (iterations, Application.MdiChildes.Count);
-					for (int i = 0; i < iterations; i++) {
-						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
-					}
+					for (int i = 0; i < iterations; i++) 						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
 				}
 				}
 				iterations--;
 				iterations--;
 			};
 			};
@@ -392,9 +381,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (Application.Current.Running);
 					Assert.False (Application.Current.Running);
 				} else {
 				} else {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 					Assert.Equal (iterations, Application.MdiChildes.Count);
-					for (int i = 0; i < iterations; i++) {
-						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
-					}
+					for (int i = 0; i < iterations; i++) 						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
 				}
 				}
 				iterations--;
 				iterations--;
 			};
 			};
@@ -461,10 +448,8 @@ namespace Terminal.Gui.Core {
 					Assert.True (c4.Running);
 					Assert.True (c4.Running);
 				} else {
 				} else {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 					Assert.Equal (iterations, Application.MdiChildes.Count);
-					for (int i = 0; i < iterations; i++) {
-						Assert.Equal ((iterations - i + (iterations == 4 && i == 0 ? 2 : 1)).ToString (),
+					for (int i = 0; i < iterations; i++) 						Assert.Equal ((iterations - i + (iterations == 4 && i == 0 ? 2 : 1)).ToString (),
 							Application.MdiChildes [i].Id);
 							Application.MdiChildes [i].Id);
-					}
 				}
 				}
 				iterations--;
 				iterations--;
 			};
 			};
@@ -586,9 +571,7 @@ namespace Terminal.Gui.Core {
 					};
 					};
 
 
 					stage.Closed += (_) => {
 					stage.Closed += (_) => {
-						if (iterations == 11) {
-							allStageClosed = true;
-						}
+						if (iterations == 11) 							allStageClosed = true;
 						Assert.Equal (iterations, Application.MdiChildes.Count);
 						Assert.Equal (iterations, Application.MdiChildes.Count);
 						if (running) {
 						if (running) {
 							stageCompleted = true;
 							stageCompleted = true;
@@ -610,16 +593,12 @@ namespace Terminal.Gui.Core {
 					running = false;
 					running = false;
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 
 
-				} else if (!mdiRequestStop && running && !allStageClosed) {
-					Assert.Equal (iterations, Application.MdiChildes.Count);
-
-				} else if (!mdiRequestStop && !running && allStageClosed) {
+				} else if (!mdiRequestStop && running && !allStageClosed) 					Assert.Equal (iterations, Application.MdiChildes.Count);
+else if (!mdiRequestStop && !running && allStageClosed) {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 					mdiRequestStop = true;
 					mdiRequestStop = true;
 					mdi.RequestStop ();
 					mdi.RequestStop ();
-				} else {
-					Assert.Empty (Application.MdiChildes);
-				}
+				} else 					Assert.Empty (Application.MdiChildes);
 			};
 			};
 
 
 			Application.Run (mdi);
 			Application.Run (mdi);

+ 3 - 2
UnitTests/MessageBoxTests.cs → UnitTests/TopLevels/MessageBoxTests.cs

@@ -2,8 +2,9 @@
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 using System.Text;
 using System.Text;
+using Terminal.Gui;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.TopLevelTests {
 
 
 	public class MessageBoxTests {
 	public class MessageBoxTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
@@ -54,7 +55,7 @@ namespace Terminal.Gui.Views {
 				iterations++;
 				iterations++;
 
 
 				if (iterations == 0) {
 				if (iterations == 0) {
-					StringBuilder aboutMessage = new StringBuilder ();
+					var aboutMessage = new StringBuilder ();
 					aboutMessage.AppendLine (@"A comprehensive sample library for");
 					aboutMessage.AppendLine (@"A comprehensive sample library for");
 					aboutMessage.AppendLine (@"");
 					aboutMessage.AppendLine (@"");
 					aboutMessage.AppendLine (@"  _______                  _             _   _____       _  ");
 					aboutMessage.AppendLine (@"  _______                  _             _   _____       _  ");

+ 77 - 13
UnitTests/ToplevelTests.cs → UnitTests/TopLevels/ToplevelTests.cs

@@ -1,8 +1,9 @@
 using System;
 using System;
+using Terminal.Gui;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TopLevelTests {
 	public class ToplevelTests {
 	public class ToplevelTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 
@@ -695,8 +696,7 @@ namespace Terminal.Gui.Core {
 					((FakeDriver)Application.Driver).SetBufferSize (40, 15);
 					((FakeDriver)Application.Driver).SetBufferSize (40, 15);
 					MessageBox.Query ("About", "Hello Word", "Ok");
 					MessageBox.Query ("About", "Hello Word", "Ok");
 
 
-				} else if (iterations == 1) {
-					TestHelpers.AssertDriverContentsWithFrameAre (@"
+				} else if (iterations == 1) TestHelpers.AssertDriverContentsWithFrameAre (@"
  File                                   
  File                                   
 ┌ Window ──────────────────────────────┐
 ┌ Window ──────────────────────────────┐
 │                                      │
 │                                      │
@@ -712,8 +712,7 @@ namespace Terminal.Gui.Core {
 │                                      │
 │                                      │
 └──────────────────────────────────────┘
 └──────────────────────────────────────┘
  CTRL-N New                             ", output);
  CTRL-N New                             ", output);
-
-				} else if (iterations == 2) {
+				else if (iterations == 2) {
 					Assert.Null (Application.MouseGrabView);
 					Assert.Null (Application.MouseGrabView);
 					// Grab the mouse
 					// Grab the mouse
 					ReflectionTools.InvokePrivate (
 					ReflectionTools.InvokePrivate (
@@ -816,11 +815,8 @@ namespace Terminal.Gui.Core {
 
 
 					Assert.Null (Application.MouseGrabView);
 					Assert.Null (Application.MouseGrabView);
 
 
-				} else if (iterations == 8) {
-					Application.RequestStop ();
-				} else if (iterations == 9) {
-					Application.RequestStop ();
-				}
+				} else if (iterations == 8) Application.RequestStop ();
+				else if (iterations == 9) Application.RequestStop ();
 			};
 			};
 
 
 			Application.Run ();
 			Application.Run ();
@@ -960,12 +956,80 @@ namespace Terminal.Gui.Core {
 
 
 					Assert.Null (Application.MouseGrabView);
 					Assert.Null (Application.MouseGrabView);
 
 
-				} else if (iterations == 8) {
-					Application.RequestStop ();
-				}
+				} else if (iterations == 8) Application.RequestStop ();
 			};
 			};
 
 
 			Application.Run ();
 			Application.Run ();
 		}
 		}
+
+		[Fact, AutoInitShutdown]
+		public void EnsureVisibleBounds_With_Border_Null_Not_Throws ()
+		{
+			var top = new Toplevel ();
+			Application.Begin (top);
+
+			var exception = Record.Exception (() => ((FakeDriver)Application.Driver).SetBufferSize (0, 10));
+			Assert.Null (exception);
+
+			exception = Record.Exception (() => ((FakeDriver)Application.Driver).SetBufferSize (10, 0));
+			Assert.Null (exception);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void OnEnter_OnLeave_Triggered_On_Application_Begin_End ()
+		{
+			var isEnter = false;
+			var isLeave = false;
+			var v = new View ();
+			v.Enter += (_) => isEnter = true;
+			v.Leave += (_) => isLeave = true;
+			var top = Application.Top;
+			top.Add (v);
+
+			Assert.False (v.CanFocus);
+			var exception = Record.Exception (() => top.OnEnter (top));
+			Assert.Null (exception);
+			exception = Record.Exception (() => top.OnLeave (top));
+			Assert.Null (exception);
+
+			v.CanFocus = true;
+			Application.Begin (top);
+
+			Assert.True (isEnter);
+			Assert.False (isLeave);
+
+			isEnter = false;
+			var d = new Dialog ();
+			var rs = Application.Begin (d);
+
+			Assert.False (isEnter);
+			Assert.True (isLeave);
+
+			isLeave = false;
+			Application.End (rs);
+
+			Assert.True (isEnter);
+			Assert.False (isLeave);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void PositionCursor_SetCursorVisibility_To_Invisible_If_Focused_Is_Null ()
+		{
+			var tf = new TextField ("test") { Width = 5 };
+			var view = new View () { Width = 10, Height = 10 };
+			view.Add (tf);
+			Application.Top.Add (view);
+			Application.Begin (Application.Top);
+
+			Assert.True (tf.HasFocus);
+			Application.Driver.GetCursorVisibility (out CursorVisibility cursor);
+			Assert.Equal (CursorVisibility.Default, cursor);
+
+			view.Enabled = false;
+			Assert.False (tf.HasFocus);
+			Application.Refresh ();
+			Application.Driver.GetCursorVisibility (out cursor);
+			Assert.Equal (CursorVisibility.Invisible, cursor);
+		}
 	}
 	}
 }
 }

+ 8 - 7
UnitTests/WindowTests.cs → UnitTests/TopLevels/WindowTests.cs

@@ -1,13 +1,14 @@
 using System;
 using System;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
-using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
+//using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 using NStack;
 using NStack;
+using Terminal.Gui;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TopLevelTests {
 	public class WindowTests {
 	public class WindowTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 
@@ -22,7 +23,7 @@ namespace Terminal.Gui.Core {
 			// Parameterless
 			// Parameterless
 			var r = new Window ();
 			var r = new Window ();
 			Assert.NotNull (r);
 			Assert.NotNull (r);
-			Assert.Equal(ustring.Empty, r.Title);
+			Assert.Equal (ustring.Empty, r.Title);
 			Assert.Equal (LayoutStyle.Computed, r.LayoutStyle);
 			Assert.Equal (LayoutStyle.Computed, r.LayoutStyle);
 			Assert.Equal ("Window()({X=0,Y=0,Width=0,Height=0})", r.ToString ());
 			Assert.Equal ("Window()({X=0,Y=0,Width=0,Height=0})", r.ToString ());
 			Assert.True (r.CanFocus);
 			Assert.True (r.CanFocus);
@@ -116,13 +117,13 @@ namespace Terminal.Gui.Core {
 			r.Title = expectedDuring = expectedAfter = "title";
 			r.Title = expectedDuring = expectedAfter = "title";
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 
 
-			expectedOld = r.Title.ToString();
+			expectedOld = r.Title.ToString ();
 			r.Title = expectedDuring = expectedAfter = "a different title";
 			r.Title = expectedDuring = expectedAfter = "a different title";
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 
 
 			// Now setup cancelling the change and change it back to "title"
 			// Now setup cancelling the change and change it back to "title"
 			cancel = true;
 			cancel = true;
-			expectedOld = r.Title.ToString();
+			expectedOld = r.Title.ToString ();
 			r.Title = expectedDuring = "title";
 			r.Title = expectedDuring = "title";
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 			r.Dispose ();
 			r.Dispose ();
@@ -154,7 +155,7 @@ namespace Terminal.Gui.Core {
 			r.Dispose ();
 			r.Dispose ();
 		}
 		}
 
 
-		[Fact,AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void MenuBar_And_StatusBar_Inside_Window ()
 		public void MenuBar_And_StatusBar_Inside_Window ()
 		{
 		{
 			var menu = new MenuBar (new MenuBarItem [] {
 			var menu = new MenuBar (new MenuBarItem [] {
@@ -175,7 +176,7 @@ namespace Terminal.Gui.Core {
 
 
 			var fv = new FrameView ("Frame View") {
 			var fv = new FrameView ("Frame View") {
 				Y = 1,
 				Y = 1,
-				Width = Dim.Fill(),
+				Width = Dim.Fill (),
 				Height = Dim.Fill (1)
 				Height = Dim.Fill (1)
 			};
 			};
 			var win = new Window ();
 			var win = new Window ();

+ 21 - 21
UnitTests/WizardTests.cs → UnitTests/TopLevels/WizardTests.cs

@@ -9,7 +9,7 @@ using System.Globalization;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 using NStack;
 using NStack;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.TopLevelTests {
 
 
 	public class WizardTests {
 	public class WizardTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
@@ -92,7 +92,7 @@ namespace Terminal.Gui.Views {
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
 		public void DefaultConstructor_SizedProperly ()
 		public void DefaultConstructor_SizedProperly ()
 		{
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var wizard = new Wizard ();
 			var wizard = new Wizard ();
 			Assert.NotEqual (0, wizard.Width);
 			Assert.NotEqual (0, wizard.Width);
@@ -104,7 +104,7 @@ namespace Terminal.Gui.Views {
 		// and that the title is correct
 		// and that the title is correct
 		public void ZeroStepWizard_Shows ()
 		public void ZeroStepWizard_Shows ()
 		{
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 			var stepTitle = "";
 			var stepTitle = "";
@@ -118,12 +118,12 @@ namespace Terminal.Gui.Views {
 			var btnNextText = "Finish";
 			var btnNextText = "Finish";
 			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
 			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
 
 
-			var topRow = $"{d.ULDCorner} {title}{stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}";
-			var row2 = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}";
+			var topRow = $"{d.ULDCorner} {title}{stepTitle} {new string (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}";
+			var row2 = $"{d.VDLine}{new string (' ', width - 2)}{d.VDLine}";
 			var row3 = row2;
 			var row3 = row2;
-			var separatorRow = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}";
-			var buttonRow = $"{d.VDLine}{btnBack}{new String (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
-			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
+			var separatorRow = $"{d.VDLine}{new string (' ', width - 2)}{d.VDLine}";
+			var buttonRow = $"{d.VDLine}{btnBack}{new string (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			var bottomRow = $"{d.LLDCorner}{new string (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
 
 
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			Application.End (Application.Begin (wizard));
 			Application.End (Application.Begin (wizard));
@@ -135,7 +135,7 @@ namespace Terminal.Gui.Views {
 		// and that the title is correct
 		// and that the title is correct
 		public void OneStepWizard_Shows ()
 		public void OneStepWizard_Shows ()
 		{
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 			var stepTitle = "ABCD";
 			var stepTitle = "ABCD";
@@ -149,13 +149,13 @@ namespace Terminal.Gui.Views {
 			var btnNextText = "Finish"; // "Next";
 			var btnNextText = "Finish"; // "Next";
 			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
 			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
 
 
-			var topRow = $"{d.ULDCorner} {title} - {stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 7)}{d.URDCorner}";
-			var row2 = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}";
+			var topRow = $"{d.ULDCorner} {title} - {stepTitle} {new string (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 7)}{d.URDCorner}";
+			var row2 = $"{d.VDLine}{new string (' ', width - 2)}{d.VDLine}";
 			var row3 = row2;
 			var row3 = row2;
 			var row4 = row3;
 			var row4 = row3;
-			var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
-			var buttonRow = $"{d.VDLine}{btnBack}{new String (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
-			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
+			var separatorRow = $"{d.VDLine}{new string (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
+			var buttonRow = $"{d.VDLine}{btnBack}{new string (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			var bottomRow = $"{d.LLDCorner}{new string (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
 
 
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			wizard.AddStep (new Wizard.WizardStep (stepTitle));
 			wizard.AddStep (new Wizard.WizardStep (stepTitle));
@@ -207,7 +207,7 @@ namespace Terminal.Gui.Views {
 		// this test is needed because Wizard overrides Dialog's title behavior ("Title - StepTitle")
 		// this test is needed because Wizard overrides Dialog's title behavior ("Title - StepTitle")
 		public void Setting_Title_Works ()
 		public void Setting_Title_Works ()
 		{
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 
 			var title = "1234";
 			var title = "1234";
 			var stepTitle = " - ABCD";
 			var stepTitle = " - ABCD";
@@ -219,13 +219,13 @@ namespace Terminal.Gui.Views {
 			var btnNextText = "Finish";
 			var btnNextText = "Finish";
 			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
 			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
 
 
-			var topRow = $"{d.ULDCorner} {title}{stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}";
-			var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
+			var topRow = $"{d.ULDCorner} {title}{stepTitle} {new string (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}";
+			var separatorRow = $"{d.VDLine}{new string (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
 
 
 			// Once this is fixed, revert to commented out line: https://github.com/gui-cs/Terminal.Gui/issues/1791
 			// Once this is fixed, revert to commented out line: https://github.com/gui-cs/Terminal.Gui/issues/1791
-			var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			var buttonRow = $"{d.VDLine}{new string (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
 			//var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
 			//var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
-			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
+			var bottomRow = $"{d.LLDCorner}{new string (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
 
 
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			wizard.AddStep (new Wizard.WizardStep ("ABCD"));
 			wizard.AddStep (new Wizard.WizardStep ("ABCD"));
@@ -560,13 +560,13 @@ namespace Terminal.Gui.Views {
 			runstate = Application.Begin (wizard);
 			runstate = Application.Begin (wizard);
 			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
 			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
 
 
-			Assert.Equal (step1.Title.ToString(), wizard.CurrentStep.Title.ToString());
+			Assert.Equal (step1.Title.ToString (), wizard.CurrentStep.Title.ToString ());
 			wizard.NextFinishButton.OnClicked ();
 			wizard.NextFinishButton.OnClicked ();
 			Assert.False (finishedFired);
 			Assert.False (finishedFired);
 			Assert.False (closedFired);
 			Assert.False (closedFired);
 
 
 			Assert.Equal (step2.Title.ToString (), wizard.CurrentStep.Title.ToString ());
 			Assert.Equal (step2.Title.ToString (), wizard.CurrentStep.Title.ToString ());
-			Assert.Equal (wizard.GetLastStep().Title.ToString(), wizard.CurrentStep.Title.ToString ());
+			Assert.Equal (wizard.GetLastStep ().Title.ToString (), wizard.CurrentStep.Title.ToString ());
 			wizard.NextFinishButton.OnClicked ();
 			wizard.NextFinishButton.OnClicked ();
 			Application.End (runstate);
 			Application.End (runstate);
 			Assert.True (finishedFired);
 			Assert.True (finishedFired);

+ 4 - 9
UnitTests/DimTests.cs → UnitTests/Types/DimTests.cs

@@ -6,14 +6,13 @@ using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using Terminal.Gui;
 using Terminal.Gui;
-using Terminal.Gui.Views;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TypeTests {
 	public class DimTests {
 	public class DimTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 
@@ -22,7 +21,7 @@ namespace Terminal.Gui.Core {
 			this.output = output;
 			this.output = output;
 			Console.OutputEncoding = System.Text.Encoding.Default;
 			Console.OutputEncoding = System.Text.Encoding.Default;
 			// Change current culture
 			// Change current culture
-			CultureInfo culture = CultureInfo.CreateSpecificCulture ("en-US");
+			var culture = CultureInfo.CreateSpecificCulture ("en-US");
 			Thread.CurrentThread.CurrentCulture = culture;
 			Thread.CurrentThread.CurrentCulture = culture;
 			Thread.CurrentThread.CurrentUICulture = culture;
 			Thread.CurrentThread.CurrentUICulture = culture;
 		}
 		}
@@ -646,9 +645,7 @@ namespace Terminal.Gui.Core {
 			};
 			};
 
 
 			Application.Iteration += () => {
 			Application.Iteration += () => {
-				while (count < 20) {
-					field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
-				}
+				while (count < 20) 					field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
 
 
 				Application.RequestStop ();
 				Application.RequestStop ();
 			};
 			};
@@ -1091,9 +1088,7 @@ namespace Terminal.Gui.Core {
 			};
 			};
 
 
 			Application.Iteration += () => {
 			Application.Iteration += () => {
-				while (count > 0) {
-					field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
-				}
+				while (count > 0) 					field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
 
 
 				Application.RequestStop ();
 				Application.RequestStop ();
 			};
 			};

+ 1 - 1
UnitTests/PointTests.cs → UnitTests/Types/PointTests.cs

@@ -1,7 +1,7 @@
 using System;
 using System;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Types {
+namespace Terminal.Gui.TypeTests {
 	public class PointTests {
 	public class PointTests {
 		[Fact]
 		[Fact]
 		public void Point_New ()
 		public void Point_New ()

+ 1 - 2
UnitTests/PosTests.cs → UnitTests/Types/PosTests.cs

@@ -5,14 +5,13 @@ using System.Data;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using Terminal.Gui;
 using Terminal.Gui;
-using Terminal.Gui.Views;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TypeTests {
 	public class PosTests {
 	public class PosTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 

+ 1 - 1
UnitTests/RectTests.cs → UnitTests/Types/RectTests.cs

@@ -1,7 +1,7 @@
 using System;
 using System;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Types {
+namespace Terminal.Gui.TypeTests {
 	public class RectTests {
 	public class RectTests {
 		[Fact]
 		[Fact]
 		public void Rect_New ()
 		public void Rect_New ()

+ 1 - 1
UnitTests/SizeTests.cs → UnitTests/Types/SizeTests.cs

@@ -1,7 +1,7 @@
 using System;
 using System;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Types {
+namespace Terminal.Gui.TypeTests {
 	public class SizeTests {
 	public class SizeTests {
 		[Fact]
 		[Fact]
 		public void Size_New ()
 		public void Size_New ()

+ 6 - 1
UnitTests/ScenarioTests.cs → UnitTests/UICatalog/ScenarioTests.cs

@@ -11,7 +11,7 @@ using Xunit.Abstractions;
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace UICatalog {
+namespace UICatalog.Tests {
 	public class ScenarioTests {
 	public class ScenarioTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 
@@ -69,6 +69,9 @@ namespace UICatalog {
 				scenario.Init (Colors.Base);
 				scenario.Init (Colors.Base);
 				scenario.Setup ();
 				scenario.Setup ();
 				scenario.Run ();
 				scenario.Run ();
+
+				scenario.Dispose();
+
 				Application.Shutdown ();
 				Application.Shutdown ();
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
 				foreach (var inst in Responder.Instances) {
 				foreach (var inst in Responder.Instances) {
@@ -136,6 +139,8 @@ namespace UICatalog {
 			// Using variable in the left side of Assert.Equal/NotEqual give error. Must be used literals values.
 			// Using variable in the left side of Assert.Equal/NotEqual give error. Must be used literals values.
 			//Assert.Equal (stackSize, iterations);
 			//Assert.Equal (stackSize, iterations);
 
 
+			generic.Dispose();
+
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
 
 

+ 7 - 7
UnitTests/UnitTests.csproj

@@ -7,12 +7,12 @@
     <IsPackable>false</IsPackable>
     <IsPackable>false</IsPackable>
     <UseDataCollector />
     <UseDataCollector />
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
-    <!-- In the source tree the version will always be 1.0 for all projects. -->
+    <!-- In the source tree the version will always be 2.0 for all projects. -->
     <!-- Do not modify these. -->
     <!-- Do not modify these. -->
-    <AssemblyVersion>1.0</AssemblyVersion>
-    <FileVersion>1.0</FileVersion>
-    <Version>1.0</Version>
-    <InformationalVersion>1.0</InformationalVersion>
+    <AssemblyVersion>2.0</AssemblyVersion>
+    <FileVersion>2.0</FileVersion>
+    <Version>2.0</Version>
+    <InformationalVersion>2.0</InformationalVersion>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
@@ -21,8 +21,8 @@
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
-    <PackageReference Include="ReportGenerator" Version="5.1.12" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
+    <PackageReference Include="ReportGenerator" Version="5.1.19" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="xunit" Version="2.4.2" />
     <PackageReference Include="xunit" Version="2.4.2" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">

+ 1 - 1
UnitTests/AllViewsTests.cs → UnitTests/Views/AllViewsTests.cs

@@ -5,7 +5,7 @@ using System.Reflection;
 using Xunit;
 using Xunit;
 using System.IO;
 using System.IO;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class AllViewsTests {
 	public class AllViewsTests {
 		[Fact]
 		[Fact]
 		public void AllViews_Tests_All_Constructors ()
 		public void AllViews_Tests_All_Constructors ()

+ 87 - 1
UnitTests/AutocompleteTests.cs → UnitTests/Views/AutocompleteTests.cs

@@ -6,9 +6,16 @@ using System.Text.RegularExpressions;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Terminal.Gui;
 using Terminal.Gui;
 using Xunit;
 using Xunit;
+using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ViewTests {
 	public class AutocompleteTests {
 	public class AutocompleteTests {
+		readonly ITestOutputHelper output;
+
+		public AutocompleteTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
 
 
 		[Fact]
 		[Fact]
 		public void Test_GenerateSuggestions_Simple ()
 		public void Test_GenerateSuggestions_Simple ()
@@ -151,5 +158,84 @@ namespace Terminal.Gui.Core {
 			Assert.Empty (tv.Autocomplete.Suggestions);
 			Assert.Empty (tv.Autocomplete.Suggestions);
 			Assert.Equal (3, tv.Autocomplete.AllSuggestions.Count);
 			Assert.Equal (3, tv.Autocomplete.AllSuggestions.Count);
 		}
 		}
+
+		[Fact, AutoInitShutdown]
+		public void CursorLeft_CursorRight_Mouse_Button_Pressed_Does_Not_Show_Popup ()
+		{
+			var tv = new TextView () {
+				Width = 50,
+				Height = 5,
+				Text = "This a long line and against TextView."
+			};
+			tv.Autocomplete.AllSuggestions = Regex.Matches (tv.Text.ToString (), "\\w+")
+					.Select (s => s.Value)
+					.Distinct ().ToList ();
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+
+			for (int i = 0; i < 7; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+				Application.Refresh ();
+				TestHelpers.AssertDriverContentsWithFrameAre (@"
+This a long line and against TextView.", output);
+			}
+
+			Assert.True (tv.MouseEvent (new MouseEvent () {
+				X = 6,
+				Y = 0,
+				Flags = MouseFlags.Button1Pressed
+			}));
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+This a long line and against TextView.", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.g, new KeyModifiers ())));
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+This ag long line and against TextView.
+       against                         ", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+This ag long line and against TextView.
+      against                          ", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+This ag long line and against TextView.
+     against                           ", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+This ag long line and against TextView.", output);
+
+			for (int i = 0; i < 3; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+				Application.Refresh ();
+				TestHelpers.AssertDriverContentsWithFrameAre (@"
+This ag long line and against TextView.", output);
+			}
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+This a long line and against TextView.", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.n, new KeyModifiers ())));
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+This an long line and against TextView.
+       and                             ", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+This an long line and against TextView.", output);
+		}
 	}
 	}
 }
 }

+ 1 - 1
UnitTests/ButtonTests.cs → UnitTests/Views/ButtonTests.cs

@@ -2,7 +2,7 @@
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ButtonTests {
 	public class ButtonTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 

+ 1 - 1
UnitTests/CheckboxTests.cs → UnitTests/Views/CheckboxTests.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class CheckboxTests {
 	public class CheckboxTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 

+ 1 - 1
UnitTests/ColorPickerTests.cs → UnitTests/Views/ColorPickerTests.cs

@@ -5,7 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ColorPickerTests {
 	public class ColorPickerTests {
 		[Fact]
 		[Fact]
 		public void Constructors ()
 		public void Constructors ()

+ 1 - 1
UnitTests/ComboBoxTests.cs → UnitTests/Views/ComboBoxTests.cs

@@ -4,7 +4,7 @@ using System.Linq;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ComboBoxTests {
 	public class ComboBoxTests {
 		ITestOutputHelper output;
 		ITestOutputHelper output;
 
 

+ 1 - 1
UnitTests/DateFieldTests.cs → UnitTests/Views/DateFieldTests.cs

@@ -5,7 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class DateFieldTests {
 	public class DateFieldTests {
 		[Fact]
 		[Fact]
 		public void Constructors_Defaults ()
 		public void Constructors_Defaults ()

+ 1 - 1
UnitTests/FrameViewTests.cs → UnitTests/Views/FrameViewTests.cs

@@ -5,7 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class FrameViewTests {
 	public class FrameViewTests {
 		[Fact]
 		[Fact]
 		public void Constuctors_Defaults ()
 		public void Constuctors_Defaults ()

+ 1 - 1
UnitTests/GraphViewTests.cs → UnitTests/Views/GraphViewTests.cs

@@ -11,7 +11,7 @@ using System.Text.RegularExpressions;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 using Rune = System.Rune;
 using Rune = System.Rune;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 
 
 	#region Helper Classes
 	#region Helper Classes
 	class FakeHAxis : HorizontalAxis {
 	class FakeHAxis : HorizontalAxis {

+ 1 - 1
UnitTests/HexViewTests.cs → UnitTests/Views/HexViewTests.cs

@@ -6,7 +6,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class HexViewTests {
 	public class HexViewTests {
 		[Fact]
 		[Fact]
 		public void Constructors_Defaults ()
 		public void Constructors_Defaults ()

+ 1 - 1
UnitTests/LineViewTests.cs → UnitTests/Views/LineViewTests.cs

@@ -1,7 +1,7 @@
 using Terminal.Gui.Graphs;
 using Terminal.Gui.Graphs;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class LineViewTests {
 	public class LineViewTests {
 
 
 		[Fact]
 		[Fact]

+ 64 - 2
UnitTests/ListViewTests.cs → UnitTests/Views/ListViewTests.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ListViewTests {
 	public class ListViewTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 
@@ -203,7 +203,7 @@ namespace Terminal.Gui.Views {
 
 
 		[Fact]
 		[Fact]
 		[AutoInitShutdown]
 		[AutoInitShutdown]
-		public void EnsuresVisibilitySelectedItem_Top ()
+		public void EnsureSelectedItemVisible_Top ()
 		{
 		{
 			var source = new List<string> () { "First", "Second" };
 			var source = new List<string> () { "First", "Second" };
 			ListView lv = new ListView (source) { Width = Dim.Fill (), Height = 1 };
 			ListView lv = new ListView (source) { Width = Dim.Fill (), Height = 1 };
@@ -451,5 +451,67 @@ namespace Terminal.Gui.Views {
 			lv.SetSourceAsync (null);
 			lv.SetSourceAsync (null);
 			Assert.NotNull (lv.Source);
 			Assert.NotNull (lv.Source);
 		}
 		}
+
+		[Fact]
+		public void ListWrapper_StartsWith ()
+		{
+			var lw = new ListWrapper (new List<string> { "One", "Two", "Three" });
+
+			Assert.Equal (1, lw.StartsWith ("t"));
+			Assert.Equal (1, lw.StartsWith ("tw"));
+			Assert.Equal (2, lw.StartsWith ("th"));
+			Assert.Equal (1, lw.StartsWith ("T"));
+			Assert.Equal (1, lw.StartsWith ("TW"));
+			Assert.Equal (2, lw.StartsWith ("TH"));
+
+			lw = new ListWrapper (new List<NStack.ustring> { "One", "Two", "Three" });
+
+			Assert.Equal (1, lw.StartsWith ("t"));
+			Assert.Equal (1, lw.StartsWith ("tw"));
+			Assert.Equal (2, lw.StartsWith ("th"));
+			Assert.Equal (1, lw.StartsWith ("T"));
+			Assert.Equal (1, lw.StartsWith ("TW"));
+			Assert.Equal (2, lw.StartsWith ("TH"));
+		}
+
+		[Fact, AutoInitShutdown]
+		public void EnsureSelectedItemVisible_SelectedItem ()
+		{
+			var source = new List<string> ();
+			for (int i = 0; i < 10; i++) {
+				source.Add ($"Item {i}");
+			}
+			var lv = new ListView (source) {
+				Width = 10,
+				Height = 5
+			};
+			Application.Top.Add (lv);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+Item 0
+Item 1
+Item 2
+Item 3
+Item 4", output);
+
+			lv.SelectedItem = 6;
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+Item 0
+Item 1
+Item 2
+Item 3
+Item 4", output);
+
+			lv.EnsureSelectedItemVisible ();
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+Item 2
+Item 3
+Item 4
+Item 5
+Item 6", output);
+		}
 	}
 	}
 }
 }

+ 1 - 1
UnitTests/PanelViewTests.cs → UnitTests/Views/PanelViewTests.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class PanelViewTests {
 	public class PanelViewTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 

+ 1 - 1
UnitTests/ProgressBarTests.cs → UnitTests/Views/ProgressBarTests.cs

@@ -5,7 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Xunit;
 using Xunit;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ProgressBarTests {
 	public class ProgressBarTests {
 		[Fact]
 		[Fact]
 		[AutoInitShutdown]
 		[AutoInitShutdown]

+ 1 - 1
UnitTests/RadioGroupTests.cs → UnitTests/Views/RadioGroupTests.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class RadioGroupTests {
 	public class RadioGroupTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 

+ 1 - 1
UnitTests/ScrollBarViewTests.cs → UnitTests/Views/ScrollBarViewTests.cs

@@ -5,7 +5,7 @@ using System.Reflection;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ScrollBarViewTests {
 	public class ScrollBarViewTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 

+ 224 - 6
UnitTests/ScrollViewTests.cs → UnitTests/Views/ScrollViewTests.cs

@@ -1,12 +1,8 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using NStack;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ScrollViewTests {
 	public class ScrollViewTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 
@@ -280,5 +276,227 @@ namespace Terminal.Gui.Views {
 ◄░░░├─┤░► 
 ◄░░░├─┤░► 
 ", output);
 ", output);
 		}
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Frame_And_Labels_Does_Not_Overspill_ScrollView ()
+		{
+			var sv = new ScrollView {
+				X = 3,
+				Y = 3,
+				Width = 10,
+				Height = 10,
+				ContentSize = new Size (50, 50)
+			};
+			for (int i = 0; i < 8; i++) {
+				sv.Add (new CustomButton ("█", $"Button {i}", 20, 3) { Y = i * 3 });
+			}
+			Application.Top.Add (sv);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+   █████████▲
+   ██████But┬
+   █████████┴
+   ┌────────░
+   │     But░
+   └────────░
+   ┌────────░
+   │     But░
+   └────────▼
+   ◄├┤░░░░░► ", output);
+
+			sv.ContentOffset = new Point (5, 5);
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+   ─────────▲
+   ─────────┬
+    Button 2│
+   ─────────┴
+   ─────────░
+    Button 3░
+   ─────────░
+   ─────────░
+    Button 4▼
+   ◄├─┤░░░░► ", output);
+		}
+
+		private class CustomButton : FrameView {
+			private Label labelFill;
+			private Label labelText;
+
+			public CustomButton (string fill, ustring text, int width, int height)
+			{
+				Width = width;
+				Height = height;
+				labelFill = new Label () { AutoSize = false, Width = Dim.Fill (), Height = Dim.Fill (), Visible = false };
+				var fillText = new System.Text.StringBuilder ();
+				for (int i = 0; i < Bounds.Height; i++) {
+					if (i > 0) {
+						fillText.AppendLine ("");
+					}
+					for (int j = 0; j < Bounds.Width; j++) {
+						fillText.Append (fill);
+					}
+				}
+				labelFill.Text = fillText.ToString ();
+				labelText = new Label (text) { X = Pos.Center (), Y = Pos.Center () };
+				Add (labelFill, labelText);
+				CanFocus = true;
+			}
+
+			public override bool OnEnter (View view)
+			{
+				Border.BorderStyle = BorderStyle.None;
+				Border.DrawMarginFrame = false;
+				labelFill.Visible = true;
+				view = this;
+				return base.OnEnter (view);
+			}
+
+			public override bool OnLeave (View view)
+			{
+				Border.BorderStyle = BorderStyle.Single;
+				Border.DrawMarginFrame = true;
+				labelFill.Visible = false;
+				if (view == null)
+					view = this;
+				return base.OnLeave (view);
+			}
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Clear_Window_Inside_ScrollView ()
+		{
+			var topLabel = new Label ("At 15,0") { X = 15 };
+			var sv = new ScrollView {
+				X = 3,
+				Y = 3,
+				Width = 10,
+				Height = 10,
+				ContentSize = new Size (23, 23),
+				KeepContentAlwaysInViewport = false
+			};
+			var bottomLabel = new Label ("At 15,15") { X = 15, Y = 15 };
+			Application.Top.Add (topLabel, sv, bottomLabel);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
+                       
+                       
+            ▲          
+            ┬          
+            ┴          
+            ░          
+            ░          
+            ░          
+            ░          
+            ░          
+            ▼          
+   ◄├┤░░░░░►           
+                       
+                       
+               At 15,15", output);
+
+			var attributes = new Attribute [] {
+				Colors.TopLevel.Normal,
+				Colors.TopLevel.Focus,
+				Colors.Base.Normal
+			};
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00011111111110000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", attributes);
+
+			sv.Add (new Window ("1") { X = 3, Y = 3, Width = 20, Height = 20 });
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
+                       
+                       
+            ▲          
+            ┬          
+            ┴          
+      ┌ 1 ──░          
+      │     ░          
+      │     ░          
+      │     ░          
+      │     ░          
+      │     ▼          
+   ◄├┤░░░░░►           
+                       
+                       
+               At 15,15", output);
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00011111111110000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", attributes);
+
+			sv.ContentOffset = new Point (20, 20);
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
+                       
+                       
+     │      ▲          
+     │      ░          
+   ──┘      ░          
+            ░          
+            ░          
+            ┬          
+            │          
+            ┴          
+            ▼          
+   ◄░░░░├─┤►           
+                       
+                       
+               At 15,15", output);
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00022200000010000000000
+00022200000010000000000
+00022200000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00011111111110000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", attributes);
+		}
 	}
 	}
 }
 }

+ 1 - 1
UnitTests/StatusBarTests.cs → UnitTests/Views/StatusBarTests.cs

@@ -2,7 +2,7 @@
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class StatusBarTests {
 	public class StatusBarTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 

+ 95 - 3
UnitTests/TabViewTests.cs → UnitTests/Views/TabViewTests.cs

@@ -8,7 +8,7 @@ using Xunit;
 using System.Globalization;
 using System.Globalization;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 
 
 	public class TabViewTests {
 	public class TabViewTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
@@ -719,7 +719,7 @@ namespace Terminal.Gui.Views {
 
 
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────────┐    
 ┌──────────────┐    
-│Les Misrables│    
+│Les Misérables│    
 ◄              └───┐
 ◄              └───┐
 │hi2               │
 │hi2               │
 └──────────────────┘", output);
 └──────────────────┘", output);
@@ -756,10 +756,102 @@ namespace Terminal.Gui.Views {
 ┌──────────────────┐
 ┌──────────────────┐
 │hi2               │
 │hi2               │
 ◄              ┌───┘
 ◄              ┌───┘
-│Les Misrables│    
+│Les Misérables│    
 └──────────────┘    ", output);
 └──────────────┘    ", output);
 		}
 		}
 
 
+		[Fact, AutoInitShutdown]
+		public void MouseClick_ChangesTab ()
+		{
+			var tv = GetTabView (out var tab1, out var tab2, false);
+
+			tv.Width = 20;
+			tv.Height = 5;
+
+			tv.LayoutSubviews ();
+
+			tv.Redraw (tv.Bounds);
+
+			var tabRow = tv.Subviews[0];
+			Assert.Equal("TabRowView",tabRow.GetType().Name);
+
+			TestHelpers.AssertDriverContentsAre (@"
+┌────┐              
+│Tab1│Tab2          
+│    └─────────────┐
+│hi                │
+└──────────────────┘
+", output);
+
+			TabView.Tab clicked = null;
+			
+
+			tv.TabClicked += (s,e)=>{
+				clicked = e.Tab;
+			};
+
+			// Waving mouse around does not trigger click
+			for(int i=0;i<100;i++)
+			{
+				tabRow.MouseEvent(new MouseEvent{
+						X = i,
+						Y = 1,
+						Flags = MouseFlags.ReportMousePosition
+				});
+
+				Assert.Null(clicked);
+				Assert.Equal(tab1, tv.SelectedTab);
+			}
+
+			tabRow.MouseEvent(new MouseEvent{
+					X = 3,
+					Y = 1,
+					Flags = MouseFlags.Button1Clicked
+			});
+
+			Assert.Equal(tab1, clicked);
+			Assert.Equal(tab1, tv.SelectedTab);
+
+
+			// Click to tab2
+			tabRow.MouseEvent(new MouseEvent{
+					X = 7,
+					Y = 1,
+					Flags = MouseFlags.Button1Clicked
+			});
+
+			Assert.Equal(tab2, clicked);
+			Assert.Equal(tab2, tv.SelectedTab);
+
+			// cancel navigation
+			tv.TabClicked += (s,e)=>{
+				clicked = e.Tab;
+				e.MouseEvent.Handled = true;
+			};
+
+			tabRow.MouseEvent(new MouseEvent{
+					X = 3,
+					Y = 1,
+					Flags = MouseFlags.Button1Clicked
+			});
+
+			// Tab 1 was clicked but event handler blocked navigation
+			Assert.Equal(tab1, clicked);
+			Assert.Equal(tab2, tv.SelectedTab);
+
+
+			tabRow.MouseEvent (new MouseEvent {
+				X = 10,
+				Y = 1,
+				Flags = MouseFlags.Button1Clicked
+			});
+
+			// Clicking beyond last tab should raise event with null Tab
+			Assert.Null (clicked);
+			Assert.Equal (tab2, tv.SelectedTab);
+
+		}
+
 		private void InitFakeDriver ()
 		private void InitFakeDriver ()
 		{
 		{
 			var driver = new FakeDriver ();
 			var driver = new FakeDriver ();

+ 1 - 1
UnitTests/TableViewTests.cs → UnitTests/Views/TableViewTests.cs

@@ -9,7 +9,7 @@ using System.Globalization;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 using System.Reflection;
 using System.Reflection;
 
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 
 
 	public class TableViewTests {
 	public class TableViewTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;

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