瀏覽代碼

Merge branch 'develop' into main

Tig 2 年之前
父節點
當前提交
094a9b57de
共有 100 個文件被更改,包括 7418 次插入3380 次删除
  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. 二進制
      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",
-	"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)

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

@@ -16,12 +16,12 @@ jobs:
         fetch-depth: 0 #fetch-depth is needed for 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:
         versionSpec: 5.x
 
     - 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
 
     - 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
 
+*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. 
 
 * 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
 
@@ -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
 
-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
 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:
 ```powershell
-git checkout -b my_new_branch
+git checkout -b <v1/v2>_my_new_branch
 ```
 
 #### Making Changes
@@ -90,6 +100,10 @@ remote:
 
 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** 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)
 ![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
 
 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>
     <TargetFramework>net6.0</TargetFramework>
     <!-- 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. -->
     <AssemblyVersion>1.0</AssemblyVersion>
     <FileVersion>1.0</FileVersion>
@@ -11,8 +11,8 @@
     <InformationalVersion>1.0</InformationalVersion>
   </PropertyGroup>
   <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" />
   </ItemGroup>
   <ItemGroup>

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

@@ -1,9 +1,6 @@
 //
 // Driver.cs: Curses-based Driver
 //
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
@@ -23,7 +20,12 @@ namespace Terminal.Gui {
 		public override int Rows => Curses.Lines;
 		public override int Left => 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; }
 
 		CursorVisibility? initialCursorVisibility = null;
@@ -62,50 +64,72 @@ namespace Terminal.Gui {
 					Curses.move (crow, ccol);
 					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 {
-					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;
+			}
+
+			if (runeWidth < 0 || runeWidth > 0) {
+				ccol++;
+			}
 
-			ccol++;
 			if (runeWidth > 1) {
 				if (validClip && ccol < Clip.Right) {
-					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 1] = CurrentAttribute;
 					contents [crow, ccol, 2] = 0;
 				}
 				ccol++;
 			}
 
-			if (sync)
+			if (sync) {
 				UpdateScreen ();
+			}
 		}
 
 		public override void AddStr (ustring str)
@@ -136,36 +160,18 @@ namespace Terminal.Gui {
 
 		public override void End ()
 		{
-			if (reportableMouseEvents.HasFlag (Curses.Event.ReportMousePosition)) {
-				StopReportingMouseMoves ();
-			}
-
+			StopReportingMouseMoves ();
 			SetCursorVisibility (CursorVisibility.Default);
 
 			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 ();
 
-		Attribute currentAttribute;
-
 		public override void SetAttribute (Attribute c)
 		{
-			currentAttribute = c;
-			Curses.attrset (currentAttribute);
+			base.SetAttribute (c);
+			Curses.attrset (CurrentAttribute);
 		}
 
 		public Curses.Window window;
@@ -201,6 +207,7 @@ namespace Terminal.Gui {
 
 		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
 		{
+			// BUGBUG: This code is never called ?? See Issue #2300
 			int f = (short)foreground;
 			int b = (short)background;
 			var v = colorPairs [f, b];
@@ -218,6 +225,7 @@ namespace Terminal.Gui {
 		Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
 		public override void SetColors (short foreColorId, short backgroundColorId)
 		{
+			// BUGBUG: This code is never called ?? See Issue #2300
 			int key = ((ushort)foreColorId << 16) | (ushort)backgroundColorId;
 			if (!rawPairs.TryGetValue (key, out var v)) {
 				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 MapKeyModifiers (Key key)
@@ -634,9 +343,18 @@ namespace Terminal.Gui {
 					ProcessWinChange ();
 				}
 				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;
 				}
 				k = MapCursesKey (wch);
@@ -672,7 +390,7 @@ namespace Terminal.Gui {
 					k = Key.AltMask | MapCursesKey (wch);
 				}
 				if (code == 0) {
-					KeyEvent key;
+					KeyEvent key = null;
 
 					// The ESC-number handling, debatable.
 					// Simulates the AltMask itself by pressing Alt + Space.
@@ -684,55 +402,13 @@ namespace Terminal.Gui {
 						k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + (wch2 + 64));
 					} else if (wch2 >= (uint)Key.D0 && wch2 <= (uint)Key.D9) {
 						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 {
 						// Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
 						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> keyDownHandler;
 		Action<KeyEvent> keyUpHandler;
@@ -813,17 +535,12 @@ namespace Terminal.Gui {
 			};
 		}
 
-		Curses.Event oldMouseEvents, reportableMouseEvents;
 		public override void Init (Action terminalResized)
 		{
 			if (window != null)
 				return;
 
 			try {
-				//Set cursor key to application.
-				//Console.Out.Write ("\x1b[?1h");
-				//Console.Out.Flush ();
-
 				window = Curses.initscr ();
 				Curses.set_escdelay (10);
 			} catch (Exception e) {
@@ -870,39 +587,21 @@ namespace Terminal.Gui {
 			Curses.noecho ();
 
 			Curses.Window.Standard.keypad (true);
-			reportableMouseEvents = Curses.mousemask (Curses.Event.AllEvents | Curses.Event.ReportMousePosition, out oldMouseEvents);
 			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) {
 				Curses.StartColor ();
 				Curses.UseDefaultColors ();
 
-				CreateColors ();
+				InitalizeColorSchemes ();
 			} 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.Focus = Curses.COLOR_WHITE;
 				Colors.TopLevel.HotNormal = Curses.COLOR_YELLOW;
@@ -929,13 +628,16 @@ namespace Terminal.Gui {
 				Colors.Error.HotFocus = Curses.A_REVERSE;
 				Colors.Error.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
 			}
+
+			ResizeScreen ();
+			UpdateOffScreen ();
+
 		}
 
 		public override void ResizeScreen ()
 		{
 			Clip = new Rect (0, 0, Cols, Rows);
-			Console.Out.Write ("\x1b[3J");
-			Console.Out.Flush ();
+			Curses.refresh ();
 		}
 
 		public override void UpdateOffScreen ()
@@ -1055,25 +757,21 @@ namespace Terminal.Gui {
 
 		public override void Suspend ()
 		{
-			if (reportableMouseEvents.HasFlag (Curses.Event.ReportMousePosition))
-				StopReportingMouseMoves ();
+			StopReportingMouseMoves ();
 			Platform.Suspend ();
 			Curses.Window.Standard.redrawwin ();
 			Curses.refresh ();
-			if (reportableMouseEvents.HasFlag (Curses.Event.ReportMousePosition))
-				StartReportingMouseMoves ();
+			StartReportingMouseMoves ();
 		}
 
 		public override void StartReportingMouseMoves ()
 		{
-			Console.Out.Write ("\x1b[?1003h");
-			Console.Out.Flush ();
+			Console.Out.Write (EscSeqUtils.EnableMouseEvents);
 		}
 
 		public override void StopReportingMouseMoves ()
 		{
-			Console.Out.Write ("\x1b[?1003l");
-			Console.Out.Flush ();
+			Console.Out.Write (EscSeqUtils.DisableMouseEvents);
 		}
 
 		//int lastMouseInterval;
@@ -1093,11 +791,6 @@ namespace Terminal.Gui {
 			//Curses.mouseinterval (lastMouseInterval);
 		}
 
-		public override Attribute GetAttribute ()
-		{
-			return currentAttribute;
-		}
-
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{
@@ -1121,7 +814,6 @@ namespace Terminal.Gui {
 
 			if (visibility != CursorVisibility.Invisible) {
 				Console.Out.Write ("\x1b[{0} q", ((int)visibility >> 24) & 0xFF);
-				Console.Out.Flush ();
 			}
 
 			currentCursorVisibility = visibility;
@@ -1186,8 +878,8 @@ namespace Terminal.Gui {
 			background = default;
 			int back = -1;
 			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)) {
 				hasColor = true;
 				back = (value >> 12) & 0xffff;
@@ -1280,6 +972,7 @@ namespace Terminal.Gui {
 
 		bool CheckSupport ()
 		{
+#pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception.
 			try {
 				var (exitCode, result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true);
 				if (exitCode == 0 && result.FileExists ()) {
@@ -1289,6 +982,7 @@ namespace Terminal.Gui {
 			} catch (Exception) {
 				// Permissions issue.
 			}
+#pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception.
 			return false;
 		}
 

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

@@ -1,30 +1,6 @@
 //
 // 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.Collections.Generic;
 using System.Runtime.InteropServices;
@@ -52,7 +28,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <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>
 		[Flags]
 		public enum Condition : short {
@@ -127,10 +103,10 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		///   Removes an active watch from the mainloop.
+		///	Removes an active watch from the mainloop.
 		/// </summary>
 		/// <remarks>
-		///   The token parameter is the value returned from AddWatch
+		///	The token parameter is the value returned from AddWatch
 		/// </remarks>
 		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 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
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -21,8 +21,6 @@ using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Threading;
 
-
-
 namespace Unix.Terminal {
 	/// <summary>
 	/// Represents a dynamically loaded unmanaged library in a (partially) platform independent manner.
@@ -45,7 +43,7 @@ namespace Unix.Terminal {
 		static bool IsNetCore;
 
 		public static bool IsMacOSPlatform => IsMacOS;
-		
+
 		[DllImport ("libc")]
 		static extern int uname (IntPtr buf);
 
@@ -105,11 +103,11 @@ namespace Unix.Terminal {
 		//
 		public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
 		{
-			if (isFullPath){
+			if (isFullPath) {
 				this.libraryPath = FirstValidLibraryPath (libraryPathAlternatives);
 				this.handle = PlatformSpecificLoadLibrary (this.libraryPath);
 			} else {
-				foreach (var lib in libraryPathAlternatives){
+				foreach (var lib in libraryPathAlternatives) {
 					this.handle = PlatformSpecificLoadLibrary (lib);
 					if (this.handle != IntPtr.Zero)
 						break;
@@ -164,13 +162,13 @@ namespace Unix.Terminal {
 		}
 
 		public T GetNativeMethodDelegate<T> (string methodName)
-		    where T : class
+			where T : class
 		{
 			var ptr = LoadSymbol (methodName);
 			if (ptr == IntPtr.Zero) {
 				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>
@@ -209,12 +207,11 @@ namespace Unix.Terminal {
 				}
 			}
 			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)));
 		}
 
-		static class Windows
-		{
+		static class Windows {
 			[DllImport ("kernel32.dll")]
 			internal static extern IntPtr LoadLibrary (string filename);
 
@@ -222,8 +219,7 @@ namespace Unix.Terminal {
 			internal static extern IntPtr GetProcAddress (IntPtr hModule, string procName);
 		}
 
-		static class Linux
-		{
+		static class Linux {
 			[DllImport ("libdl.so")]
 			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);
 		}
 
-		static class MacOSX
-		{
+		static class MacOSX {
 			[DllImport ("libSystem.dylib")]
 			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
 		/// Mono sure is linked against these symbols.
 		/// </summary>
-		static class Mono
-		{
+		static class Mono {
 			[DllImport ("__Internal")]
 			internal static extern IntPtr dlopen (string filename, int flags);
 
@@ -261,13 +255,12 @@ namespace Unix.Terminal {
 		/// dlopen and dlsym from the "libcoreclr.so",
 		/// to avoid the dependency on libc-dev Linux.
 		/// </summary>
-		static class CoreCLR
-		{
+		static class CoreCLR {
 #if NET6_0
 			// Custom resolver to support true single-file apps
 			// (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.
 			static CoreCLR() =>  NativeLibrary.SetDllImportResolver(typeof(CoreCLR).Assembly,
 				(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) {
 				lines = l;
 				cols = c;
-				//if (l <= 0 || c <= 0) {
-				//	Console.Out.Write ($"\x1b[8;50;{c}t");
-				//	Console.Out.Flush ();
-				//	return false;
-				//}
 				return true;
 			}
 			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_GRAY = unchecked((int)0x8);
 		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 TIOCGWINSZ  = unchecked((int)0x5413);
 		public const int TIOCGWINSZ_MAC  = unchecked((int)0x40087468);
@@ -69,7 +68,7 @@ namespace Unix.Terminal {
 			Button2Released = unchecked((int)0x20),
 			Button2Clicked = unchecked((int)0x80),
 			Button2DoubleClicked = unchecked((int)0x100),
-			Button2TrippleClicked = unchecked((int)0x200),
+			Button2TripleClicked = unchecked((int)0x200),
 			Button3Pressed = unchecked((int)0x800),
 			Button3Released = unchecked((int)0x400),
 			Button3Clicked = unchecked((int)0x1000),
@@ -106,6 +105,7 @@ namespace Unix.Terminal {
 		public const int KeyPPage = unchecked((int)0x153);
 		public const int KeyHome = unchecked((int)0x106);
 		public const int KeyMouse = unchecked((int)0x199);
+		public const int KeyCSI = unchecked((int)0x5b);
 		public const int KeyEnd = unchecked((int)0x168);
 		public const int KeyDeleteChar = unchecked((int)0x14a);
 		public const int KeyInsertChar = unchecked((int)0x14b);

文件差異過大導致無法顯示
+ 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. 
 //
-// Authors:
-//   Charlie Kindel (github.com/tig)
-//
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
@@ -33,7 +30,7 @@ namespace Terminal.Gui {
 				UseFakeClipboard = useFakeClipboard;
 				FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
 				FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
-				
+
 				// double check usage is correct
 				Debug.Assert (useFakeClipboard == false && fakeClipboardAlwaysThrowsNotSupportedException == 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.
 		public override int Left => 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;
 		public override IClipboard Clipboard => clipboard;
 
@@ -131,34 +133,54 @@ namespace Terminal.Gui {
 					//MockConsole.CursorTop = crow;
 					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) {
 
-					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;
+			}
+
+			if (runeWidth < 0 || runeWidth > 0) {
+				ccol++;
+			}
 
-			ccol++;
 			if (runeWidth > 1) {
 				if (validClip && ccol < Clip.Right) {
-					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 1] = CurrentAttribute;
 					contents [crow, ccol, 2] = 0;
 				}
 				ccol++;
@@ -169,8 +191,9 @@ namespace Terminal.Gui {
 			//	if (crow + 1 < Rows)
 			//		crow++;
 			//}
-			if (sync)
+			if (sync) {
 				UpdateScreen ();
+			}
 		}
 
 		public override void AddStr (ustring str)
@@ -208,11 +231,10 @@ namespace Terminal.Gui {
 			rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
 			FakeConsole.Clear ();
 			ResizeScreen ();
+			// Call InitalizeColorSchemes before UpdateOffScreen as it references Colors
+			CurrentAttribute = MakeColor (Color.White, Color.Black);
+			InitalizeColorSchemes ();
 			UpdateOffScreen ();
-
-			CreateColors ();
-
-			//MockConsole.Clear ();
 		}
 
 		public override Attribute MakeAttribute (Color fore, Color back)
@@ -225,8 +247,8 @@ namespace Terminal.Gui {
 		{
 			redrawColor = color;
 			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-			      .OfType<ConsoleColor> ()
-			      .Select (s => (int)s);
+				.OfType<ConsoleColor> ()
+				.Select (s => (int)s);
 			if (values.Contains (color & 0xffff)) {
 				FakeConsole.BackgroundColor = (ConsoleColor)(color & 0xffff);
 			}
@@ -283,10 +305,9 @@ namespace Terminal.Gui {
 			UpdateCursor ();
 		}
 
-		Attribute currentAttribute;
 		public override void SetAttribute (Attribute c)
 		{
-			currentAttribute = c;
+			base.SetAttribute (c);
 		}
 
 		public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
@@ -475,11 +496,6 @@ namespace Terminal.Gui {
 			keyUpHandler (new KeyEvent (map, keyModifiers));
 		}
 
-		public override Attribute GetAttribute ()
-		{
-			return currentAttribute;
-		}
-
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{
@@ -521,7 +537,7 @@ namespace Terminal.Gui {
 			FakeConsole.SetBufferSize (width, height);
 			cols = width;
 			rows = height;
-			if (!HeightAsBuffer) {
+			if (!EnableConsoleScrolling) {
 				SetWindowSize (width, height);
 			}
 			ProcessResize ();
@@ -530,7 +546,7 @@ namespace Terminal.Gui {
 		public void SetWindowSize (int width, int height)
 		{
 			FakeConsole.SetWindowSize (width, height);
-			if (!HeightAsBuffer) {
+			if (!EnableConsoleScrolling) {
 				if (width != cols || height != rows) {
 					SetBufferSize (width, height);
 					cols = width;
@@ -542,7 +558,7 @@ namespace Terminal.Gui {
 
 		public void SetWindowPosition (int left, int top)
 		{
-			if (HeightAsBuffer) {
+			if (EnableConsoleScrolling) {
 				this.left = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0);
 				this.top = Math.Max (Math.Min (top, Rows - FakeConsole.WindowHeight), 0);
 			} else if (this.left > 0 || this.top > 0) {
@@ -561,7 +577,7 @@ namespace Terminal.Gui {
 
 		public override void ResizeScreen ()
 		{
-			if (!HeightAsBuffer) {
+			if (!EnableConsoleScrolling) {
 				if (FakeConsole.WindowHeight > 0) {
 					// Can raise an exception while is still resizing.
 					try {
@@ -615,8 +631,8 @@ namespace Terminal.Gui {
 			foreground = default;
 			background = default;
 			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-			      .OfType<ConsoleColor> ()
-			      .Select (s => (int)s);
+				.OfType<ConsoleColor> ()
+				.Select (s => (int)s);
 			if (values.Contains (value & 0xffff)) {
 				hasColor = true;
 				background = (Color)(ConsoleColor)(value & 0xffff);

文件差異過大導致無法顯示
+ 258 - 828
Terminal.Gui/ConsoleDrivers/NetDriver.cs


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

@@ -1,30 +1,6 @@
 //
 // 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 System;
 using System.Collections.Generic;
@@ -116,6 +92,10 @@ namespace Terminal.Gui {
 
 		public bool GetCursorVisibility (out CursorVisibility visibility)
 		{
+			if (ScreenBuffer == IntPtr.Zero) {
+				visibility = CursorVisibility.Invisible;
+				return false;
+			}
 			if (!GetConsoleCursorInfo (ScreenBuffer, out ConsoleCursorInfo info)) {
 				var err = Marshal.GetLastWin32Error ();
 				if (err != 0) {
@@ -251,7 +231,7 @@ namespace Terminal.Gui {
 				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 			}
 			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 ());
 				return new Size (cols, rows);
 			}
@@ -261,7 +241,7 @@ namespace Terminal.Gui {
 
 		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 ());
 			}
 		}
@@ -283,6 +263,9 @@ namespace Terminal.Gui {
 			position = new Point (csbi.srWindow.Left, csbi.srWindow.Top);
 			SetConsoleOutputWindow (csbi);
 			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)) {
 				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)]
 		static extern IntPtr GetConsoleWindow ();
 
@@ -732,7 +715,12 @@ namespace Terminal.Gui {
 		public override int Rows => rows;
 		public override int Left => left;
 		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 int [,,] Contents => contents;
 
@@ -767,13 +755,13 @@ namespace Terminal.Gui {
 
 		private void ChangeWin (Size e)
 		{
-			if (!HeightAsBuffer) {
+			if (!EnableConsoleScrolling) {
 				var w = e.Width;
 				if (w == cols - 3 && e.Height < rows) {
 					w += 3;
 				}
 				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;
 				top = 0;
 				cols = newSize.Width;
@@ -908,8 +896,12 @@ namespace Terminal.Gui {
 				left = pos.X;
 				top = pos.Y;
 				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 ();
 				UpdateOffScreen ();
 				TerminalResized?.Invoke ();
@@ -1452,16 +1444,32 @@ namespace Terminal.Gui {
 			TerminalResized = terminalResized;
 
 			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);
 				cols = winSize.Width;
 				rows = winSize.Height;
-
 				WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
 
+				CurrentAttribute = MakeColor (Color.White, Color.Black);
+				InitalizeColorSchemes ();
+
+				CurrentAttribute = MakeColor (Color.White, Color.Black);
+				InitalizeColorSchemes ();
+
 				ResizeScreen ();
 				UpdateOffScreen ();
-
-				CreateColors ();
 			} catch (Win32Exception e) {
 				throw new InvalidOperationException ("The Windows Console output window is not available.", e);
 			}
@@ -1478,8 +1486,15 @@ namespace Terminal.Gui {
 				Right = (short)Cols
 			};
 			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 ()
@@ -1517,49 +1532,72 @@ namespace Terminal.Gui {
 			var validClip = IsValidContent (ccol, crow, Clip);
 
 			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);
-					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 (validClip && ccol < Clip.Right) {
 					position = GetOutputBufferPosition ();
-					OutputBuffer [position].Attributes = (ushort)currentAttribute;
+					OutputBuffer [position].Attributes = (ushort)CurrentAttribute;
 					OutputBuffer [position].Char.UnicodeChar = (char)0x00;
 					contents [crow, ccol, 0] = (int)(uint)0x00;
-					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 1] = CurrentAttribute;
 					contents [crow, ccol, 2] = 0;
 				}
 				ccol++;
 			}
 
-			if (sync)
+			if (sync) {
 				UpdateScreen ();
+			}
 		}
 
 		public override void AddStr (ustring str)
@@ -1568,11 +1606,9 @@ namespace Terminal.Gui {
 				AddRune (rune);
 		}
 
-		Attribute currentAttribute;
-
 		public override void SetAttribute (Attribute c)
 		{
-			currentAttribute = c;
+			base.SetAttribute (c);
 		}
 
 		public override Attribute MakeColor (Color foreground, Color background)
@@ -1625,7 +1661,7 @@ namespace Terminal.Gui {
 			if (damageRegion.Left == -1)
 				return;
 
-			if (!HeightAsBuffer) {
+			if (!EnableConsoleScrolling) {
 				var windowSize = WinConsole.GetConsoleBufferWindow (out _);
 				if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows))
 					return;
@@ -1672,11 +1708,18 @@ namespace Terminal.Gui {
 		{
 			WinConsole.Cleanup ();
 			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/>
@@ -1757,8 +1800,8 @@ namespace Terminal.Gui {
 			foreground = default;
 			background = default;
 			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)) {
 				hasColor = true;
 				background = (Color)(ConsoleColor)((value >> 4) & 0xffff);
@@ -1874,9 +1917,9 @@ namespace Terminal.Gui {
 		{
 			while (true) {
 				Thread.Sleep (100);
-				if (!consoleDriver.HeightAsBuffer) {
+				if (!consoleDriver.EnableConsoleScrolling) {
 					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
 						|| windowSize.Height != consoleDriver.Rows) {
 						return;

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

@@ -57,7 +57,7 @@ namespace Terminal.Gui {
 	///   </para>
 	/// </remarks>
 	public static class Application {
-		static Stack<Toplevel> toplevels = new Stack<Toplevel> ();
+		static readonly Stack<Toplevel> toplevels = new Stack<Toplevel> ();
 
 		/// <summary>
 		/// The current <see cref="ConsoleDriver"/> in use.
@@ -111,28 +111,54 @@ namespace Terminal.Gui {
 		/// </summary>
 		public static View WantContinuousButtonPressedView { get; private set; }
 
+		private static bool? _enableConsoleScrolling;
+
 		/// <summary>
-		/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
+		/// The current <see cref="ConsoleDriver.EnableConsoleScrolling"/> used in the terminal.
 		/// </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 {
 				if (Driver == null) {
-					throw new ArgumentNullException ("The driver must be initialized first.");
+					return _enableConsoleScrolling.HasValue && _enableConsoleScrolling.Value;
 				}
-				return Driver.HeightAsBuffer;
+				return Driver.EnableConsoleScrolling;
 			}
 			set {
+				_enableConsoleScrolling = value;
 				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;
 
 		/// <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>
 		public static Key AlternateForwardKey {
 			get => alternateForwardKey;
@@ -147,7 +173,7 @@ namespace Terminal.Gui {
 
 		static void OnAlternateForwardKeyChanged (Key oldKey)
 		{
-			foreach (var top in toplevels) {
+			foreach (var top in toplevels.ToArray ()) {
 				top.OnAlternateForwardKeyChanged (oldKey);
 			}
 		}
@@ -155,7 +181,7 @@ namespace Terminal.Gui {
 		static Key alternateBackwardKey = Key.PageUp | Key.CtrlMask;
 
 		/// <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>
 		public static Key AlternateBackwardKey {
 			get => alternateBackwardKey;
@@ -170,7 +196,7 @@ namespace Terminal.Gui {
 
 		static void OnAlternateBackwardKeyChanged (Key oldKey)
 		{
-			foreach (var top in toplevels) {
+			foreach (var top in toplevels.ToArray ()) {
 				top.OnAlternateBackwardKeyChanged (oldKey);
 			}
 		}
@@ -200,7 +226,8 @@ namespace Terminal.Gui {
 
 		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);
 			}
 		}
@@ -212,7 +239,7 @@ namespace Terminal.Gui {
 		public static MainLoop MainLoop { get; private set; }
 
 		/// <summary>
-		/// Disable or enable the mouse in this <see cref="Application"/>
+		/// Disable or enable the mouse. The mouse is enabled by default.
 		/// </summary>
 		public static bool IsMouseDisabled { get; set; }
 
@@ -266,7 +293,7 @@ namespace Terminal.Gui {
 		// users use async/await on their code
 		//
 		class MainLoopSyncContext : SynchronizationContext {
-			MainLoop mainLoop;
+			readonly MainLoop mainLoop;
 
 			public MainLoopSyncContext (MainLoop mainLoop)
 			{
@@ -305,9 +332,9 @@ namespace Terminal.Gui {
 		}
 
 		/// <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>
-		public static bool UseSystemConsole;
+		public static bool UseSystemConsole { get; set; } = false;
 
 		// For Unit testing - ignores UseSystemConsole
 		internal static bool ForceFakeConsole;
@@ -422,6 +449,7 @@ namespace Terminal.Gui {
 			MainLoop = new MainLoop (mainLoopDriver);
 
 			try {
+				Driver.EnableConsoleScrolling = EnableConsoleScrolling;
 				Driver.Init (TerminalResized);
 			} catch (InvalidOperationException ex) {
 				// 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)) {
 					Top.Dispose ();
 					Top = null;
+				} else if (Top != null && toplevel != Top && toplevels.Contains (Top)) {
+					Top.OnLeave (toplevel);
 				}
 				if (string.IsNullOrEmpty (toplevel.Id.ToString ())) {
 					var count = 1;
@@ -986,9 +1016,7 @@ namespace Terminal.Gui {
 			toplevel.PositionToplevels ();
 			toplevel.WillPresent ();
 			if (refreshDriver) {
-				if (MdiTop != null) {
-					MdiTop.OnChildLoaded (toplevel);
-				}
+				MdiTop?.OnChildLoaded (toplevel);
 				toplevel.OnLoaded ();
 				Redraw (toplevel);
 				toplevel.PositionCursor ();
@@ -1043,6 +1071,7 @@ namespace Terminal.Gui {
 					MdiTop.OnAllChildClosed ();
 				} else {
 					SetCurrentAsTop ();
+					Current.OnEnter (Current);
 				}
 				Refresh ();
 			}
@@ -1096,6 +1125,7 @@ namespace Terminal.Gui {
 			NotifyStopRunState = null;
 			_initialized = false;
 			mouseGrabView = null;
+			_enableConsoleScrolling = false;
 
 			// Reset synchronization context to allow the user to run async/await,
 			// as the main loop has been ended, the synchronization context from 
@@ -1111,12 +1141,6 @@ namespace Terminal.Gui {
 			Driver.Refresh ();
 		}
 
-		static void Refresh (View view)
-		{
-			view.Redraw (view.Bounds);
-			Driver.Refresh ();
-		}
-
 		/// <summary>
 		/// Triggers a refresh of the entire display.
 		/// </summary>

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

@@ -324,6 +324,7 @@ namespace Terminal.Gui {
 			if (IsWordChar ((char)kb.Key)) {
 				Visible = true;
 				closed = false;
+				return false;
 			}
 
 			if (kb.Key == Reopen) {
@@ -332,6 +333,9 @@ namespace Terminal.Gui {
 
 			if (closed || Suggestions.Count == 0) {
 				Visible = false;
+				if (!closed) {
+					Close ();
+				}
 				return false;
 			}
 
@@ -345,6 +349,17 @@ namespace Terminal.Gui {
 				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) {
 				return Select ();
 			}
@@ -368,6 +383,9 @@ namespace Terminal.Gui {
 		public virtual bool MouseEvent (MouseEvent me, bool fromHost = false)
 		{
 			if (fromHost) {
+				if (!Visible) {
+					return false;
+				}
 				GenerateSuggestions ();
 				if (Visible && Suggestions.Count == 0) {
 					Visible = false;
@@ -444,7 +462,8 @@ namespace Terminal.Gui {
 		/// Populates <see cref="Suggestions"/> with all strings in <see cref="AllSuggestions"/> that
 		/// match with the current cursor position/text in the <see cref="HostControl"/>
 		/// </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 (AllSuggestions.Count == 0) {
@@ -452,7 +471,7 @@ namespace Terminal.Gui {
 				return;
 			}
 
-			var currentWord = GetCurrentWord ();
+			var currentWord = GetCurrentWord (columnOffset);
 
 			if (string.IsNullOrWhiteSpace (currentWord)) {
 				ClearSuggestions ();
@@ -524,11 +543,12 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Returns the currently selected word from the <see cref="HostControl"/>.
 		/// <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>
 		/// </summary>
+		/// <param name="columnOffset">The column offset.</param>
 		/// <returns></returns>
-		protected abstract string GetCurrentWord ();
+		protected abstract string GetCurrentWord (int columnOffset = 0);
 
 		/// <summary>
 		/// <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.
 		/// </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>
 		/// <param name="line"></param>
 		/// <param name="idx"></param>
+		/// <param name="columnOffset"></param>
 		/// <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 ();
+			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;
 			}
 
 			// 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 {
 					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
 		/// match with the current cursor position/text in the <see cref="HostControl"/>.
 		/// </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 Attribute? effect3DBrush;
 		private ustring title = ustring.Empty;
+		private View child;
 
 		/// <summary>
 		/// Specifies the <see cref="Gui.BorderStyle"/> for a view.
@@ -433,7 +434,47 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Gets or sets the single child element of a <see cref="View"/>.
 		/// </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>
 		/// Gets the parent <see cref="Child"/> parent if any.
@@ -583,7 +624,7 @@ namespace Terminal.Gui {
 				Child.Clear (borderRect);
 			}
 
-			driver.SetAttribute (savedAttribute);
+			driver.SetAttribute (new Attribute (BorderBrush, Background));
 
 			// Draw margin frame
 			if (DrawMarginFrame) {
@@ -607,6 +648,7 @@ namespace Terminal.Gui {
 					driver.DrawWindowFrame (borderRect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
 				}
 			}
+			driver.SetAttribute (savedAttribute);
 		}
 
 		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
 			if (DrawMarginFrame) {
@@ -858,7 +900,7 @@ namespace Terminal.Gui {
 				}
 			}
 
-			driver.SetAttribute (savedAttribute);
+			driver.SetAttribute (new Attribute (BorderBrush, Background));
 
 			// Draw the MarginFrame
 			if (DrawMarginFrame) {
@@ -947,9 +989,9 @@ namespace Terminal.Gui {
 		{
 			var driver = Application.Driver;
 			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 ();
 				Rect scrRect;
 				if (view == Child) {
@@ -958,7 +1000,7 @@ namespace Terminal.Gui {
 					driver.DrawWindowTitle (scrRect, Title, 0, 0, 0, 0);
 				} else {
 					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);
 				}
 			}
@@ -974,9 +1016,9 @@ namespace Terminal.Gui {
 		{
 			var driver = Application.Driver;
 			if (DrawMarginFrame) {
-				driver.SetAttribute (view.GetNormalColor ());
+				driver.SetAttribute (new Attribute (BorderBrush, Background));
 				if (view.HasFocus) {
-					driver.SetAttribute (view.ColorScheme.HotNormal);
+					driver.SetAttribute (new Attribute (view.ColorScheme.HotNormal.Foreground, Background));
 				}
 				var padding = Parent.Border.GetSumThickness ();
 				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 System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Diagnostics;
 using System.Linq;
 using System.Runtime.CompilerServices;
 using System.Threading.Tasks;
-using Unix.Terminal;
 
 namespace Terminal.Gui {
 	/// <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>
+	/// <remarks>
+	/// The <see cref="Attribute.HasValidColors"/> value indicates either no-color has been set or the color is invalid.
+	/// </remarks>
 	public enum Color {
 		/// <summary>
 		/// The black color.
@@ -86,22 +85,104 @@ namespace Terminal.Gui {
 	}
 
 	/// <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>
 	/// <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>
 	public struct Attribute {
 		/// <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>
 		public int Value { get; }
+
 		/// <summary>
 		/// The foreground color.
 		/// </summary>
 		public Color Foreground { get; }
+
 		/// <summary>
 		/// The background color.
 		/// </summary>
@@ -117,8 +198,10 @@ namespace Terminal.Gui {
 			Color foreground = default;
 			Color background = default;
 
+			Initialized = false;
 			if (Application.Driver != null) {
 				Application.Driver.GetColors (value, out foreground, out background);
+				Initialized = true;
 			}
 			Value = value;
 			Foreground = foreground;
@@ -136,6 +219,7 @@ namespace Terminal.Gui {
 			Value = value;
 			Foreground = foreground;
 			Background = background;
+			Initialized = true;
 		}
 
 		/// <summary>
@@ -145,7 +229,9 @@ namespace Terminal.Gui {
 		/// <param name="background">Background</param>
 		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;
 			Background = background;
 		}
@@ -158,29 +244,42 @@ namespace Terminal.Gui {
 		public Attribute (Color color) : this (color, color) { }
 
 		/// <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>
-		/// <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>
-		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>
-		/// Implicitly convert an integer value into an <see cref="Attribute"/>
+		/// Implicitly convert an driver-specific color value into an <see cref="Attribute"/>
 		/// </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>
 		public static implicit operator Attribute (int v) => new Attribute (v);
 
 		/// <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>
-		/// <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="background">Background color to use.</param>
 		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);
 		}
 
@@ -194,170 +293,109 @@ namespace Terminal.Gui {
 				throw new InvalidOperationException ("The Application has not been initialized");
 			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>
-	/// 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>
+	/// <remarks>
+	/// See also: <see cref="Colors.ColorSchemes"/>.
+	/// </remarks>
 	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>
-		/// 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>
-		public Attribute Normal { get { return _normal; } set { _normal = SetAttribute (value); } }
+		internal string schemeBeingSet = "";
 
 		/// <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>
-		public Attribute Focus { get { return _focus; } set { _focus = SetAttribute (value); } }
+		public Attribute Normal {
+			get { return _normal; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_normal = value;
+			}
+		}
 
 		/// <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>
-		public Attribute HotNormal { get { return _hotNormal; } set { _hotNormal = SetAttribute (value); } }
+		public Attribute Focus {
+			get { return _focus; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_focus = value;
+			}
+		}
 
 		/// <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>
-		public Attribute HotFocus { get { return _hotFocus; } set { _hotFocus = SetAttribute (value); } }
+		public Attribute HotNormal {
+			get { return _hotNormal; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_hotNormal = value;
+			}
+		}
 
 		/// <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>
-		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>
@@ -421,20 +459,67 @@ namespace Terminal.Gui {
 		{
 			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>
 	/// The default <see cref="ColorScheme"/>s for the application.
 	/// </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 {
+		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 ()
+		{
+			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 
 			// by the class. 
-			ColorSchemes = typeof (Colors).GetProperties ()
+			return typeof (Colors).GetProperties ()
 				.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>
@@ -487,21 +572,21 @@ namespace Terminal.Gui {
 		/// </remarks>
 		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>
 		/// Provides the defined <see cref="ColorScheme"/>s.
 		/// </summary>
-		public static Dictionary<string, ColorScheme> ColorSchemes { get; }
+		public static Dictionary<string, ColorScheme> ColorSchemes { get; private set; }
 	}
 
 	/// <summary>
@@ -561,72 +646,7 @@ namespace Terminal.Gui {
 		/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
 		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>
 	/// 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.
@@ -663,9 +683,27 @@ namespace Terminal.Gui {
 		public abstract IClipboard Clipboard { get; }
 
 		/// <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>
+		/// <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; }
 
 		/// <summary>
@@ -785,13 +823,35 @@ namespace Terminal.Gui {
 		public abstract void UpdateScreen ();
 
 		/// <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>
+		/// <remarks>
+		/// Implementations should call <c>base.SetAttribute(c)</c>.
+		/// </remarks>
 		/// <param name="c">C.</param>
-		public abstract void SetAttribute (Attribute c);
+		public virtual void SetAttribute (Attribute c)
+		{
+			CurrentAttribute = c;
+		}
 
 		/// <summary>
-		/// Set Colors from limit sets of colors.
+		/// Set Colors from limit sets of colors. Not implemented by any driver: See Issue #2300.
 		/// </summary>
 		/// <param name="foreground">Foreground.</param>
 		/// <param name="background">Background.</param>
@@ -801,7 +861,7 @@ namespace Terminal.Gui {
 		// that independently with the R, G, B values.
 		/// <summary>
 		/// 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>
 		/// <param name="foregroundColorId">Foreground color identifier.</param>
 		/// <param name="backgroundColorId">Background color identifier.</param>
@@ -1124,12 +1184,13 @@ namespace Terminal.Gui {
 		public abstract void StopReportingMouseMoves ();
 
 		/// <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>
 		public abstract void UncookMouse ();
 
 		/// <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>
 		public abstract void CookMouse ();
 
@@ -1322,6 +1383,7 @@ namespace Terminal.Gui {
 		/// Lower right rounded corner
 		/// </summary>
 		public Rune LRRCorner = '\u256f';
+		private Attribute currentAttribute;
 
 		/// <summary>
 		/// Make the attribute for the foreground and background colors.
@@ -1335,7 +1397,7 @@ namespace Terminal.Gui {
 		/// Gets the current <see cref="Attribute"/>.
 		/// </summary>
 		/// <returns>The current attribute.</returns>
-		public abstract Attribute GetAttribute ();
+		public Attribute GetAttribute () => CurrentAttribute;
 
 		/// <summary>
 		/// 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);
 
 		/// <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>
-		/// <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;
 			}
 
+
+			// Define the default color theme only if the user has not defined one.
+
 			Colors.TopLevel.Normal = MakeColor (Color.BrightGreen, Color.Black);
 			Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan);
 			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;
 		}
 
+		/// <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> {
 			new ScanCodeMapping (1,27,0,27),	// Escape
 			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>
 		WheeledUp = unchecked((int)0x10000000),
 		/// <summary>
-		/// Vertical button wheeled up.
+		/// Vertical button wheeled down.
 		/// </summary>
 		WheeledDown = unchecked((int)0x20000000),
 		/// <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++) {
 				if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height))
 					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;
 
 				var runes = lines [line].ToRunes ();
@@ -1212,11 +1214,11 @@ namespace Terminal.Gui {
 					if (isVertical) {
 						var runesWidth = GetSumMaxCharWidth (Lines, line);
 						x = bounds.Right - runesWidth;
-						CursorPosition = bounds.Width - runesWidth + hotKeyPos;
+						CursorPosition = bounds.Width - runesWidth + (hotKeyPos > -1 ? hotKeyPos : 0);
 					} else {
 						var runesWidth = GetTextWidth (ustring.Make (runes));
 						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) {
 					if (isVertical) {
@@ -1225,16 +1227,16 @@ namespace Terminal.Gui {
 					} else {
 						x = bounds.Left;
 					}
-					CursorPosition = hotKeyPos;
+					CursorPosition = hotKeyPos > -1 ? hotKeyPos : 0;
 				} else if (textAlignment == TextAlignment.Centered) {
 					if (isVertical) {
 						var runesWidth = GetSumMaxCharWidth (Lines, line);
 						x = bounds.Left + line + ((bounds.Width - runesWidth) / 2);
-						CursorPosition = (bounds.Width - runesWidth) / 2 + hotKeyPos;
+						CursorPosition = (bounds.Width - runesWidth) / 2 + (hotKeyPos > -1 ? hotKeyPos : 0);
 					} else {
 						var runesWidth = GetTextWidth (ustring.Make (runes));
 						x = bounds.Left + (bounds.Width - runesWidth) / 2;
-						CursorPosition = (bounds.Width - runesWidth) / 2 + hotKeyPos;
+						CursorPosition = (bounds.Width - runesWidth) / 2 + (hotKeyPos > -1 ? hotKeyPos : 0);
 					}
 				} else {
 					throw new ArgumentOutOfRangeException ();
@@ -1291,7 +1293,7 @@ namespace Terminal.Gui {
 							rune = runes [idx];
 						}
 					}
-					if (idx == HotKeyPos) {
+					if (HotKeyPos > -1 && idx == HotKeyPos) {
 						if ((isVertical && textVerticalAlignment == VerticalTextAlignment.Justified) ||
 						(!isVertical && textAlignment == TextAlignment.Justified)) {
 							CursorPosition = idx - start;

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

@@ -613,8 +613,9 @@ namespace Terminal.Gui {
 			}
 			nx = Math.Max (x, 0);
 			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}");
 			bool m, s;
@@ -653,8 +654,8 @@ namespace Terminal.Gui {
 			}
 			ny = Math.Min (ny, l);
 			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}");
 
@@ -820,7 +821,6 @@ namespace Terminal.Gui {
 
 			if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) {
 				Application.UngrabMouse ();
-				Driver.UncookMouse ();
 				dragPosition = null;
 			}
 
@@ -908,6 +908,12 @@ namespace Terminal.Gui {
 		{
 			if (!IsMdiContainer) {
 				base.PositionCursor ();
+				if (Focused == null) {
+					EnsureFocus ();
+					if (Focused == null) {
+						Driver.SetCursorVisibility (CursorVisibility.Invisible);
+					}
+				}
 				return;
 			}
 
@@ -920,6 +926,9 @@ namespace Terminal.Gui {
 				}
 			}
 			base.PositionCursor ();
+			if (Focused == null) {
+				Driver.SetCursorVisibility (CursorVisibility.Invisible);
+			}
 		}
 
 		/// <summary>
@@ -959,6 +968,18 @@ namespace Terminal.Gui {
 			}
 			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>

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

@@ -446,14 +446,11 @@ namespace Terminal.Gui {
 		public virtual Rect Frame {
 			get => frame;
 			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));
 				TextFormatter.Size = GetBoundsTextFormatterSize ();
 				SetNeedsLayout ();
-				SetNeedsDisplay (frame);
+				SetNeedsDisplay (rect);
 			}
 		}
 
@@ -811,6 +808,7 @@ namespace Terminal.Gui {
 		{
 			var actX = x is Pos.PosAbsolute ? x.Anchor (0) : frame.X;
 			var actY = y is Pos.PosAbsolute ? y.Anchor (0) : frame.Y;
+			Rect oldFrame = frame;
 
 			if (AutoSize) {
 				var s = GetAutoSize ();
@@ -825,7 +823,21 @@ namespace Terminal.Gui {
 			}
 			TextFormatter.Size = GetBoundsTextFormatterSize ();
 			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)
@@ -939,6 +951,10 @@ namespace Terminal.Gui {
 				view.tabIndex = tabIndexes.IndexOf (view);
 				addingView = false;
 			}
+			if (view.Enabled && !Enabled) {
+				view.oldEnabled = true;
+				view.Enabled = false;
+			}
 			SetNeedsLayout ();
 			SetNeedsDisplay ();
 			OnAdded (view);
@@ -1291,14 +1307,16 @@ namespace Terminal.Gui {
 				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 ();
+			} 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 {
-				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>
 		public virtual ColorScheme ColorScheme {
 			get {
-				if (colorScheme == null)
+				if (colorScheme == null) {
 					return SuperView?.ColorScheme;
+				}
 				return colorScheme;
 			}
 			set {
@@ -1491,13 +1510,13 @@ namespace Terminal.Gui {
 			var clipRect = new Rect (Point.Empty, frame.Size);
 
 			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);
 			} else if (ustring.IsNullOrEmpty (TextFormatter.Text) &&
-				(GetType ().IsNestedPublic) && !IsOverridden (this, "Redraw") &&
+				(GetType ().IsNestedPublic && !IsOverridden (this, "Redraw") || GetType ().Name == "View") &&
 				(!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded)) {
 
 				Clear ();
@@ -1505,19 +1524,15 @@ namespace Terminal.Gui {
 			}
 
 			if (!ustring.IsNullOrEmpty (TextFormatter.Text)) {
-				Clear ();
+				Rect containerBounds = GetContainerBounds ();
+				Clear (ViewToScreen (GetNeedDisplay (containerBounds)));
 				SetChildNeedsDisplay ();
 				// Draw any Text
 				if (TextFormatter != null) {
 					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);
 			}
 
@@ -1534,12 +1549,7 @@ namespace Terminal.Gui {
 							// Draw the subview
 							// Use the view's bounds (view-relative; Location will always be (0,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.Redraw (rect);
 								view.OnDrawContentComplete (rect);
@@ -1558,6 +1568,38 @@ namespace Terminal.Gui {
 			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>
 		/// Event invoked when the content area of the View is to be drawn.
 		/// </summary>
@@ -2581,7 +2623,13 @@ namespace Terminal.Gui {
 			get => base.Enabled;
 			set {
 				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) {
 						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>
 		/// Pretty prints the View
 		/// </summary>
@@ -3064,6 +3121,17 @@ namespace Terminal.Gui {
 			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>
 		/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 		/// </summary>

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

@@ -35,6 +35,9 @@ namespace Terminal.Gui {
 				if (!OnTitleChanging (title, value)) {
 					var old = title;
 					title = value;
+					if (Border != null) {
+						Border.Title = title;
+					}
 					OnTitleChanged (old, title);
 				}
 				SetNeedsDisplay ();
@@ -180,10 +183,13 @@ namespace Terminal.Gui {
 				Border = new Border () {
 					BorderStyle = BorderStyle.Single,
 					Padding = new Thickness (padding),
-					BorderBrush = ColorScheme.Normal.Background
+					Title = title
 				};
 			} else {
 				Border = border;
+				if (ustring.IsNullOrEmpty (border.Title)) {
+					border.Title = title;
+				}
 			}
 			AdjustContentView (frame);
 		}
@@ -275,9 +281,6 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		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) {
 				Driver.SetAttribute (GetNormalColor ());
 				Clear ();
@@ -286,7 +289,6 @@ namespace Terminal.Gui {
 			var savedClip = contentView.ClipToBounds ();
 
 			// 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);
 			Driver.Clip = savedClip;
 
@@ -294,14 +296,7 @@ namespace Terminal.Gui {
 			ClearNeedsDisplay ();
 
 			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);
-			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/>

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

@@ -10,9 +10,10 @@
     <!-- 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. -->
     <!-- 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>
   <ItemGroup>
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
@@ -52,6 +53,7 @@
   <!-- Enable Nuget Source Link for github -->
   <ItemGroup>
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
+    <PackageReference Include="System.Management" Version="7.0.0" />
   </ItemGroup>
   <PropertyGroup>
     <TargetFrameworks>net472;netstandard2.0;net6.0</TargetFrameworks>

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

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

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

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

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

@@ -53,6 +53,14 @@ namespace Terminal.Gui {
 		/// </summary>
 		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>
 		/// The currently selected member of <see cref="Tabs"/> chosen by the user
 		/// </summary>
@@ -665,6 +673,22 @@ namespace Terminal.Gui {
 
 			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) &&
 				!me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
 				!me.Flags.HasFlag (MouseFlags.Button1TripleClicked))
@@ -689,7 +713,7 @@ namespace Terminal.Gui {
 						return true;
 					}
 
-					var hit = ScreenToTab (me.X, me.Y);
+
 					if (hit != null) {
 						host.SelectedTab = hit;
 						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>
 		/// A single tab in a <see cref="TabView"/>

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

@@ -298,6 +298,7 @@ namespace Terminal.Gui {
 					}
 					return;
 				}
+				ClearAllSelection ();
 				text = TextModel.ToRunes (newText.NewText);
 
 				if (!Secret && !historyText.IsFromHistory) {
@@ -1063,8 +1064,8 @@ namespace Terminal.Gui {
 				EnsureHasFocus ();
 				int x = PositionCursor (ev);
 				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);
 				}
@@ -1345,12 +1346,12 @@ namespace Terminal.Gui {
 		}
 
 		/// <inheritdoc/>
-		protected override string GetCurrentWord ()
+		protected override string GetCurrentWord (int columnOffset = 0)
 		{
 			var host = (TextField)HostControl;
 			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/>

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

@@ -2035,6 +2035,16 @@ namespace Terminal.Gui {
 			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)
 		void GetEncodedRegionBounds (out long start, out long end,
 			int? startRow = null, int? startCol = null, int? cRow = null, int? cCol = null)
@@ -2437,6 +2447,10 @@ namespace Terminal.Gui {
 
 			PositionCursor ();
 
+			if (clickWithSelecting) {
+				clickWithSelecting = false;
+				return;
+			}
 			if (SelectedLength > 0)
 				return;
 
@@ -2667,8 +2681,10 @@ namespace Terminal.Gui {
 				need = true;
 			} else if ((wordWrap && leftColumn > 0) || (dSize.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) {
@@ -4269,6 +4285,7 @@ namespace Terminal.Gui {
 		}
 
 		bool isButtonShift;
+		bool clickWithSelecting;
 
 		///<inheritdoc/>
 		public override bool MouseEvent (MouseEvent ev)
@@ -4362,6 +4379,7 @@ namespace Terminal.Gui {
 				columnTrack = currentColumn;
 			} else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed)) {
 				if (shiftSelecting) {
+					clickWithSelecting = true;
 					StopSelecting ();
 				}
 				ProcessMouseClick (ev, out _);
@@ -4447,16 +4465,6 @@ namespace Terminal.Gui {
 			line = r;
 		}
 
-		///<inheritdoc/>
-		public override bool OnLeave (View view)
-		{
-			if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
-				Application.UngrabMouse ();
-			}
-
-			return base.OnLeave (view);
-		}
-
 		/// <summary>
 		/// Allows clearing the <see cref="HistoryText.HistoryTextItem"/> items updating the original text.
 		/// </summary>
@@ -4475,12 +4483,12 @@ namespace Terminal.Gui {
 	public class TextViewAutocomplete : Autocomplete {
 
 		///<inheritdoc/>
-		protected override string GetCurrentWord ()
+		protected override string GetCurrentWord (int columnOffset = 0)
 		{
 			var host = (TextView)HostControl;
 			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/>

+ 6 - 10
UICatalog/Properties/launchSettings.json

@@ -3,6 +3,12 @@
     "UICatalog": {
       "commandName": "Project"
     },
+    "WSL : UICatalog": {
+      "commandName": "Executable",
+      "executablePath": "wsl",
+      "commandLineArgs": "dotnet UICatalog.dll",
+      "distributionName": ""
+    },
     "UICatalog -usc": {
       "commandName": "Project",
       "commandLineArgs": "-usc"
@@ -29,10 +35,6 @@
       "commandName": "Project",
       "commandLineArgs": "WizardAsView"
     },
-    "VkeyPacketSimulator": {
-      "commandName": "Project",
-      "commandLineArgs": "VkeyPacketSimulator"
-    },
     "CollectionNavigatorTester": {
       "commandName": "Project",
       "commandLineArgs": "\"Search Collection Nav\""
@@ -48,12 +50,6 @@
     "Windows & FrameViews": {
       "commandName": "Project",
       "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));
 
 			foreach (Color bg in colors) {
+				Attribute attr = new Attribute (bg, colors.Length - 1 - bg);
 				var vl = new Label (bg.ToString (), TextDirection.TopBottom_LeftRight) {
 					X = vx,
 					Y = 0,
 					Width = 1,
 					Height = 13,
 					VerticalTextAlignment = VerticalTextAlignment.Bottom,
-					ColorScheme = new ColorScheme () { Normal = new Attribute (bg, colors.Length - 1 - bg) }
+					ColorScheme = new ColorScheme () { Normal = attr }
 				};
 				Win.Add (vl);
 				var hl = new Label (bg.ToString ()) {
@@ -28,7 +29,7 @@ namespace UICatalog.Scenarios {
 					Width = 13,
 					Height = 1,
 					TextAlignment = TextAlignment.Right,
-					ColorScheme = new ColorScheme () { Normal = new Attribute (bg, colors.Length - 1 - bg) }
+					ColorScheme = new ColorScheme () { Normal = attr }
 				};
 				Win.Add (hl);
 				vx++;

+ 3 - 2
UICatalog/Scenarios/BordersComparisons.cs

@@ -12,9 +12,10 @@ namespace UICatalog.Scenarios {
 			var borderStyle = BorderStyle.Double;
 			var drawMarginFrame = false;
 			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 background = Colors.Base.HotNormal.Foreground;
+			var background = Color.Cyan;
 			var effect3D = true;
 
 			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),
 			};
 
+			tabView.TabClicked += TabView_TabClicked;
+
 			tabView.Style.ShowBorder = true;
 			tabView.ApplyStyleChanges ();
 
@@ -63,6 +65,34 @@ namespace UICatalog.Scenarios {
 			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 ()
 		{
 			Open ("", null, $"new {numbeOfNewTabs++}");
@@ -70,7 +100,11 @@ namespace UICatalog.Scenarios {
 
 		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) {
 				return;
@@ -158,7 +192,11 @@ namespace UICatalog.Scenarios {
 
 		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) {
 				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
+		}
+	}
+}

二進制
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.Setup ();
 				_selectedScenario.Run ();
+				_selectedScenario.Dispose ();
 				_selectedScenario = null;
 				Application.Shutdown ();
 				return;
@@ -95,6 +96,7 @@ namespace UICatalog {
 				scenario.Init (_colorScheme);
 				scenario.Setup ();
 				scenario.Run ();
+				scenario.Dispose ();
 
 				// This call to Application.Shutdown brackets the Application.Init call
 				// made by Scenario.Init() above
@@ -118,6 +120,9 @@ namespace UICatalog {
 			// Run UI Catalog UI. When it exits, if _selectedScenario is != null then
 			// a Scenario was selected. Otherwise, the user wants to exit UI Catalog.
 			Application.Init ();
+			
+			Application.EnableConsoleScrolling = _enableConsoleScrolling;
+			
 			Application.Run<UICatalogTopLevel> ();
 			Application.Shutdown ();
 
@@ -139,7 +144,7 @@ namespace UICatalog {
 
 		static bool _useSystemConsole = false;
 		static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
-		static bool _heightAsBuffer = false;
+		static bool _enableConsoleScrolling = false;
 		static bool _isFirstRunning = true;
 		static ColorScheme _colorScheme;
 
@@ -149,7 +154,7 @@ namespace UICatalog {
 		/// </summary>
 		class UICatalogTopLevel : Toplevel {
 			public MenuItem miIsMouseDisabled;
-			public MenuItem miHeightAsBuffer;
+			public MenuItem miEnableConsoleScrolling;
 
 			public FrameView LeftPane;
 			public ListView CategoryListView;
@@ -164,7 +169,7 @@ namespace UICatalog {
 
 			public UICatalogTopLevel ()
 			{
-				ColorScheme = _colorScheme;
+				ColorScheme = _colorScheme = Colors.Base;
 				MenuBar = new MenuBar (new MenuBarItem [] {
 					new MenuBarItem ("_File", new MenuItem [] {
 						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),
 					}),
 				});
-				
+
 				Capslock = new StatusItem (Key.CharMask, "Caps", null);
 				Numlock = new StatusItem (Key.CharMask, "Num", null);
 				Scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
@@ -272,14 +277,12 @@ namespace UICatalog {
 
 			void LoadedHandler ()
 			{
-				Application.HeightAsBuffer = _heightAsBuffer;
-
 				if (_colorScheme == null) {
 					ColorScheme = _colorScheme = Colors.Base;
 				}
 
 				miIsMouseDisabled.Checked = Application.IsMouseDisabled;
-				miHeightAsBuffer.Checked = Application.HeightAsBuffer;
+				miEnableConsoleScrolling.Checked = Application.EnableConsoleScrolling;
 				DriverName.Title = $"Driver: {Driver.GetType ().Name}";
 				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 []> ();
 				menuItems.Add (CreateDiagnosticFlagsMenuItems ());
 				menuItems.Add (new MenuItem [] { null });
-				menuItems.Add (CreateHeightAsBufferMenuItems ());
+				menuItems.Add (CreateEnableConsoleScrollingMenuItems ());
 				menuItems.Add (CreateDisabledEnabledMouseItems ());
 				menuItems.Add (CreateKeybindingsMenuItems ());
 				return menuItems;
@@ -354,18 +357,18 @@ namespace UICatalog {
 				return menuItems.ToArray ();
 			}
 
-			MenuItem [] CreateHeightAsBufferMenuItems ()
+			MenuItem [] CreateEnableConsoleScrollingMenuItems ()
 			{
 				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 ();
 			}

+ 5 - 1
UICatalog/UICatalog.csproj

@@ -5,7 +5,7 @@
     <LangVersion>8.0</LangVersion>
     <StartupObject>UICatalog.UICatalogApp</StartupObject>
     <!-- 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. -->
     <AssemblyVersion>1.0</AssemblyVersion>
     <FileVersion>1.0</FileVersion>
@@ -19,6 +19,10 @@
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   <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="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
   </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
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	public class ApplicationTests {
 		public ApplicationTests ()
 		{
@@ -24,7 +24,7 @@ namespace Terminal.Gui.Core {
 			Assert.Null (Application.Driver);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Current);
-			Assert.Throws<ArgumentNullException> (() => Application.HeightAsBuffer == true);
+			Assert.False (Application.EnableConsoleScrolling);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);
@@ -36,7 +36,7 @@ namespace Terminal.Gui.Core {
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.Current);
-			Assert.False (Application.HeightAsBuffer);
+			Assert.False (Application.EnableConsoleScrolling);
 			Assert.NotNull (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);
@@ -313,7 +313,7 @@ namespace Terminal.Gui.Core {
 		public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws ()
 		{
 			Init ();
-			
+
 			Application.Driver = null;
 
 			Application.Iteration = () => {
@@ -333,8 +333,8 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Run_T_NoInit_DoesNotThrow ()
 		{
-			Application.ForceFakeConsole = true; 
-			
+			Application.ForceFakeConsole = true;
+
 			Application.Iteration = () => {
 				Application.RequestStop ();
 			};
@@ -430,7 +430,7 @@ namespace Terminal.Gui.Core {
 		}
 
 		// TODO: Add tests for Run that test errorHandler
-		
+
 		#endregion
 
 		#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
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	/// <summary>
 	/// Tests MainLoop using the FakeMainLoop.
 	/// </summary>
@@ -171,9 +171,7 @@ namespace Terminal.Gui.Core {
 			var functionCalled = 0;
 			Func<bool> fn1 = () => {
 				functionCalled++;
-				if (functionCalled == 10) {
-					return false;
-				}
+				if (functionCalled == 10) 					return false;
 				return true;
 			};
 
@@ -181,9 +179,7 @@ namespace Terminal.Gui.Core {
 			var stopCount = 0;
 			Func<bool> fnStop = () => {
 				stopCount++;
-				if (stopCount == 20) {
-					ml.Stop ();
-				}
+				if (stopCount == 20) 					ml.Stop ();
 				return true;
 			};
 
@@ -212,9 +208,7 @@ namespace Terminal.Gui.Core {
 			var stopCount = 0;
 			Func<bool> fnStop = () => {
 				stopCount++;
-				if (stopCount == 10) {
-					ml.Stop ();
-				}
+				if (stopCount == 10) 					ml.Stop ();
 				return true;
 			};
 
@@ -237,9 +231,7 @@ namespace Terminal.Gui.Core {
 			var functionCalled = 0;
 			Func<bool> fn = () => {
 				functionCalled++;
-				if (functionCalled == 10) {
-					ml.Stop ();
-				}
+				if (functionCalled == 10) 					ml.Stop ();
 				return true;
 			};
 
@@ -258,7 +250,7 @@ namespace Terminal.Gui.Core {
 			var ms = 100;
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				return true;
 			};
@@ -279,7 +271,7 @@ namespace Terminal.Gui.Core {
 			var ms = 100;
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				ml.Stop ();
 				return true;
@@ -300,11 +292,9 @@ namespace Terminal.Gui.Core {
 			object token1 = null, token2 = null;
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
-				if (callbackCount == 2) {
-					ml.Stop ();
-				}
+				if (callbackCount == 2) 					ml.Stop ();
 				return true;
 			};
 
@@ -332,11 +322,9 @@ namespace Terminal.Gui.Core {
 			object token1 = null, token2 = null;
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
-				if (callbackCount == 2) {
-					ml.Stop ();
-				}
+				if (callbackCount == 2) 					ml.Stop ();
 				return true;
 			};
 
@@ -369,7 +357,7 @@ namespace Terminal.Gui.Core {
 			var watch = new System.Diagnostics.Stopwatch ();
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				watch.Stop ();
 				callbackCount++;
 				ml.Stop ();
@@ -381,7 +369,7 @@ namespace Terminal.Gui.Core {
 			ml.Run ();
 			// +/- 100ms should be good enuf
 			// 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.Equal (1, callbackCount);
@@ -395,7 +383,7 @@ namespace Terminal.Gui.Core {
 			var watch = new System.Diagnostics.Stopwatch ();
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				if (callbackCount == 2) {
 					watch.Stop ();
@@ -409,7 +397,7 @@ namespace Terminal.Gui.Core {
 			ml.Run ();
 			// +/- 100ms should be good enuf
 			// 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.Equal (2, callbackCount);
@@ -425,15 +413,13 @@ namespace Terminal.Gui.Core {
 			var stopCount = 0;
 			Func<bool> fnStop = () => {
 				stopCount++;
-				if (stopCount == 10) {
-					ml.Stop ();
-				}
+				if (stopCount == 10) 					ml.Stop ();
 				return true;
 			};
 			ml.AddIdle (fnStop);
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				return true;
 			};
@@ -455,15 +441,13 @@ namespace Terminal.Gui.Core {
 			Func<bool> fnStop = () => {
 				Thread.Sleep (10); // Sleep to enable timer to fire
 				stopCount++;
-				if (stopCount == 10) {
-					ml.Stop ();
-				}
+				if (stopCount == 10) 					ml.Stop ();
 				return true;
 			};
 			ml.AddIdle (fnStop);
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				return false;
 			};
@@ -541,10 +525,8 @@ namespace Terminal.Gui.Core {
 				Application.MainLoop.Invoke (() => {
 					tf.Text = $"index{r.Next ()}";
 					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 ();
-					}
 				});
 			});
 		}
@@ -554,9 +536,7 @@ namespace Terminal.Gui.Core {
 			for (int j = 0; j < numPasses; j++) {
 
 				_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
@@ -594,7 +574,7 @@ namespace Terminal.Gui.Core {
 
 			await task; // Propagate exception if any occurred
 
-			Assert.Equal ((numIncrements * numPasses), tbCounter);
+			Assert.Equal (numIncrements * numPasses, tbCounter);
 		}
 
 		private static int total;
@@ -652,9 +632,7 @@ namespace Terminal.Gui.Core {
 					Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null)));
 					Assert.Equal (cancel, btn.Text);
 					Assert.Equal (one, total);
-				} else if (taskCompleted) {
-					Application.RequestStop ();
-				}
+				} else if (taskCompleted) 					Application.RequestStop ();
 			};
 
 			Application.Run ();

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

@@ -3,12 +3,13 @@ using System.Diagnostics;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Terminal.Gui;
 using Xunit;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	/// <summary>
 	/// These tests focus on Application.RunState and the various ways it can be changed.
 	/// </summary>
@@ -26,7 +27,7 @@ namespace Terminal.Gui.Core {
 		{
 			var rs = new Application.RunState (null);
 			Assert.Null (rs.Toplevel);
-			
+
 			var top = new Toplevel ();
 			rs = new Application.RunState (top);
 			Assert.Equal (top, rs.Toplevel);
@@ -72,9 +73,7 @@ namespace Terminal.Gui.Core {
 			Application.Shutdown ();
 #if DEBUG_IDISPOSABLE
 			// 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
 		}
 

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

@@ -2,7 +2,7 @@
 using System.Collections.Generic;
 using Xunit;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	public class StackExtensionsTests {
 		[Fact]
 		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
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	public class SyncrhonizationContextTests {
 
 		[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.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Reflection.Emit;
 using Xunit;
+using Xunit.Abstractions;
 using Rune = System.Rune;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.CoreTests {
 	public class BorderTests {
-		[Fact]
-		[AutoInitShutdown]
+		readonly ITestOutputHelper output;
+
+		public BorderTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		[Fact, AutoInitShutdown]
 		public void Constructor_Defaults ()
 		{
 			var b = new Border ();
@@ -45,8 +49,7 @@ namespace Terminal.Gui.Core {
 			Assert.False (b.DrawMarginFrame);
 		}
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void ActualWidth_ActualHeight ()
 		{
 			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 ());
 		}
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void DrawContent_With_Child_Border ()
 		{
 			var top = Application.Top;
@@ -226,7 +228,12 @@ namespace Terminal.Gui.Core {
 
 					var color = (Attribute)driver.Contents [r, c, 1];
 					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) {
 						Assert.Equal (uLCorner, rune);
 					} 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 ()
 		{
 			var top = Application.Top;
@@ -456,7 +462,12 @@ namespace Terminal.Gui.Core {
 
 					var color = (Attribute)driver.Contents [r, c, 1];
 					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) {
 						Assert.Equal (uLCorner, rune);
 					} else if (c == frame.Right - drawMarginFrame - sumThickness.Right
@@ -540,8 +551,7 @@ namespace Terminal.Gui.Core {
 			}
 		}
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void BorderOnControlWithNoChildren ()
 		{
 			var label = new TextField ("Loading...") {
@@ -557,5 +567,57 @@ namespace Terminal.Gui.Core {
 
 			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 static Terminal.Gui.Core.ViewTests;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.CoreTests {
 	public class ResponderTests {
 		[Fact]
 		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
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.ConsoleDrivers {
+namespace Terminal.Gui.DriverTests {
 	public class AttributeTests {
 		[Fact]
 		public void Constuctors_Constuct ()
@@ -85,7 +85,34 @@ namespace Terminal.Gui.ConsoleDrivers {
 		}
 
 		[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 ();
 			fg = Color.Red;
@@ -93,7 +120,9 @@ namespace Terminal.Gui.ConsoleDrivers {
 			var bg = new Color ();
 			bg = Color.Blue;
 
-			Assert.Throws<InvalidOperationException> (() => Attribute.Make (fg, bg));
+			var a = Attribute.Make (fg, bg);
+
+			Assert.False (a.Initialized);
 		}
 
 		[Fact]
@@ -109,8 +138,8 @@ namespace Terminal.Gui.ConsoleDrivers {
 			var bg = new Color ();
 			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 (bg, attr.Background);
 
@@ -119,7 +148,23 @@ namespace Terminal.Gui.ConsoleDrivers {
 		}
 
 		[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 ());
 		}
@@ -163,5 +208,24 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Assert.Equal (Color.Red, fg);
 			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.Diagnostics;
 using System.Runtime.InteropServices;
+using Terminal.Gui;
 using Xunit;
 using Xunit.Abstractions;
 using static AutoInitShutdownAttribute;
 
-namespace Terminal.Gui.ConsoleDrivers {
+namespace Terminal.Gui.DriverTests {
 	public class ClipboardTests {
 		readonly ITestOutputHelper output;
 
@@ -101,11 +102,8 @@ namespace Terminal.Gui.ConsoleDrivers {
 		[Fact, AutoInitShutdown (useFakeClipboard: false)]
 		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)]
@@ -131,21 +129,15 @@ namespace Terminal.Gui.ConsoleDrivers {
 		public void TrySetClipboardData_Sets_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.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 ();
 
-			if (!failed) {
-				Assert.Equal (clipText, getClipText);
-			}
+			if (!failed) 				Assert.Equal (clipText, getClipText);
 		}
 
 		[Fact, AutoInitShutdown (useFakeClipboard: false)]
@@ -275,9 +265,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 
 			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 {
 				var (_, result) = ClipboardProcessRunner.Process ("bash", $"-c \"which xclip\"");
 				return result.TrimEnd () != "";
-			} catch (System.Exception) {
+			} catch (Exception) {
 				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 Terminal.Gui;
 using Xunit;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.DriverTests {
 	public class KeyTests {
 		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 Xunit;
 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 {
 		readonly ITestOutputHelper output;
 

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

@@ -3,9 +3,9 @@ using System.Collections.Generic;
 using System.Linq;
 using Xunit;
 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 {
 		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.
 	/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
 	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)
 	{
 		//Assert.True (autoInit == false && consoleDriverType == null);
@@ -54,7 +54,7 @@ public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
 
 	static bool _init = false;
 	bool AutoInit { get; }
-	bool AutoShutdown { get;  }
+	bool AutoShutdown { get; }
 	Type DriverType;
 
 	public override void Before (MethodInfo methodUnderTest)
@@ -251,7 +251,7 @@ class TestHelpers {
 
 				var match = expectedColors.Where (e => e.Value == val).ToList ();
 				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) {
 					throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value");
 				}
@@ -260,7 +260,7 @@ class TestHelpers {
 				var userExpected = line [c];
 
 				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 Xunit;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TextTests {
 	public class CollectionNavigatorTests {
 		static string [] simpleStrings = new string []{
 		    "appricot", // 0

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

@@ -2,14 +2,14 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using Terminal.Gui.Views;
+using Terminal.Gui;
 using Xunit;
 using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TextTests {
 	public class TextFormatterTests {
 		readonly ITestOutputHelper output;
 
@@ -394,7 +394,7 @@ namespace Terminal.Gui.Core {
 			bool supportFirstUpperCase = true;
 
 			var text = ustring.Empty;
-			Rune hotKeySpecifier = (Rune)0;
+			var hotKeySpecifier = (Rune)0;
 			int hotPos = 0;
 			Key hotKey = Key.Unknown;
 			bool result = false;
@@ -437,7 +437,7 @@ namespace Terminal.Gui.Core {
 			bool supportFirstUpperCase = true;
 
 			var text = ustring.Empty;
-			Rune hotKeySpecifier = (Rune)0;
+			var hotKeySpecifier = (Rune)0;
 			int hotPos = 0;
 			Key hotKey = Key.Unknown;
 			bool result = false;
@@ -2162,9 +2162,7 @@ namespace Terminal.Gui.Core {
 			var height = 8;
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			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 frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 
@@ -2202,9 +2200,7 @@ namespace Terminal.Gui.Core {
 			var height = 3;
 			var wrappedLines = TextFormatter.WordWrap (text, height, true);
 			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) {
 				TextDirection = TextDirection.TopBottom_LeftRight,
 				Width = Dim.Fill (),
@@ -2241,9 +2237,7 @@ namespace Terminal.Gui.Core {
 			var height = 8;
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			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 frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 
@@ -2282,9 +2276,7 @@ namespace Terminal.Gui.Core {
 			var height = 4;
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			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) {
 				TextDirection = TextDirection.TopBottom_LeftRight,
 				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));
 
 			var result = tf.ReplaceHotKeyWithTag (text, hotPos);
-			Assert.Equal ('e', (uint)(result.ToRunes () [1]));
+			Assert.Equal ('e', result.ToRunes () [1]);
 
 			text = "Ok";
 			tag = 'O';
 			hotPos = 0;
 			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 = ustring.Make (new Rune [] { '[', '◦', ' ', 'O', 'k', ' ', '◦', ']' });
@@ -2449,13 +2441,13 @@ namespace Terminal.Gui.Core {
 			tag = 'O';
 			hotPos = 3;
 			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";
 			tag = '^';
 			hotPos = 0;
 			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]
@@ -2814,37 +2806,37 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		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, ustring.Make (c).ConsoleWidth);
 			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, ustring.Make (c).ConsoleWidth);
 			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, ustring.Make (c).ConsoleWidth);
 			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, ustring.Make (c).ConsoleWidth);
 			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, ustring.Make (c).ConsoleWidth);
 			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 (0, ustring.Make (c).ConsoleWidth);// ConsoleWidth only returns zero or greater than zero
 			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 (0, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
@@ -2896,9 +2888,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("nd", list1 [10].ToString ());
 			Assert.Equal ("Line", list1 [11].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);
 
 			// With preserveTrailingSpaces = true.
@@ -2920,9 +2910,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("Line", list2 [13].ToString ());
 			Assert.Equal (" - ", list2 [14].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);
 		}
 
@@ -3430,7 +3418,7 @@ This TextFormatter (tf2) is rewritten.
 		[Fact]
 		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 (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
 			text = new List<ustring> () { "こんにちは", "世界" };
@@ -4259,5 +4247,46 @@ This TextFormatter (tf2) is rewritten.
 0111000000
 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 NStack;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.TopLevelTests {
 
 	public class DialogTests {
 		readonly ITestOutputHelper output;
@@ -29,7 +29,7 @@ namespace Terminal.Gui.Views {
 		[AutoInitShutdown]
 		public void ButtonAlignment_One ()
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 			Application.RunState runstate = null;
 
 			var title = "1234";
@@ -37,8 +37,8 @@ namespace Terminal.Gui.Views {
 			var btnText = "ok";
 			var buttonRow = $"{d.VLine}   {d.LeftBracket} {btnText} {d.RightBracket}   {d.VLine}";
 			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);
 
@@ -74,7 +74,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			// E.g "|[ yes ][ no ]|"
@@ -85,8 +85,8 @@ namespace Terminal.Gui.Views {
 
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}";
 			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);
 
@@ -123,7 +123,7 @@ namespace Terminal.Gui.Views {
 			Application.RunState runstate = null;
 			bool firstIteration = false;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			// E.g "|[ yes ][ no ]|"
@@ -134,8 +134,8 @@ namespace Terminal.Gui.Views {
 
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}";
 			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);
 
@@ -191,7 +191,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			// E.g "|[ yes ][ no ][ maybe ]|"
@@ -204,8 +204,8 @@ namespace Terminal.Gui.Views {
 
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {btn3} {d.VLine}";
 			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);
 
@@ -241,7 +241,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 
@@ -257,8 +257,8 @@ namespace Terminal.Gui.Views {
 
 			var buttonRow = $"{d.VLine} {btn1} {btn2} {btn3} {btn4} {d.VLine}";
 			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);
 
 			// Default - Center
@@ -294,7 +294,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 
@@ -349,7 +349,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 
@@ -368,8 +368,8 @@ namespace Terminal.Gui.Views {
 			//                         12345                           123456
 			var buttonRow = $"{d.VLine}     {btn1} {btn2} {btn3} {btn4}      {d.VLine}";
 			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);
 
 			// Default - Center
@@ -405,7 +405,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 
@@ -423,8 +423,8 @@ namespace Terminal.Gui.Views {
 			//                         12345                          123456
 			var buttonRow = $"{d.VLine}     {btn1} {btn2} {btn3} {btn4}      {d.VLine}";
 			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);
 
 			// Default - Center
@@ -460,14 +460,14 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 
 			var buttonRow = $"{d.VLine}        {d.VLine}";
 			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);
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null);
@@ -482,15 +482,15 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			var btnText = "ok";
 			var buttonRow = $"{d.VLine}   {d.LeftBracket} {btnText} {d.RightBracket}   {d.VLine}";
 
 			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);
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText));
@@ -504,7 +504,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			var btn1Text = "yes";
@@ -516,8 +516,8 @@ namespace Terminal.Gui.Views {
 			var width = $@"{d.VLine} {btn1} {btn2} {d.VLine}".Length;
 			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)
 			var dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Center };
@@ -550,7 +550,7 @@ namespace Terminal.Gui.Views {
 			// Right
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Right };
 			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);
 
 			// Now add a second button
@@ -564,7 +564,7 @@ namespace Terminal.Gui.Views {
 			// Left
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Left };
 			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);
 
 			// 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.Threading;
 using System.Threading.Tasks;
+using Terminal.Gui;
 using Xunit;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TopLevelTests {
 	public class MdiTests {
 		public MdiTests ()
 		{
@@ -94,17 +95,11 @@ namespace Terminal.Gui.Core {
 
 			Application.Iteration += () => {
 				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);
 				iterations--;
 			};
@@ -171,9 +166,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (d.Running);
 				} else {
 					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--;
 			};
@@ -231,9 +224,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (d.Running);
 				} else {
 					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--;
 			};
@@ -292,9 +283,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (d.Running);
 				} else {
 					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--;
 			};
@@ -392,9 +381,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (Application.Current.Running);
 				} else {
 					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--;
 			};
@@ -461,10 +448,8 @@ namespace Terminal.Gui.Core {
 					Assert.True (c4.Running);
 				} else {
 					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);
-					}
 				}
 				iterations--;
 			};
@@ -586,9 +571,7 @@ namespace Terminal.Gui.Core {
 					};
 
 					stage.Closed += (_) => {
-						if (iterations == 11) {
-							allStageClosed = true;
-						}
+						if (iterations == 11) 							allStageClosed = true;
 						Assert.Equal (iterations, Application.MdiChildes.Count);
 						if (running) {
 							stageCompleted = true;
@@ -610,16 +593,12 @@ namespace Terminal.Gui.Core {
 					running = false;
 					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);
 					mdiRequestStop = true;
 					mdi.RequestStop ();
-				} else {
-					Assert.Empty (Application.MdiChildes);
-				}
+				} else 					Assert.Empty (Application.MdiChildes);
 			};
 
 			Application.Run (mdi);

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

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

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

@@ -1,8 +1,9 @@
 using System;
+using Terminal.Gui;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TopLevelTests {
 	public class ToplevelTests {
 		readonly ITestOutputHelper output;
 
@@ -695,8 +696,7 @@ namespace Terminal.Gui.Core {
 					((FakeDriver)Application.Driver).SetBufferSize (40, 15);
 					MessageBox.Query ("About", "Hello Word", "Ok");
 
-				} else if (iterations == 1) {
-					TestHelpers.AssertDriverContentsWithFrameAre (@"
+				} else if (iterations == 1) TestHelpers.AssertDriverContentsWithFrameAre (@"
  File                                   
 ┌ Window ──────────────────────────────┐
 │                                      │
@@ -712,8 +712,7 @@ namespace Terminal.Gui.Core {
 │                                      │
 └──────────────────────────────────────┘
  CTRL-N New                             ", output);
-
-				} else if (iterations == 2) {
+				else if (iterations == 2) {
 					Assert.Null (Application.MouseGrabView);
 					// Grab the mouse
 					ReflectionTools.InvokePrivate (
@@ -816,11 +815,8 @@ namespace Terminal.Gui.Core {
 
 					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 ();
@@ -960,12 +956,80 @@ namespace Terminal.Gui.Core {
 
 					Assert.Null (Application.MouseGrabView);
 
-				} else if (iterations == 8) {
-					Application.RequestStop ();
-				}
+				} else if (iterations == 8) Application.RequestStop ();
 			};
 
 			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 Xunit;
 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
 using Console = Terminal.Gui.FakeConsole;
 using NStack;
+using Terminal.Gui;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TopLevelTests {
 	public class WindowTests {
 		readonly ITestOutputHelper output;
 
@@ -22,7 +23,7 @@ namespace Terminal.Gui.Core {
 			// Parameterless
 			var r = new Window ();
 			Assert.NotNull (r);
-			Assert.Equal(ustring.Empty, r.Title);
+			Assert.Equal (ustring.Empty, r.Title);
 			Assert.Equal (LayoutStyle.Computed, r.LayoutStyle);
 			Assert.Equal ("Window()({X=0,Y=0,Width=0,Height=0})", r.ToString ());
 			Assert.True (r.CanFocus);
@@ -116,13 +117,13 @@ namespace Terminal.Gui.Core {
 			r.Title = expectedDuring = expectedAfter = "title";
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 
-			expectedOld = r.Title.ToString();
+			expectedOld = r.Title.ToString ();
 			r.Title = expectedDuring = expectedAfter = "a different title";
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 
 			// Now setup cancelling the change and change it back to "title"
 			cancel = true;
-			expectedOld = r.Title.ToString();
+			expectedOld = r.Title.ToString ();
 			r.Title = expectedDuring = "title";
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 			r.Dispose ();
@@ -154,7 +155,7 @@ namespace Terminal.Gui.Core {
 			r.Dispose ();
 		}
 
-		[Fact,AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void MenuBar_And_StatusBar_Inside_Window ()
 		{
 			var menu = new MenuBar (new MenuBarItem [] {
@@ -175,7 +176,7 @@ namespace Terminal.Gui.Core {
 
 			var fv = new FrameView ("Frame View") {
 				Y = 1,
-				Width = Dim.Fill(),
+				Width = Dim.Fill (),
 				Height = Dim.Fill (1)
 			};
 			var win = new Window ();

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

@@ -9,7 +9,7 @@ using System.Globalization;
 using Xunit.Abstractions;
 using NStack;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.TopLevelTests {
 
 	public class WizardTests {
 		readonly ITestOutputHelper output;
@@ -92,7 +92,7 @@ namespace Terminal.Gui.Views {
 		[Fact, AutoInitShutdown]
 		public void DefaultConstructor_SizedProperly ()
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var wizard = new Wizard ();
 			Assert.NotEqual (0, wizard.Width);
@@ -104,7 +104,7 @@ namespace Terminal.Gui.Views {
 		// and that the title is correct
 		public void ZeroStepWizard_Shows ()
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			var stepTitle = "";
@@ -118,12 +118,12 @@ namespace Terminal.Gui.Views {
 			var btnNextText = "Finish";
 			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 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 };
 			Application.End (Application.Begin (wizard));
@@ -135,7 +135,7 @@ namespace Terminal.Gui.Views {
 		// and that the title is correct
 		public void OneStepWizard_Shows ()
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			var stepTitle = "ABCD";
@@ -149,13 +149,13 @@ namespace Terminal.Gui.Views {
 			var btnNextText = "Finish"; // "Next";
 			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 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 };
 			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")
 		public void Setting_Title_Works ()
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			var stepTitle = " - ABCD";
@@ -219,13 +219,13 @@ namespace Terminal.Gui.Views {
 			var btnNextText = "Finish";
 			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
-			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 };
 			wizard.AddStep (new Wizard.WizardStep ("ABCD"));
@@ -560,13 +560,13 @@ namespace Terminal.Gui.Views {
 			runstate = Application.Begin (wizard);
 			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 ();
 			Assert.False (finishedFired);
 			Assert.False (closedFired);
 
 			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 ();
 			Application.End (runstate);
 			Assert.True (finishedFired);

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

@@ -6,14 +6,13 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using Terminal.Gui;
-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.Core {
+namespace Terminal.Gui.TypeTests {
 	public class DimTests {
 		readonly ITestOutputHelper output;
 
@@ -22,7 +21,7 @@ namespace Terminal.Gui.Core {
 			this.output = output;
 			Console.OutputEncoding = System.Text.Encoding.Default;
 			// Change current culture
-			CultureInfo culture = CultureInfo.CreateSpecificCulture ("en-US");
+			var culture = CultureInfo.CreateSpecificCulture ("en-US");
 			Thread.CurrentThread.CurrentCulture = culture;
 			Thread.CurrentThread.CurrentUICulture = culture;
 		}
@@ -646,9 +645,7 @@ namespace Terminal.Gui.Core {
 			};
 
 			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 ();
 			};
@@ -1091,9 +1088,7 @@ namespace Terminal.Gui.Core {
 			};
 
 			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 ();
 			};

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

@@ -1,7 +1,7 @@
 using System;
 using Xunit;
 
-namespace Terminal.Gui.Types {
+namespace Terminal.Gui.TypeTests {
 	public class PointTests {
 		[Fact]
 		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.Linq;
 using Terminal.Gui;
-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.Core {
+namespace Terminal.Gui.TypeTests {
 	public class PosTests {
 		readonly ITestOutputHelper output;
 

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

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

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

@@ -1,7 +1,7 @@
 using System;
 using Xunit;
 
-namespace Terminal.Gui.Types {
+namespace Terminal.Gui.TypeTests {
 	public class SizeTests {
 		[Fact]
 		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
 using Console = Terminal.Gui.FakeConsole;
 
-namespace UICatalog {
+namespace UICatalog.Tests {
 	public class ScenarioTests {
 		readonly ITestOutputHelper output;
 
@@ -69,6 +69,9 @@ namespace UICatalog {
 				scenario.Init (Colors.Base);
 				scenario.Setup ();
 				scenario.Run ();
+
+				scenario.Dispose();
+
 				Application.Shutdown ();
 #if DEBUG_IDISPOSABLE
 				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.
 			//Assert.Equal (stackSize, iterations);
 
+			generic.Dispose();
+
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 

+ 7 - 7
UnitTests/UnitTests.csproj

@@ -7,12 +7,12 @@
     <IsPackable>false</IsPackable>
     <UseDataCollector />
     <!-- 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. -->
-    <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 Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
     <DefineConstants>TRACE</DefineConstants>
@@ -21,8 +21,8 @@
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   <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="xunit" Version="2.4.2" />
     <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 System.IO;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class AllViewsTests {
 		[Fact]
 		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 Terminal.Gui;
 using Xunit;
+using Xunit.Abstractions;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ViewTests {
 	public class AutocompleteTests {
+		readonly ITestOutputHelper output;
+
+		public AutocompleteTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
 
 		[Fact]
 		public void Test_GenerateSuggestions_Simple ()
@@ -151,5 +158,84 @@ namespace Terminal.Gui.Core {
 			Assert.Empty (tv.Autocomplete.Suggestions);
 			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.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ButtonTests {
 		readonly ITestOutputHelper output;
 

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

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

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

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

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

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

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

@@ -5,7 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class DateFieldTests {
 		[Fact]
 		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 Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class FrameViewTests {
 		[Fact]
 		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 Rune = System.Rune;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 
 	#region Helper Classes
 	class FakeHAxis : HorizontalAxis {

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

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

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

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

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

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ListViewTests {
 		readonly ITestOutputHelper output;
 
@@ -203,7 +203,7 @@ namespace Terminal.Gui.Views {
 
 		[Fact]
 		[AutoInitShutdown]
-		public void EnsuresVisibilitySelectedItem_Top ()
+		public void EnsureSelectedItemVisible_Top ()
 		{
 			var source = new List<string> () { "First", "Second" };
 			ListView lv = new ListView (source) { Width = Dim.Fill (), Height = 1 };
@@ -451,5 +451,67 @@ namespace Terminal.Gui.Views {
 			lv.SetSourceAsync (null);
 			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.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class PanelViewTests {
 		readonly ITestOutputHelper output;
 

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

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

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

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

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

@@ -5,7 +5,7 @@ using System.Reflection;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ScrollBarViewTests {
 		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.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ScrollViewTests {
 		readonly ITestOutputHelper output;
 
@@ -280,5 +276,227 @@ namespace Terminal.Gui.Views {
 ◄░░░├─┤░► 
 ", 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.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class StatusBarTests {
 		readonly ITestOutputHelper output;
 

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

@@ -8,7 +8,7 @@ using Xunit;
 using System.Globalization;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 
 	public class TabViewTests {
 		readonly ITestOutputHelper output;
@@ -719,7 +719,7 @@ namespace Terminal.Gui.Views {
 
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────────┐    
-│Les Misrables│    
+│Les Misérables│    
 ◄              └───┐
 │hi2               │
 └──────────────────┘", output);
@@ -756,10 +756,102 @@ namespace Terminal.Gui.Views {
 ┌──────────────────┐
 │hi2               │
 ◄              ┌───┘
-│Les Misrables│    
+│Les Misérables│    
 └──────────────┘    ", 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 ()
 		{
 			var driver = new FakeDriver ();

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

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

部分文件因文件數量過多而無法顯示