瀏覽代碼

Merge branch 'tznind-splitcontainer' into v2

Tig Kindel 2 年之前
父節點
當前提交
32c9c80a10
共有 44 個文件被更改,包括 6851 次插入589 次删除
  1. 24 20
      .devcontainer/devcontainer.json
  2. 5 5
      Example/Example.csproj
  3. 0 4
      README.md
  4. 4 4
      ReactiveExample/ReactiveExample.csproj
  5. 68 61
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  6. 45 31
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  7. 42 26
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  8. 56 36
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  9. 24 22
      Terminal.Gui/Core/Application.cs
  10. 45 22
      Terminal.Gui/Core/Autocomplete/Autocomplete.cs
  11. 2 1
      Terminal.Gui/Core/Autocomplete/IAutocomplete.cs
  12. 14 7
      Terminal.Gui/Core/Border.cs
  13. 315 69
      Terminal.Gui/Core/ConsoleDriver.cs
  14. 626 0
      Terminal.Gui/Core/Graphs/LineCanvas.cs
  15. 12 1
      Terminal.Gui/Core/Toplevel.cs
  16. 50 21
      Terminal.Gui/Core/View.cs
  17. 5 5
      Terminal.Gui/Terminal.Gui.csproj
  18. 55 9
      Terminal.Gui/Views/FrameView.cs
  19. 1 1
      Terminal.Gui/Views/LineView.cs
  20. 1 1
      Terminal.Gui/Views/TabView.cs
  21. 5 5
      Terminal.Gui/Views/TextField.cs
  22. 23 15
      Terminal.Gui/Views/TextView.cs
  23. 1131 0
      Terminal.Gui/Views/TileView.cs
  24. 4 0
      UICatalog/Properties/launchSettings.json
  25. 201 0
      UICatalog/Scenarios/LineDrawing.cs
  26. 208 60
      UICatalog/Scenarios/Notepad.cs
  27. 130 0
      UICatalog/Scenarios/TileViewExperiment.cs
  28. 243 0
      UICatalog/Scenarios/TileViewNesting.cs
  29. 23 29
      UICatalog/UICatalog.cs
  30. 4 4
      UICatalog/UICatalog.csproj
  31. 0 1
      UnitTests/Application/ApplicationTests.cs
  32. 326 0
      UnitTests/Core/LineCanvasTests.cs
  33. 69 5
      UnitTests/Drivers/AttributeTests.cs
  34. 9 0
      UnitTests/Drivers/ColorTests.cs
  35. 118 111
      UnitTests/Drivers/ConsoleDriverTests.cs
  36. 1 0
      UnitTests/Drivers/KeyTests.cs
  37. 5 2
      UnitTests/TestHelpers.cs
  38. 42 5
      UnitTests/TopLevels/ToplevelTests.cs
  39. 86 0
      UnitTests/Views/AutocompleteTests.cs
  40. 2 2
      UnitTests/Views/TabViewTests.cs
  41. 118 1
      UnitTests/Views/TextFieldTests.cs
  42. 2239 0
      UnitTests/Views/TileViewTests.cs
  43. 316 3
      UnitTests/Views/ViewTests.cs
  44. 154 0
      docfx/v2specs/View.md

+ 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)

+ 5 - 5
Example/Example.csproj

@@ -3,12 +3,12 @@
     <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 2.0 for all projects. -->
+    <!-- In the source tree the version will always be 1.0 for all projects. -->
     <!-- Do not modify these. -->
-    <AssemblyVersion>2.0</AssemblyVersion>
-    <FileVersion>2.0</FileVersion>
-    <Version>2.0</Version>
-    <InformationalVersion>2.0</InformationalVersion>
+    <AssemblyVersion>1.0</AssemblyVersion>
+    <FileVersion>1.0</FileVersion>
+    <Version>1.0</Version>
+    <InformationalVersion>1.0</InformationalVersion>
   </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />

+ 0 - 4
README.md

@@ -6,10 +6,6 @@
 [![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)
 
-# This is the v2.0 Branch - Under Development
-
-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.

+ 4 - 4
ReactiveExample/ReactiveExample.csproj

@@ -5,10 +5,10 @@
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- In the source tree the version will always be 2.0 for all projects. -->
     <!-- Do not modify these. -->
-    <AssemblyVersion>2.0</AssemblyVersion>
-    <FileVersion>2.0</FileVersion>
-    <Version>2.0</Version>
-    <InformationalVersion>2.0</InformationalVersion>
+    <AssemblyVersion>1.0</AssemblyVersion>
+    <FileVersion>1.0</FileVersion>
+    <Version>1.0</Version>
+    <InformationalVersion>1.0</InformationalVersion>
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="ReactiveUI.Fody" Version="18.4.1" />

+ 68 - 61
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -62,50 +62,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;
+			}
 
-			ccol++;
+			if (runeWidth < 0 || runeWidth > 0) {
+				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)
@@ -160,12 +182,10 @@ namespace Terminal.Gui {
 
 		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 +221,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 +239,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);
@@ -875,34 +897,18 @@ namespace Terminal.Gui {
 			if (reportableMouseEvents.HasFlag (Curses.Event.ReportMousePosition))
 				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,6 +935,10 @@ namespace Terminal.Gui {
 				Colors.Error.HotFocus = Curses.A_REVERSE;
 				Colors.Error.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
 			}
+
+			ResizeScreen ();
+			UpdateOffScreen ();
+
 		}
 
 		public override void ResizeScreen ()
@@ -1003,6 +1013,8 @@ namespace Terminal.Gui {
 				return Curses.COLOR_YELLOW | Curses.A_BOLD | Curses.COLOR_GRAY;
 			case Color.White:
 				return Curses.COLOR_WHITE | Curses.A_BOLD | Curses.COLOR_GRAY;
+			case Color.Invalid:
+				return Curses.COLOR_BLACK; 
 			}
 			throw new ArgumentException ("Invalid color code");
 		}
@@ -1093,11 +1105,6 @@ namespace Terminal.Gui {
 			//Curses.mouseinterval (lastMouseInterval);
 		}
 
-		public override Attribute GetAttribute ()
-		{
-			return currentAttribute;
-		}
-
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{

+ 45 - 31
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -33,7 +33,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);
@@ -131,34 +131,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 +189,9 @@ namespace Terminal.Gui {
 			//	if (crow + 1 < Rows)
 			//		crow++;
 			//}
-			if (sync)
+			if (sync) {
 				UpdateScreen ();
+			}
 		}
 
 		public override void AddStr (ustring str)
@@ -208,11 +229,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)
@@ -283,10 +303,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 +494,6 @@ namespace Terminal.Gui {
 			keyUpHandler (new KeyEvent (map, keyModifiers));
 		}
 
-		public override Attribute GetAttribute ()
-		{
-			return currentAttribute;
-		}
-
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{

+ 42 - 26
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1241,33 +1241,52 @@ 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];
+					contents [crow, ccol - 1, 0] = c;
+					contents [crow, ccol - 1, 1] = CurrentAttribute;
+					contents [crow, ccol - 1, 2] = 1;
 
-					contents [crow, ccol - 1, 0] = (int)(uint)' ';
+				} 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) {
+						contents [crow, ccol - 1, 0] = (int)(uint)' ';
 
-					contents [crow, ccol + 1, 0] = (int)(uint)' ';
-					contents [crow, ccol + 1, 2] = 1;
+					} else if (runeWidth < 2 && ccol <= Clip.Right - 1
+						&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 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;
+						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;
 
+				}
 				dirtyLine [crow] = true;
 			}
 
-			ccol++;
+			if (runeWidth < 0 || runeWidth > 0) {
+				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++;
@@ -1340,12 +1359,14 @@ namespace Terminal.Gui {
 			cols = Console.WindowWidth;
 			rows = Console.WindowHeight;
 
+			CurrentAttribute = MakeColor (Color.White, Color.Black);
+			InitalizeColorSchemes ();
+
 			ResizeScreen ();
 			UpdateOffScreen ();
 
 			StartReportingMouseMoves ();
 
-			CreateColors ();
 
 			Clear ();
 		}
@@ -1485,7 +1506,7 @@ namespace Terminal.Gui {
 						outputWidth++;
 						var rune = contents [row, col, 0];
 						char [] spair;
-						if (Rune.DecodeSurrogatePair((uint) rune, out spair)) {
+						if (Rune.DecodeSurrogatePair ((uint)rune, out spair)) {
 							output.Append (spair);
 						} else {
 							output.Append ((char)rune);
@@ -1613,10 +1634,10 @@ namespace Terminal.Gui {
 		{
 		}
 
-		Attribute currentAttribute;
+
 		public override void SetAttribute (Attribute c)
 		{
-			currentAttribute = c;
+			base.SetAttribute (c);
 		}
 
 		public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
@@ -1934,11 +1955,6 @@ namespace Terminal.Gui {
 			};
 		}
 
-		public override Attribute GetAttribute ()
-		{
-			return currentAttribute;
-		}
-
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{

+ 56 - 36
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -116,6 +116,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) {
@@ -1455,13 +1459,13 @@ namespace Terminal.Gui {
 				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 ();
+				
 				ResizeScreen ();
 				UpdateOffScreen ();
-
-				CreateColors ();
 			} catch (Win32Exception e) {
 				throw new InvalidOperationException ("The Windows Console output window is not available.", e);
 			}
@@ -1517,49 +1521,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 +1595,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)
@@ -1674,11 +1699,6 @@ namespace Terminal.Gui {
 			WinConsole = null;
 		}
 
-		public override Attribute GetAttribute ()
-		{
-			return currentAttribute;
-		}
-
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{

+ 24 - 22
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,33 @@ namespace Terminal.Gui {
 		/// </summary>
 		public static View WantContinuousButtonPressedView { get; private set; }
 
+		private static bool? _heightAsBuffer;
+
 		/// <summary>
 		/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
 		/// </summary>
+		/// 
 		public static bool HeightAsBuffer {
 			get {
 				if (Driver == null) {
-					throw new ArgumentNullException ("The driver must be initialized first.");
+					return _heightAsBuffer.HasValue && _heightAsBuffer.Value;
 				}
 				return Driver.HeightAsBuffer;
 			}
 			set {
+				_heightAsBuffer = value;
 				if (Driver == null) {
-					throw new ArgumentNullException ("The driver must be initialized first.");
+					return;
 				}
-				Driver.HeightAsBuffer = value;
+
+				Driver.HeightAsBuffer = _heightAsBuffer.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 +152,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 +160,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 +175,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 +205,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 +218,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 +272,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 +311,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 +428,7 @@ namespace Terminal.Gui {
 			MainLoop = new MainLoop (mainLoopDriver);
 
 			try {
+				Driver.HeightAsBuffer = HeightAsBuffer;
 				Driver.Init (TerminalResized);
 			} catch (InvalidOperationException ex) {
 				// This is a case where the driver is unable to initialize the console.
@@ -933,6 +940,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 +995,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 +1050,7 @@ namespace Terminal.Gui {
 					MdiTop.OnAllChildClosed ();
 				} else {
 					SetCurrentAsTop ();
+					Current.OnEnter (Current);
 				}
 				Refresh ();
 			}
@@ -1111,12 +1119,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);
 	}
 }

+ 14 - 7
Terminal.Gui/Core/Border.cs

@@ -1,5 +1,6 @@
 using NStack;
 using System;
+using Terminal.Gui.Graphs;
 
 namespace Terminal.Gui {
 	/// <summary>
@@ -707,14 +708,20 @@ namespace Terminal.Gui {
 
 			// Draw the MarginFrame
 			if (DrawMarginFrame) {
-				var rect = new Rect () {
-					X = frame.X - drawMarginFrame,
-					Y = frame.Y - drawMarginFrame,
-					Width = frame.Width + (2 * drawMarginFrame),
-					Height = frame.Height + (2 * drawMarginFrame)
-				};
+				var rect = Child.ViewToScreen (new Rect (-1, -1, Child.Frame.Width + 2, Child.Frame.Height + 2));
 				if (rect.Width > 0 && rect.Height > 0) {
-					driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
+
+					var lc = new LineCanvas ();
+
+					lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, BorderStyle);
+					lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, BorderStyle);
+
+					lc.AddLine (new Point (rect.X, rect.Y + rect.Height - 1), rect.Width, Orientation.Horizontal, BorderStyle);
+					lc.AddLine (new Point (rect.X + rect.Width, rect.Y), rect.Height, Orientation.Vertical, BorderStyle);
+
+					driver.SetAttribute (new Attribute(Color.Red, Color.BrightYellow));
+					
+					lc.Draw (null, rect);
 					DrawTitle (Child);
 				}
 			}

+ 315 - 69
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="Color.Invalid"/> value indicates either no-color has been set or the color is invalid.
+	/// </remarks>
 	public enum Color {
 		/// <summary>
 		/// The black color.
@@ -82,26 +81,112 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// The White color.
 		/// </summary>
-		White
+		White,
+		/// <summary>
+		/// Indicates an invalid or un-set color value. 
+		/// </summary>
+		Invalid = -1
 	}
 
 	/// <summary>
-	/// Attributes are used as elements that contain both a foreground and a background or platform specific features
+	/// 
+	/// </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>
+		/// 
+		/// </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 (typcially 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>
@@ -114,11 +199,13 @@ namespace Terminal.Gui {
 		/// <param name="value">Value.</param>
 		public Attribute (int value)
 		{
-			Color foreground = default;
-			Color background = default;
+			Color foreground = Color.Invalid;
+			Color background = Color.Invalid;
 
+			Initialized = false;
 			if (Application.Driver != null) {
 				Application.Driver.GetColors (value, out foreground, out background);
+				Initialized = true;
 			}
 			Value = value;
 			Foreground = foreground;
@@ -136,6 +223,7 @@ namespace Terminal.Gui {
 			Value = value;
 			Foreground = foreground;
 			Background = background;
+			Initialized = true;
 		}
 
 		/// <summary>
@@ -145,7 +233,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 +248,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
+				var a = new Attribute (-1, foreground, background);
+				a.Initialized = false;
+				return a;
+			}
 			return Application.Driver.MakeAttribute (foreground, background);
 		}
 
@@ -194,45 +297,114 @@ 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 initialzed 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 (see <see cref="Color.Invalid"/>) 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 Atrribute is valid (both foreground and background have valid color values).
+		/// </summary>
+		/// <returns></returns>
+		public bool HasValidColors {
+			get {
+				return Foreground != Color.Invalid && Background != Color.Invalid;
+			}
+		}
 	}
 
 	/// <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 = 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 = 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 = 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 = value; } }
+		public Attribute HotNormal {
+			get { return _hotNormal; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_hotNormal = value;
+			}
+		}
+
+		/// <summary>
+		/// The foreground and background color for text when the view is highlighted (hot) and has focus.
+		/// </summary>
+		public Attribute HotFocus {
+			get { return _hotFocus; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_hotFocus = value;
+			}
+		}
 
 		/// <summary>
-		/// The default color for text, when the view is disabled.
+		/// The default foreground and background color for text, when the view is disabled.
 		/// </summary>
-		public Attribute Disabled { get { return _disabled; } set { _disabled = value; } }
+		public Attribute Disabled {
+			get { return _disabled; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_disabled = value;
+			}
+		}
 
 		/// <summary>
 		/// Compares two <see cref="ColorScheme"/> objects for equality.
@@ -295,20 +467,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 x.ToLowerInvariant () == y.ToLowerInvariant ();
+				}
+				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>
@@ -361,21 +580,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>
@@ -659,13 +878,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>
@@ -675,7 +916,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>
@@ -998,12 +1239,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 ();
 
@@ -1201,6 +1443,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.
@@ -1214,7 +1457,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"/>.
@@ -1225,21 +1468,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 correclty 
+		/// initalized 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 initlaized 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);

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

@@ -0,0 +1,626 @@
+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="bounds"/> and generate a 'bitmap' 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="bounds"></param>
+		/// <returns>Map as 2D array where first index is rows and second is column</returns>
+		public Rune? [,] GenerateImage (Rect inArea)
+		{
+			Rune? [,] canvas = new Rune? [inArea.Height, inArea.Width];
+
+			// walk through each pixel of the bitmap
+			for (int y = 0; y < inArea.Height; y++) {
+				for (int x = 0; x < inArea.Width; x++) {
+
+					var intersects = lines
+						.Select (l => l.Intersects (inArea.X + x, inArea.Y + y))
+						.Where (i => i != null)
+						.ToArray ();
+
+					// TODO: use Driver and LineStyle to map
+					canvas [y, x] = GetRuneForIntersects (Application.Driver, intersects);
+
+				}
+			}
+
+			return canvas;
+		}
+
+		/// <summary>
+		/// Draws all the lines that lie within the <paramref name="bounds"/> onto
+		/// the <paramref name="view"/> client area.  This method should be called from
+		/// <see cref="View.Redraw(Rect)"/>.
+		/// </summary>
+		/// <param name="view"></param>
+		/// <param name="bounds"></param>
+		public void Draw (View view, Rect bounds)
+		{
+			var runes = GenerateImage (bounds);
+
+			for (int y = bounds.Y; y < bounds.Y + bounds.Height; y++) {
+				for (int x = bounds.X; x < bounds.X + bounds.Width; x++) {
+					var rune = runes [y - bounds.Y, x - bounds.X];
+					if (rune.HasValue) {
+						if (view != null) {
+							view.AddRune (x - bounds.X, y - bounds.Y, rune.Value);
+						} else {
+							Application.Driver.Move (x, y);
+							Application.Driver.AddRune (rune.Value);
+						}
+					}
+				}
+			}
+		}
+
+		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;
+			}
+		}
+	}
+}

+ 12 - 1
Terminal.Gui/Core/Toplevel.cs

@@ -821,7 +821,6 @@ namespace Terminal.Gui {
 
 			if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) {
 				Application.UngrabMouse ();
-				Driver.UncookMouse ();
 				dragPosition = null;
 			}
 
@@ -960,6 +959,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>

+ 50 - 21
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)
@@ -1134,7 +1146,7 @@ namespace Terminal.Gui {
 		/// <param name="rcol">Absolute column; screen-relative.</param>
 		/// <param name="rrow">Absolute row; screen-relative.</param>
 		/// <param name="clipped">Whether to clip the result of the ViewToScreen method, if set to <see langword="true"/>, the rcol, rrow values are clamped to the screen (terminal) dimensions (0..TerminalDim-1).</param>
-		internal void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
+		public void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
 		{
 			// Computes the real row, col relative to the screen.
 			rrow = row + frame.Y;
@@ -1435,8 +1447,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		public virtual ColorScheme ColorScheme {
 			get {
-				if (colorScheme == null)
+				if (colorScheme == null) {
 					return SuperView?.ColorScheme;
+				}
 				return colorScheme;
 			}
 			set {
@@ -1501,8 +1514,10 @@ namespace Terminal.Gui {
 				Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
 			}
 
-			if (Border != null) {
+			var boundsAdjustedForBorder = Bounds;
+			if (!IgnoreBorderPropertyOnRedraw && Border != null) {
 				Border.DrawContent (this);
+				boundsAdjustedForBorder = new Rect (bounds.X + 1, bounds.Y + 1, bounds.Width - 2, bounds.Height - 2);
 			} else if (ustring.IsNullOrEmpty (TextFormatter.Text) &&
 				(GetType ().IsNestedPublic) && !IsOverridden (this, "Redraw") &&
 				(!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded)) {
@@ -1519,30 +1534,25 @@ namespace Terminal.Gui {
 					TextFormatter.NeedsFormat = true;
 				}
 				Rect containerBounds = GetContainerBounds ();
-				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
+				TextFormatter?.Draw (ViewToScreen (boundsAdjustedForBorder), HasFocus ? ColorScheme.Focus : GetNormalColor (),
 				    HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
 				    containerBounds);
 			}
 
 			// Invoke DrawContentEvent
-			OnDrawContent (bounds);
+			OnDrawContent (boundsAdjustedForBorder);
 
 			if (subviews != null) {
 				foreach (var view in subviews) {
 					if (!view.NeedDisplay.IsEmpty || view.ChildNeedsDisplay || view.LayoutNeeded) {
-						if (view.Frame.IntersectsWith (clipRect) && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
+						if (view.Frame.IntersectsWith (clipRect) && (view.Frame.IntersectsWith (boundsAdjustedForBorder) || boundsAdjustedForBorder.X < 0 || bounds.Y < 0)) {
 							if (view.LayoutNeeded)
 								view.LayoutSubviews ();
 
 							// 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);
@@ -1555,7 +1565,7 @@ namespace Terminal.Gui {
 			}
 
 			// Invoke DrawContentCompleteEvent
-			OnDrawContentComplete (bounds);
+			OnDrawContentComplete (boundsAdjustedForBorder);
 
 			ClearLayoutNeeded ();
 			ClearNeedsDisplay ();
@@ -1567,8 +1577,18 @@ namespace Terminal.Gui {
 			var driverClip = Driver == null ? Rect.Empty : Driver.Clip;
 			containerBounds.X = Math.Max (containerBounds.X, driverClip.X);
 			containerBounds.Y = Math.Max (containerBounds.Y, driverClip.Y);
-			containerBounds.Width = Math.Min (containerBounds.Width, driverClip.Width);
-			containerBounds.Height = Math.Min (containerBounds.Height, driverClip.Height);
+			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;
 		}
 
@@ -2655,6 +2675,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; } = false;
+
 		/// <summary>
 		/// Pretty prints the View
 		/// </summary>

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

@@ -8,12 +8,12 @@
   </PropertyGroup>
   <PropertyGroup>
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
-    <!-- In the source tree the version will always be 2.0 for all projects. -->
+    <!-- In the source tree the version will always be 1.0 for all projects. -->
     <!-- Do not modify these. Do NOT commit after manually running `dotnet-gitversion /updateprojectfiles` -->
-    <AssemblyVersion>2.0</AssemblyVersion>
-    <FileVersion>2.0</FileVersion>
-    <Version>2.0</Version>
-    <InformationalVersion>2.0</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" />

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

@@ -12,6 +12,7 @@
 using System;
 using System.Linq;
 using NStack;
+using Terminal.Gui.Graphs;
 
 namespace Terminal.Gui {
 	/// <summary>
@@ -85,9 +86,10 @@ namespace Terminal.Gui {
 		/// <param name="title">Title.</param>
 		/// <param name="views">Views.</param>
 		/// <param name="border">The <see cref="Border"/>.</param>
-		public FrameView (Rect frame, ustring title = null, View [] views = null, Border border = null) : base (frame)
+		public FrameView (Rect frame, ustring title = null, View [] views = null, Border border = null) //: base (frame)
 		{
 			//var cFrame = new Rect (1, 1, Math.Max (frame.Width - 2, 0), Math.Max (frame.Height - 2, 0));
+
 			Initialize (frame, title, views, border);
 		}
 
@@ -229,14 +231,58 @@ namespace Terminal.Gui {
 
 			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 ());
+			if (!IgnoreBorderPropertyOnRedraw) {
+				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 ());
+			} else {
+				var lc = new LineCanvas ();
+
+				if (Border?.BorderStyle != BorderStyle.None) {
+
+					lc.AddLine (new Point (0, 0), bounds.Width - 1, Orientation.Horizontal, Border.BorderStyle);
+					lc.AddLine (new Point (0, 0), bounds.Height - 1, Orientation.Vertical, Border.BorderStyle);
+
+					lc.AddLine (new Point (bounds.Width - 1, bounds.Height - 1), -bounds.Width + 1, Orientation.Horizontal, Border.BorderStyle);
+					lc.AddLine (new Point (bounds.Width - 1, bounds.Height - 1), -bounds.Height + 1, Orientation.Vertical, Border.BorderStyle);
+				}
+
+				//foreach (var subview in contentView.Subviews) {
+				//	lc.AddLine (new Point (subview.Frame.X + 1, subview.Frame.Y + 1), subview.Frame.Width - 1, Orientation.Horizontal, subview.Border.BorderStyle);
+				//	lc.AddLine (new Point (subview.Frame.X + 1, subview.Frame.Y + 1), subview.Frame.Height - 1, Orientation.Vertical, subview.Border.BorderStyle);
+
+				//	lc.AddLine (new Point (subview.Frame.X + subview.Frame.Width, subview.Frame.Y + subview.Frame.Height), -subview.Frame.Width + 1, Orientation.Horizontal, subview.Border.BorderStyle);
+				//	lc.AddLine (new Point (subview.Frame.X + subview.Frame.Width, subview.Frame.Y + subview.Frame.Height), -subview.Frame.Height + 1, Orientation.Vertical, subview.Border.BorderStyle);
+
+				//}
+
+				Driver.SetAttribute (ColorScheme.Normal);
+				lc.Draw (this, bounds);
+
+
+				// Redraw the lines so that focus/drag symbol renders
+				foreach (var subview in contentView.Subviews) {
+					//	line.DrawSplitterSymbol ();
+				}
+
+
+				// Draw Titles over Border
+				foreach (var subview in contentView.Subviews) {
+					// TODO: Use reflection to see if subview has a Title property
+					if (subview is FrameView viewWithTite) {
+						var rect = viewWithTite.Frame;
+						rect.X = rect.X + 1;
+						rect.Y = rect.Y + 2;
+						// TODO: Do focus color correctly
+						Driver.DrawWindowTitle (rect, viewWithTite.Title, padding.Left, padding.Top, padding.Right, padding.Bottom);
+					}
+				}
+			}
 		}
 
 		/// <summary>

+ 1 - 1
Terminal.Gui/Views/LineView.cs

@@ -71,7 +71,7 @@ namespace Terminal.Gui {
 		public override void Redraw (Rect bounds)
 		{
 			base.Redraw (bounds);
-
+			
 			Move (0, 0);
 			Driver.SetAttribute (GetNormalColor ());
 

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

@@ -195,7 +195,7 @@ namespace Terminal.Gui {
 				int startAtY = Math.Max (0, GetTabHeight (true) - 1);
 
 				DrawFrame (new Rect (0, startAtY, bounds.Width,
-			       Math.Max (bounds.Height - spaceAtBottom - startAtY, 0)), 0, true);
+				Math.Max (bounds.Height - spaceAtBottom - startAtY, 0)), 0, true);
 			}
 
 			if (Tabs.Any ()) {

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

@@ -1064,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);
 				}
@@ -1346,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/>

+ 1131 - 0
Terminal.Gui/Views/TileView.cs

@@ -0,0 +1,1131 @@
+using NStack;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Terminal.Gui.Graphs;
+
+namespace Terminal.Gui {
+
+	/// <summary>
+	/// A <see cref="View"/> consisting of a moveable bar that divides
+	/// the display area into resizeable <see cref="Tiles"/>.
+	/// </summary>
+	public class TileView : View {
+		TileView parentTileView;
+
+		/// <summary>
+		/// A single <see cref="ContentView"/> presented in a <see cref="TileView"/>. To create
+		/// new instances use <see cref="TileView.RebuildForTileCount(int)"/> 
+		/// or <see cref="TileView.InsertTile(int)"/>.
+		/// </summary>
+		public class Tile {
+			/// <summary>
+			/// The <see cref="ContentView"/> that is contained in this <see cref="TileView"/>.
+			/// Add new child views to this member for multiple 
+			/// <see cref="ContentView"/>s within the <see cref="Tile"/>.
+			/// </summary>
+			public View ContentView { get; internal set; }
+
+			/// <summary>
+			/// Gets or Sets the minimum size you to allow when splitter resizing along
+			/// parent <see cref="TileView.Orientation"/> direction.
+			/// </summary>
+			public int MinSize { get; set; }
+
+			/// <summary>
+			/// The text that should be displayed above the <see cref="ContentView"/>. This 
+			/// will appear over the splitter line or border (above the view client area).
+			/// </summary>
+			/// <remarks>
+			/// Title are not rendered for root level tiles 
+			/// <see cref="Border.BorderStyle"/> is <see cref="BorderStyle.None"/>.
+			///</remarks>
+			public string Title {
+				get => _title;
+				set {
+					if (!OnTitleChanging (_title, value)) {
+						var old = _title;
+						_title = value;
+						OnTitleChanged (old, _title);
+						return;
+					}
+					_title = value;
+				}
+			}
+
+			private string _title = string.Empty;
+
+			/// <summary>
+			/// An <see cref="EventArgs"/> which allows passing a cancelable new <see cref="Title"/> value event.
+			/// </summary>
+			public class TitleEventArgs : EventArgs {
+				/// <summary>
+				/// The new Window Title.
+				/// </summary>
+				public ustring NewTitle { get; set; }
+
+				/// <summary>
+				/// The old Window Title.
+				/// </summary>
+				public ustring OldTitle { get; set; }
+
+				/// <summary>
+				/// Flag which allows cancelling the Title change.
+				/// </summary>
+				public bool Cancel { get; set; }
+
+				/// <summary>
+				/// Initializes a new instance of <see cref="TitleEventArgs"/>
+				/// </summary>
+				/// <param name="oldTitle">The <see cref="Title"/> that is/has been replaced.</param>
+				/// <param name="newTitle">The new <see cref="Title"/> to be replaced.</param>
+				public TitleEventArgs (ustring oldTitle, ustring newTitle)
+				{
+					OldTitle = oldTitle;
+					NewTitle = newTitle;
+				}
+			}
+
+			/// <summary>
+			/// Called before the <see cref="Title"/> changes. Invokes the <see cref="TitleChanging"/> event, which can be cancelled.
+			/// </summary>
+			/// <param name="oldTitle">The <see cref="Title"/> that is/has been replaced.</param>
+			/// <param name="newTitle">The new <see cref="Title"/> to be replaced.</param>
+			/// <returns><c>true</c> if an event handler cancelled the Title change.</returns>
+			public virtual bool OnTitleChanging (ustring oldTitle, ustring newTitle)
+			{
+				var args = new TitleEventArgs (oldTitle, newTitle);
+				TitleChanging?.Invoke (args);
+				return args.Cancel;
+			}
+
+			/// <summary>
+			/// Event fired when the <see cref="Title"/> is changing. Set <see cref="TitleEventArgs.Cancel"/> to 
+			/// <c>true</c> to cancel the Title change.
+			/// </summary>
+			public event Action<TitleEventArgs> TitleChanging;
+
+			/// <summary>
+			/// Called when the <see cref="Title"/> has been changed. Invokes the <see cref="TitleChanged"/> event.
+			/// </summary>
+			/// <param name="oldTitle">The <see cref="Title"/> that is/has been replaced.</param>
+			/// <param name="newTitle">The new <see cref="Title"/> to be replaced.</param>
+			public virtual void OnTitleChanged (ustring oldTitle, ustring newTitle)
+			{
+				var args = new TitleEventArgs (oldTitle, newTitle);
+				TitleChanged?.Invoke (args);
+			}
+
+			/// <summary>
+			/// Event fired after the <see cref="Title"/> has been changed. 
+			/// </summary>
+			public event Action<TitleEventArgs> TitleChanged;
+
+			/// <summary>
+			/// Creates a new instance of the <see cref="Tile"/> class.
+			/// </summary>
+			public Tile ()
+			{
+				ContentView = new View () { Width = Dim.Fill (), Height = Dim.Fill () };
+#if DEBUG_IDISPOSABLE
+				ContentView.Data = "Tile.ContentView";
+#endif
+				Title = string.Empty;
+				MinSize = 0;
+			}
+		}
+
+		List<Tile> tiles;
+		private List<Pos> splitterDistances;
+		private List<TileViewLineView> splitterLines;
+
+		/// <summary>
+		/// The sub sections hosted by the view
+		/// </summary>
+		public IReadOnlyCollection<Tile> Tiles => tiles.AsReadOnly ();
+
+		/// <summary>
+		/// The splitter locations. Note that there will be N-1 splitters where
+		/// N is the number of <see cref="Tiles"/>.
+		/// </summary>
+		public IReadOnlyCollection<Pos> SplitterDistances => splitterDistances.AsReadOnly ();
+
+		private Orientation orientation = Orientation.Vertical;
+
+		/// <summary>
+		/// Creates a new instance of the <see cref="TileView"/> class with 
+		/// 2 tiles (i.e. left and right).
+		/// </summary>
+		public TileView () : this (2)
+		{
+		}
+
+		/// <summary>
+		/// Creates a new instance of the <see cref="TileView"/> class with 
+		/// <paramref name="tiles"/> number of tiles.
+		/// </summary>
+		/// <param name="tiles"></param>
+		public TileView (int tiles)
+		{
+			CanFocus = true;
+			RebuildForTileCount (tiles);
+			IgnoreBorderPropertyOnRedraw = true;
+			Border = new Border () {
+				BorderStyle = BorderStyle.None
+			};
+		}
+
+		/// <summary>
+		/// Invoked when any of the <see cref="SplitterDistances"/> is changed.
+		/// </summary>
+		public event SplitterEventHandler SplitterMoved;
+
+		/// <summary>
+		/// Raises the <see cref="SplitterMoved"/> event
+		/// </summary>
+		protected virtual void OnSplitterMoved (int idx)
+		{
+			SplitterMoved?.Invoke (this, new SplitterEventArgs (this, idx, splitterDistances [idx]));
+		}
+
+		/// <summary>
+		/// Scraps all <see cref="Tiles"/> and creates <paramref name="count"/> new tiles
+		/// in orientation <see cref="Orientation"/>
+		/// </summary>
+		/// <param name="count"></param>
+		public void RebuildForTileCount (int count)
+		{
+			tiles = new List<Tile> ();
+			splitterDistances = new List<Pos> ();
+			if (splitterLines != null) {
+				foreach (var sl in splitterLines) {
+					sl.Dispose ();
+				}
+			}
+			splitterLines = new List<TileViewLineView> ();
+
+			RemoveAll ();
+			foreach (var tile in tiles) {
+				tile.ContentView.Dispose ();
+				tile.ContentView = null;
+			}
+			tiles.Clear ();
+			splitterDistances.Clear ();
+
+			if (count == 0) {
+				return;
+			}
+
+			for (int i = 0; i < count; i++) {
+				if (i > 0) {
+					var currentPos = Pos.Percent ((100 / count) * i);
+					splitterDistances.Add (currentPos);
+					var line = new TileViewLineView (this, i - 1);
+					Add (line);
+					splitterLines.Add (line);
+				}
+
+				var tile = new Tile ();
+				tiles.Add (tile);
+				Add (tile.ContentView);
+				tile.TitleChanged += (e) => SetNeedsDisplay ();
+			}
+
+			LayoutSubviews ();
+		}
+
+		/// <summary>
+		/// Adds a new <see cref="Tile"/> to the collection at <paramref name="idx"/>.
+		/// This will also add another splitter line
+		/// </summary>
+		/// <param name="idx"></param>
+		public Tile InsertTile (int idx)
+		{
+			var oldTiles = Tiles.ToArray ();
+			RebuildForTileCount (oldTiles.Length + 1);
+
+			Tile toReturn = null;
+
+			for (int i = 0; i < tiles.Count; i++) {
+
+				if (i != idx) {
+					var oldTile = oldTiles [i > idx ? i - 1 : i];
+
+					// remove the new empty View
+					Remove (tiles [i].ContentView);
+					tiles [i].ContentView.Dispose ();
+					tiles [i].ContentView = null;
+
+					// restore old Tile and View
+					tiles [i] = oldTile;
+					Add (tiles [i].ContentView);
+				} else {
+					toReturn = tiles [i];
+				}
+			}
+			SetNeedsDisplay ();
+			LayoutSubviews ();
+
+			return toReturn;
+		}
+
+		/// <summary>
+		/// Removes a <see cref="Tiles"/> at the provided <paramref name="idx"/> from
+		/// the view. Returns the removed tile or null if already empty.
+		/// </summary>
+		/// <param name="idx"></param>
+		/// <returns></returns>
+		public Tile RemoveTile (int idx)
+		{
+			var oldTiles = Tiles.ToArray ();
+
+			if (idx < 0 || idx >= oldTiles.Length) {
+				return null;
+			}
+
+			var removed = Tiles.ElementAt (idx);
+
+			RebuildForTileCount (oldTiles.Length - 1);
+
+			for (int i = 0; i < tiles.Count; i++) {
+
+				int oldIdx = i >= idx ? i + 1 : i;
+				var oldTile = oldTiles [oldIdx];
+
+				// remove the new empty View
+				Remove (tiles [i].ContentView);
+				tiles [i].ContentView.Dispose ();
+				tiles [i].ContentView = null;
+
+				// restore old Tile and View
+				tiles [i] = oldTile;
+				Add (tiles [i].ContentView);
+
+			}
+			SetNeedsDisplay ();
+			LayoutSubviews ();
+
+			return removed;
+		}
+
+		///<summary>
+		/// Returns the index of the first <see cref="Tile"/> in
+		/// <see cref="Tiles"/> which contains <paramref name="toFind"/>.
+		///</summary>
+		public int IndexOf (View toFind, bool recursive = false)
+		{
+			for (int i = 0; i < tiles.Count; i++) {
+				var v = tiles [i].ContentView;
+
+				if (v == toFind) {
+					return i;
+				}
+
+				if (v.Subviews.Contains (toFind)) {
+					return i;
+				}
+
+				if (recursive) {
+					if (RecursiveContains (v.Subviews, toFind)) {
+						return i;
+					}
+				}
+			}
+
+			return -1;
+		}
+
+		private bool RecursiveContains (IEnumerable<View> haystack, View needle)
+		{
+			foreach (var v in haystack) {
+				if (v == needle) {
+					return true;
+				}
+
+				if (RecursiveContains (v.Subviews, needle)) {
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+		/// <summary>
+		/// Orientation of the dividing line (Horizontal or Vertical).
+		/// </summary>
+		public Orientation Orientation {
+			get { return orientation; }
+			set {
+				orientation = value;
+				LayoutSubviews ();
+			}
+		}
+		/// <inheritdoc/>
+		public override void LayoutSubviews ()
+		{
+			var contentArea = Bounds;
+
+			if (HasBorder ()) {
+				contentArea = new Rect (
+					contentArea.X + 1,
+					contentArea.Y + 1,
+					Math.Max (0, contentArea.Width - 2),
+					Math.Max (0, contentArea.Height - 2));
+			}
+
+			Setup (contentArea);
+			base.LayoutSubviews ();
+		}
+
+		/// <summary>
+		/// <para>Attempts to update the <see cref="splitterDistances"/> of line at <paramref name="idx"/>
+		/// to the new <paramref name="value"/>. Returns false if the new position is not allowed because of
+		/// <see cref="Tile.MinSize"/>, location of other splitters etc.
+		/// </para>
+		/// <para>Only absolute values (e.g. 10) and percent values (i.e. <see cref="Pos.Percent(float)"/>)
+		/// are supported for this property.</para>
+		/// </summary>
+		public bool SetSplitterPos (int idx, Pos value)
+		{
+			if (!(value is Pos.PosAbsolute) && !(value is Pos.PosFactor)) {
+				throw new ArgumentException ($"Only Percent and Absolute values are supported. Passed value was {value.GetType ().Name}");
+			}
+
+			var fullSpace = orientation == Orientation.Vertical ? Bounds.Width : Bounds.Height;
+
+			if (fullSpace != 0 && !IsValidNewSplitterPos (idx, value, fullSpace)) {
+				return false;
+			}
+
+			splitterDistances [idx] = value;
+			GetRootTileView ().LayoutSubviews ();
+			OnSplitterMoved (idx);
+			return true;
+		}
+
+		/// <inheritdoc/>
+		public override bool OnEnter (View view)
+		{
+			Driver.SetCursorVisibility (CursorVisibility.Invisible);
+			if (!Tiles.Where (t => t.ContentView.HasFocus).Any ()) {
+				Tiles.FirstOrDefault ()?.ContentView.SetFocus ();
+			}
+			return base.OnEnter (view);
+		}
+
+		/// <inheritdoc/>
+		public override void Redraw (Rect bounds)
+		{
+			Driver.SetAttribute (ColorScheme.Normal);
+			Clear ();
+
+			base.Redraw (bounds);
+
+			var lc = new LineCanvas ();
+
+			var allLines = GetAllLineViewsRecursively (this);
+			var allTitlesToRender = GetAllTitlesToRenderRecursively (this);
+
+			if (IsRootTileView ()) {
+				if (HasBorder ()) {
+
+					lc.AddLine (new Point (0, 0), bounds.Width - 1, Orientation.Horizontal, Border.BorderStyle);
+					lc.AddLine (new Point (0, 0), bounds.Height - 1, Orientation.Vertical, Border.BorderStyle);
+
+					lc.AddLine (new Point (bounds.Width - 1, bounds.Height - 1), -bounds.Width + 1, Orientation.Horizontal, Border.BorderStyle);
+					lc.AddLine (new Point (bounds.Width - 1, bounds.Height - 1), -bounds.Height + 1, Orientation.Vertical, Border.BorderStyle);
+				}
+
+				foreach (var line in allLines) {
+					bool isRoot = splitterLines.Contains (line);
+
+					line.ViewToScreen (0, 0, out var x1, out var y1);
+					var origin = ScreenToView (x1, y1);
+					var length = line.Orientation == Orientation.Horizontal ?
+							line.Frame.Width - 1 :
+							line.Frame.Height - 1;
+
+					if (!isRoot) {
+						if (line.Orientation == Orientation.Horizontal) {
+							origin.X -= 1;
+						} else {
+							origin.Y -= 1;
+						}
+						length += 2;
+					}
+
+					lc.AddLine (origin, length, line.Orientation, Border.BorderStyle);
+				}
+			}
+
+			Driver.SetAttribute (ColorScheme.Normal);
+			lc.Draw (this, bounds);
+
+			// Redraw the lines so that focus/drag symbol renders
+			foreach (var line in allLines) {
+				line.DrawSplitterSymbol ();
+			}
+
+			// Draw Titles over Border
+
+			foreach (var titleToRender in allTitlesToRender) {
+				var renderAt = titleToRender.GetLocalCoordinateForTitle (this);
+
+				if (renderAt.Y < 0) {
+					// If we have no border then root level tiles
+					// have nowhere to render their titles.
+					continue;
+				}
+
+				// TODO: Render with focus color if focused
+
+				var title = titleToRender.GetTrimmedTitle ();
+
+				for (int i = 0; i < title.Length; i++) {
+					AddRune (renderAt.X + i, renderAt.Y, title [i]);
+				}
+			}
+		}
+
+		/// <summary>
+		/// Converts of <see cref="Tiles"/> element <paramref name="idx"/>
+		/// from a regular <see cref="View"/> to a new nested <see cref="TileView"/> 
+		/// the specified <paramref name="numberOfPanels"/>.
+		/// Returns false if the element already contains a nested view.
+		/// </summary>
+		/// <remarks>After successful splitting, the old contents will be moved to the 
+		/// <paramref name="result"/> <see cref="TileView"/>'s first tile.</remarks>
+		/// <param name="idx">The element of <see cref="Tiles"/> that is to be subdivided.</param>
+		/// <param name="numberOfPanels">The number of panels that the <see cref="Tile"/> should be split into</param>
+		/// <param name="result">The new nested <see cref="TileView"/>.</param>
+		/// <returns><see langword="true"/> if a <see cref="View"/> was converted to a new nested
+		/// <see cref="TileView"/>. <see langword="false"/> if it was already a nested
+		/// <see cref="TileView"/></returns>
+		public bool TrySplitTile (int idx, int numberOfPanels, out TileView result)
+		{
+			// when splitting a view into 2 sub views we will need to migrate
+			// the title too
+			var tile = tiles [idx];
+
+			var title = tile.Title;
+			View toMove = tile.ContentView;
+
+			if (toMove is TileView existing) {
+				result = existing;
+				return false;
+			}
+
+			var newContainer = new TileView (numberOfPanels) {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				parentTileView = this,
+			};
+
+			// Take everything out of the View we are moving
+			var childViews = toMove.Subviews.ToArray ();
+			toMove.RemoveAll ();
+
+			// Remove the view itself and replace it with the new TileView
+			Remove (toMove);
+			toMove.Dispose ();
+			toMove = null;
+
+			Add (newContainer);
+
+			tile.ContentView = newContainer;
+
+			var newTileView1 = newContainer.tiles [0].ContentView;
+			// Add the original content into the first view of the new container
+			foreach (var childView in childViews) {
+				newTileView1.Add (childView);
+			}
+
+			// Move the title across too
+			newContainer.tiles [0].Title = title;
+			tile.Title = string.Empty;
+
+			result = newContainer;
+			return true;
+		}
+
+		private bool IsValidNewSplitterPos (int idx, Pos value, int fullSpace)
+		{
+			int newSize = value.Anchor (fullSpace);
+			bool isGettingBigger = newSize > splitterDistances [idx].Anchor (fullSpace);
+			int lastSplitterOrBorder = HasBorder () ? 1 : 0;
+			int nextSplitterOrBorder = HasBorder () ? fullSpace - 1 : fullSpace;
+
+			// Cannot move off screen right
+			if (newSize >= fullSpace - (HasBorder () ? 1 : 0)) {
+
+				if (isGettingBigger) {
+					return false;
+				}
+			}
+
+			// Cannot move off screen left
+			if (newSize < (HasBorder () ? 1 : 0)) {
+
+				if (!isGettingBigger) {
+					return false;
+				}
+			}
+
+			// Do not allow splitter to move left of the one before
+			if (idx > 0) {
+				int posLeft = splitterDistances [idx - 1].Anchor (fullSpace);
+
+				if (newSize <= posLeft) {
+					return false;
+				}
+
+				lastSplitterOrBorder = posLeft;
+			}
+
+			// Do not allow splitter to move right of the one after
+			if (idx + 1 < splitterDistances.Count) {
+				int posRight = splitterDistances [idx + 1].Anchor (fullSpace);
+
+				if (newSize >= posRight) {
+					return false;
+				}
+				nextSplitterOrBorder = posRight;
+			}
+
+			if (isGettingBigger) {
+				var spaceForNext = nextSplitterOrBorder - newSize;
+
+				// space required for the last line itself
+				if (idx > 0) {
+					spaceForNext--;
+				}
+
+				// don't grow if it would take us below min size of right panel
+				if (spaceForNext < tiles [idx + 1].MinSize) {
+					return false;
+				}
+			} else {
+				var spaceForLast = newSize - lastSplitterOrBorder;
+
+				// space required for the line itself
+				if (idx > 0) {
+					spaceForLast--;
+				}
+
+
+				// don't shrink if it would take us below min size of left panel
+				if (spaceForLast < tiles [idx].MinSize) {
+					return false;
+				}
+			}
+
+			return true;
+		}
+
+		private List<TileViewLineView> GetAllLineViewsRecursively (View v)
+		{
+			var lines = new List<TileViewLineView> ();
+
+			foreach (var sub in v.Subviews) {
+				if (sub is TileViewLineView s) {
+					if (s.Visible && s.Parent.GetRootTileView () == this) {
+						lines.Add (s);
+					}
+				} else {
+					if (sub.Visible) {
+						lines.AddRange (GetAllLineViewsRecursively (sub));
+					}
+				}
+			}
+
+			return lines;
+		}
+
+		private List<TileTitleToRender> GetAllTitlesToRenderRecursively (TileView v, int depth = 0)
+		{
+			var titles = new List<TileTitleToRender> ();
+
+			foreach (var sub in v.Tiles) {
+
+				// Don't render titles for invisible stuff!
+				if (!sub.ContentView.Visible) {
+					continue;
+				}
+
+				if (sub.ContentView is TileView subTileView) {
+					// Panels with sub split tiles in them can never
+					// have their Titles rendered. Instead we dive in
+					// and pull up their children as titles
+					titles.AddRange (GetAllTitlesToRenderRecursively (subTileView, depth + 1));
+				} else {
+					if (sub.Title.Length > 0) {
+						titles.Add (new TileTitleToRender (v, sub, depth));
+					}
+				}
+			}
+
+			return titles;
+		}
+
+		/// <summary>
+		/// <para>
+		/// <see langword="true"/> if <see cref="TileView"/> is nested within a parent <see cref="TileView"/>
+		/// e.g. via the <see cref="TrySplitTile"/>. <see langword="false"/> if it is a root level <see cref="TileView"/>.
+		/// </para>
+		/// </summary>
+		/// <remarks>Note that manually adding one <see cref="TileView"/> to another will not result in a parent/child
+		/// relationship and both will still be considered 'root' containers. Always use
+		/// <see cref="TrySplitTile(int, int, out TileView)"/> if you want to subdivide a <see cref="TileView"/>.</remarks>
+		/// <returns></returns>
+		public bool IsRootTileView ()
+		{
+			return parentTileView == null;
+		}
+
+		/// <summary>
+		/// Returns the immediate parent <see cref="TileView"/> of this. Note that in case
+		/// of deep nesting this might not be the root <see cref="TileView"/>. Returns null
+		/// if this instance is not a nested child (created with 
+		/// <see cref="TrySplitTile(int, int, out TileView)"/>)
+		/// </summary>
+		/// <remarks>
+		/// Use <see cref="IsRootTileView"/> to determine if the returned value is the root.
+		/// </remarks>
+		/// <returns></returns>
+		public TileView GetParentTileView ()
+		{
+			return this.parentTileView;
+		}
+		private TileView GetRootTileView ()
+		{
+			TileView root = this;
+
+			while (root.parentTileView != null) {
+				root = root.parentTileView;
+			}
+
+			return root;
+		}
+		private void Setup (Rect bounds)
+		{
+			if (bounds.IsEmpty || bounds.Height <= 0 || bounds.Width <= 0) {
+				return;
+			}
+
+			for (int i = 0; i < splitterLines.Count; i++) {
+				var line = splitterLines [i];
+
+				line.Orientation = Orientation;
+				line.Width = orientation == Orientation.Vertical
+					? 1 : Dim.Fill ();
+				line.Height = orientation == Orientation.Vertical
+					? Dim.Fill () : 1;
+				line.LineRune = orientation == Orientation.Vertical ?
+					Driver.VLine : Driver.HLine;
+
+				if (orientation == Orientation.Vertical) {
+					line.X = splitterDistances [i];
+					line.Y = 0;
+				} else {
+					line.Y = splitterDistances [i];
+					line.X = 0;
+				}
+
+			}
+
+			HideSplittersBasedOnTileVisibility ();
+
+			var visibleTiles = tiles.Where (t => t.ContentView.Visible).ToArray ();
+			var visibleSplitterLines = splitterLines.Where (l => l.Visible).ToArray ();
+
+			for (int i = 0; i < visibleTiles.Length; i++) {
+				var tile = visibleTiles [i];
+
+				if (Orientation == Orientation.Vertical) {
+					tile.ContentView.X = i == 0 ? bounds.X : Pos.Right (visibleSplitterLines [i - 1]);
+					tile.ContentView.Y = bounds.Y;
+					tile.ContentView.Height = bounds.Height;
+					tile.ContentView.Width = GetTileWidthOrHeight (i, Bounds.Width, visibleTiles, visibleSplitterLines);
+				} else {
+					tile.ContentView.X = bounds.X;
+					tile.ContentView.Y = i == 0 ? 0 : Pos.Bottom (visibleSplitterLines [i - 1]);
+					tile.ContentView.Width = bounds.Width;
+					tile.ContentView.Height = GetTileWidthOrHeight (i, Bounds.Height, visibleTiles, visibleSplitterLines);
+				}
+			}
+		}
+
+		private void HideSplittersBasedOnTileVisibility ()
+		{
+			if (splitterLines.Count == 0) {
+				return;
+			}
+
+			foreach (var line in splitterLines) {
+				line.Visible = true;
+			}
+
+			for (int i = 0; i < tiles.Count; i++) {
+				if (!tiles [i].ContentView.Visible) {
+
+					// when a tile is not visible, prefer hiding
+					// the splitter on it's left
+					var candidate = splitterLines [Math.Max (0, i - 1)];
+
+					// unless that splitter is already hidden
+					// e.g. when hiding panels 0 and 1 of a 3 panel 
+					// container
+					if (candidate.Visible) {
+						candidate.Visible = false;
+					} else {
+						splitterLines [Math.Min (i, splitterLines.Count - 1)].Visible = false;
+					}
+
+
+				}
+			}
+		}
+
+		private Dim GetTileWidthOrHeight (int i, int space, Tile [] visibleTiles, TileViewLineView [] visibleSplitterLines)
+		{
+			// last tile
+			if (i + 1 >= visibleTiles.Length) {
+				return Dim.Fill (HasBorder () ? 1 : 0);
+			}
+
+			var nextSplitter = visibleSplitterLines [i];
+			var nextSplitterPos = Orientation == Orientation.Vertical ?
+				nextSplitter.X : nextSplitter.Y;
+			var nextSplitterDistance = nextSplitterPos.Anchor (space);
+
+			var lastSplitter = i >= 1 ? visibleSplitterLines [i - 1] : null;
+			var lastSplitterPos = Orientation == Orientation.Vertical ?
+				lastSplitter?.X : lastSplitter?.Y;
+			var lastSplitterDistance = lastSplitterPos?.Anchor (space) ?? 0;
+
+			var distance = nextSplitterDistance - lastSplitterDistance;
+
+			if (i > 0) {
+				return distance - 1;
+			}
+
+			return distance - (HasBorder () ? 1 : 0);
+		}
+
+		private class TileTitleToRender {
+			public TileView Parent { get; }
+			public Tile Tile { get; }
+
+			public int Depth { get; }
+
+			public TileTitleToRender (TileView parent, Tile tile, int depth)
+			{
+				Parent = parent;
+				Tile = tile;
+				Depth = depth;
+			}
+
+			/// <summary>
+			/// Translates the <see cref="Tile"/> title location from its local
+			/// coordinate space <paramref name="intoCoordinateSpace"/>.
+			/// </summary>
+			public Point GetLocalCoordinateForTitle (TileView intoCoordinateSpace)
+			{
+				Tile.ContentView.ViewToScreen (0, 0, out var screenCol, out var screenRow);
+				screenRow--;
+				return intoCoordinateSpace.ScreenToView (screenCol, screenRow);
+			}
+
+			internal string GetTrimmedTitle ()
+			{
+				Dim spaceDim = Tile.ContentView.Width;
+
+				var spaceAbs = spaceDim.Anchor (Parent.Bounds.Width);
+
+				var title = $" {Tile.Title} ";
+
+				if (title.Length > spaceAbs) {
+					return title.Substring (0, spaceAbs);
+				}
+
+				return title;
+			}
+		}
+
+		private class TileViewLineView : LineView {
+			public TileView Parent { get; private set; }
+			public int Idx { get; }
+
+			Point? dragPosition;
+			Pos dragOrignalPos;
+			public Point? moveRuneRenderLocation;
+
+			public TileViewLineView (TileView parent, int idx)
+			{
+				CanFocus = true;
+				TabStop = true;
+
+				this.Parent = parent;
+				Idx = idx;
+				base.AddCommand (Command.Right, () => {
+					return MoveSplitter (1, 0);
+				});
+
+				base.AddCommand (Command.Left, () => {
+					return MoveSplitter (-1, 0);
+				});
+
+				base.AddCommand (Command.LineUp, () => {
+					return MoveSplitter (0, -1);
+				});
+
+				base.AddCommand (Command.LineDown, () => {
+					return MoveSplitter (0, 1);
+				});
+
+				AddKeyBinding (Key.CursorRight, Command.Right);
+				AddKeyBinding (Key.CursorLeft, Command.Left);
+				AddKeyBinding (Key.CursorUp, Command.LineUp);
+				AddKeyBinding (Key.CursorDown, Command.LineDown);
+			}
+
+			public override bool ProcessKey (KeyEvent kb)
+			{
+				if (!CanFocus || !HasFocus) {
+					return base.ProcessKey (kb);
+				}
+
+				var result = InvokeKeybindings (kb);
+				if (result != null)
+					return (bool)result;
+
+				return base.ProcessKey (kb);
+			}
+
+			public override void PositionCursor ()
+			{
+				base.PositionCursor ();
+				var location = moveRuneRenderLocation ??
+					new Point (Bounds.Width / 2, Bounds.Height / 2);
+				Move (location.X, location.Y);
+			}
+
+			public override bool OnEnter (View view)
+			{
+				Driver.SetCursorVisibility (CursorVisibility.Default);
+				PositionCursor ();
+
+				return base.OnEnter (view);
+			}
+
+			public override void Redraw (Rect bounds)
+			{
+				base.Redraw (bounds);
+
+				DrawSplitterSymbol ();
+			}
+
+			public void DrawSplitterSymbol ()
+			{
+				if (CanFocus && HasFocus) {
+					var location = moveRuneRenderLocation ??
+						new Point (Bounds.Width / 2, Bounds.Height / 2);
+
+					AddRune (location.X, location.Y, Driver.Diamond);
+				}
+			}
+
+			public override bool MouseEvent (MouseEvent mouseEvent)
+			{
+				if (!CanFocus) {
+					return true;
+				}
+
+				if (!dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed)) {
+
+					// Start a Drag
+					SetFocus ();
+					Application.EnsuresTopOnFront ();
+
+					if (mouseEvent.Flags == MouseFlags.Button1Pressed) {
+						dragPosition = new Point (mouseEvent.X, mouseEvent.Y);
+						dragOrignalPos = Orientation == Orientation.Horizontal ? Y : X;
+						Application.GrabMouse (this);
+
+						if (Orientation == Orientation.Horizontal) {
+
+						} else {
+							moveRuneRenderLocation = new Point (0, Math.Max (1, Math.Min (Bounds.Height - 2, mouseEvent.Y)));
+						}
+					}
+
+					return true;
+				} else if (
+					dragPosition.HasValue &&
+					(mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
+
+					// Continue Drag
+
+					// how far has user dragged from original location?						
+					if (Orientation == Orientation.Horizontal) {
+						int dy = mouseEvent.Y - dragPosition.Value.Y;
+						Parent.SetSplitterPos (Idx, Offset (Y, dy));
+						moveRuneRenderLocation = new Point (mouseEvent.X, 0);
+					} else {
+						int dx = mouseEvent.X - dragPosition.Value.X;
+						Parent.SetSplitterPos (Idx, Offset (X, dx));
+						moveRuneRenderLocation = new Point (0, Math.Max (1, Math.Min (Bounds.Height - 2, mouseEvent.Y)));
+					}
+
+					Parent.SetNeedsDisplay ();
+					return true;
+				}
+
+				if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) {
+
+					// End Drag
+
+					Application.UngrabMouse ();
+					Driver.UncookMouse ();
+					FinalisePosition (
+						dragOrignalPos,
+						Orientation == Orientation.Horizontal ? Y : X);
+					dragPosition = null;
+					moveRuneRenderLocation = null;
+				}
+
+				return false;
+			}
+
+			private bool MoveSplitter (int distanceX, int distanceY)
+			{
+				if (Orientation == Orientation.Vertical) {
+
+					// Cannot move in this direction
+					if (distanceX == 0) {
+						return false;
+					}
+
+					var oldX = X;
+					return FinalisePosition (oldX, Offset (X, distanceX));
+				} else {
+
+					// Cannot move in this direction
+					if (distanceY == 0) {
+						return false;
+					}
+
+					var oldY = Y;
+					return FinalisePosition (oldY, (Pos)Offset (Y, distanceY));
+				}
+			}
+
+			private Pos Offset (Pos pos, int delta)
+			{
+				var posAbsolute = pos.Anchor (Orientation == Orientation.Horizontal ?
+					Parent.Bounds.Height : Parent.Bounds.Width);
+
+				return posAbsolute + delta;
+			}
+
+			/// <summary>
+			/// <para>
+			/// Moves <see cref="Parent"/> <see cref="TileView.SplitterDistances"/> to 
+			/// <see cref="Pos"/> <paramref name="newValue"/> preserving <see cref="Pos"/> format
+			/// (absolute / relative) that <paramref name="oldValue"/> had.
+			/// </para>
+			/// <remarks>This ensures that if splitter location was e.g. 50% before and you move it
+			/// to absolute 5 then you end up with 10% (assuming a parent had 50 width). </remarks>
+			/// </summary>
+			/// <param name="oldValue"></param>
+			/// <param name="newValue"></param>
+			private bool FinalisePosition (Pos oldValue, Pos newValue)
+			{
+				if (oldValue is Pos.PosFactor) {
+					if (Orientation == Orientation.Horizontal) {
+						return Parent.SetSplitterPos (Idx, ConvertToPosFactor (newValue, Parent.Bounds.Height));
+					} else {
+						return Parent.SetSplitterPos (Idx, ConvertToPosFactor (newValue, Parent.Bounds.Width));
+					}
+				} else {
+					return Parent.SetSplitterPos (Idx, newValue);
+				}
+			}
+
+			/// <summary>
+			/// <para>
+			/// Determines the absolute position of <paramref name="p"/> and
+			/// returns a <see cref="Pos.PosFactor"/> that describes the percentage of that.
+			/// </para>
+			/// <para>Effectively turning any <see cref="Pos"/> into a <see cref="Pos.PosFactor"/>
+			/// (as if created with <see cref="Pos.Percent(float)"/>)</para>
+			/// </summary>
+			/// <param name="p">The <see cref="Pos"/> to convert to <see cref="Pos.Percent(float)"/></param>
+			/// <param name="parentLength">The Height/Width that <paramref name="p"/> lies within</param>
+			/// <returns></returns>
+			private Pos ConvertToPosFactor (Pos p, int parentLength)
+			{
+				// calculate position in the 'middle' of the cell at p distance along parentLength
+				float position = p.Anchor (parentLength) + 0.5f;
+
+				return new Pos.PosFactor (position / parentLength);
+			}
+		}
+
+		private bool HasBorder ()
+		{
+			return Border?.BorderStyle != BorderStyle.None;
+		}
+
+		/// <inheritdoc/>
+		protected override void Dispose (bool disposing)
+		{
+			foreach (var tile in Tiles) {
+				Remove (tile.ContentView);
+				tile.ContentView.Dispose ();
+			}
+			base.Dispose (disposing);
+		}
+
+	}
+
+	/// <summary>
+	/// Provides data for <see cref="TileView"/> events.
+	/// </summary>
+	public class SplitterEventArgs : EventArgs {
+
+		/// <summary>
+		/// Creates a new instance of the <see cref="SplitterEventArgs"/> class.
+		/// </summary>
+		/// <param name="tileView"><see cref="TileView"/> in which splitter is being moved.</param>
+		/// <param name="idx">Index of the splitter being moved in <see cref="TileView.SplitterDistances"/>.</param>
+		/// <param name="splitterDistance">The new <see cref="Pos"/> of the splitter line.</param>
+		public SplitterEventArgs (TileView tileView, int idx, Pos splitterDistance)
+		{
+			SplitterDistance = splitterDistance;
+			TileView = tileView;
+			Idx = idx;
+		}
+
+		/// <summary>
+		/// New position of the splitter line (see <see cref="TileView.SplitterDistances"/>).
+		/// </summary>
+		public Pos SplitterDistance { get; }
+
+		/// <summary>
+		/// Container (sender) of the event.
+		/// </summary>
+		public TileView TileView { get; }
+
+		/// <summary>
+		/// Gets the index of the splitter that is being moved. This can be
+		/// used to index <see cref="TileView.SplitterDistances"/>
+		/// </summary>
+		public int Idx { get; }
+	}
+
+	/// <summary>
+	/// Represents a method that will handle splitter events.
+	/// </summary>
+	public delegate void SplitterEventHandler (object sender, SplitterEventArgs e);
+}

+ 4 - 0
UICatalog/Properties/launchSettings.json

@@ -54,6 +54,10 @@
       "executablePath": "wsl",
       "commandLineArgs": "dotnet UICatalog.dll",
       "distributionName": ""
+    },
+    "Tile View Experiments": {
+      "commandName": "Project",
+      "commandLineArgs": "\"Tile View Experiments\""
     }
   }
 }

+ 201 - 0
UICatalog/Scenarios/LineDrawing.cs

@@ -0,0 +1,201 @@
+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));
+				grid.Draw (this, bounds);
+
+				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));
+					canvases [kvp.Value].Draw (this, bounds);
+				}
+			}
+			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];
+			}
+
+		}
+	}
+}

+ 208 - 60
UICatalog/Scenarios/Notepad.cs

@@ -1,5 +1,8 @@
-using System.IO;
+using System;
+using System.IO;
+using System.Linq;
 using Terminal.Gui;
+using Terminal.Gui.Graphs;
 
 namespace UICatalog.Scenarios {
 
@@ -9,6 +12,8 @@ namespace UICatalog.Scenarios {
 		TabView tabView;
 
 		private int numbeOfNewTabs = 1;
+		private TabView focusedTabView;
+		private StatusItem lenStatusItem;
 
 		// Don't create a Window, just return the top-level view
 		public override void Init (ColorScheme colorScheme)
@@ -31,21 +36,24 @@ namespace UICatalog.Scenarios {
 				});
 			Application.Top.Add (menu);
 
-			tabView = new TabView () {
+			tabView = CreateNewTabView ();
+
+			tabView.Style.ShowBorder = true;
+			tabView.ApplyStyleChanges ();
+
+			// Start with only a single view but support splitting to show side by side
+			var split = new TileView(1) {
 				X = 0,
 				Y = 1,
 				Width = Dim.Fill (),
 				Height = Dim.Fill (1),
 			};
+			split.Tiles.ElementAt(0).ContentView.Add (tabView);
+			split.Border.BorderStyle = BorderStyle.None;
 
-			tabView.TabClicked += TabView_TabClicked;
-
-			tabView.Style.ShowBorder = true;
-			tabView.ApplyStyleChanges ();
-
-			Application.Top.Add (tabView);
+			Application.Top.Add (split);
 
-			var lenStatusItem = new StatusItem (Key.CharMask, "Len: ", null);
+			lenStatusItem = new StatusItem (Key.CharMask, "Len: ", null);
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 
@@ -57,14 +65,20 @@ namespace UICatalog.Scenarios {
 				new StatusItem(Key.CtrlMask | Key.W, "~^W~ Close", () => Close()),
 				lenStatusItem,
 			});
-
-			tabView.SelectedTabChanged += (s, e) => lenStatusItem.Title = $"Len:{(e.NewTab?.View?.Text?.Length ?? 0)}";
+			focusedTabView = tabView;
+			tabView.SelectedTabChanged += TabView_SelectedTabChanged;
+			tabView.Enter += (e) => focusedTabView = tabView;
 
 			Application.Top.Add (statusBar);
 
 			New ();
 		}
 
+		private void TabView_SelectedTabChanged (object sender, TabView.TabChangedEventArgs e)
+		{
+			lenStatusItem.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
+		}
+
 		private void TabView_TabClicked (object sender, TabView.TabMouseEventArgs e)
 		{
 			// we are only interested in right clicks
@@ -80,29 +94,100 @@ namespace UICatalog.Scenarios {
 				});
 
 			} else {
+
+				var tv = (TabView)sender;
+				var t = (OpenedFile)e.Tab;
+
 				items = new MenuBarItem (new MenuItem [] {
-					new MenuItem ($"Save", "", () => Save(e.Tab)),
-					new MenuItem ($"Close", "", () => Close(e.Tab)),
+					new MenuItem ($"Save", "", () => Save(focusedTabView, e.Tab)),
+					new MenuItem ($"Close", "", () => Close(tv, e.Tab)),
+					null,
+					new MenuItem ($"Split Up", "", () => SplitUp(tv,t)),
+					new MenuItem ($"Split Down", "", () => SplitDown(tv,t)),
+					new MenuItem ($"Split Right", "", () => SplitRight(tv,t)),
+					new MenuItem ($"Split Left", "", () => SplitLeft(tv,t)),
 				});
 			}
 
+		((View)sender).ViewToScreen (e.MouseEvent.X, e.MouseEvent.Y, out int screenX, out int screenY,true);
 
-			var contextMenu = new ContextMenu (e.MouseEvent.X + 1, e.MouseEvent.Y + 1, items);
+		var contextMenu = new ContextMenu (screenX,screenY, items);
 
 			contextMenu.Show ();
 			e.MouseEvent.Handled = true;
 		}
 
+		private void SplitUp (TabView sender, OpenedFile tab)
+		{
+			Split(0, Orientation.Horizontal,sender,tab);			
+		}
+		private void SplitDown (TabView sender, OpenedFile tab)
+		{
+			Split(1, Orientation.Horizontal,sender,tab);
+			
+		}
+		private void SplitLeft (TabView sender, OpenedFile tab)
+		{
+			Split(0, Orientation.Vertical,sender,tab);
+		}
+		private void SplitRight (TabView sender, OpenedFile tab)
+		{
+			Split(1, Orientation.Vertical,sender,tab);
+		}
+
+		private void Split (int offset, Orientation orientation,TabView sender, OpenedFile tab)
+		{
+			
+			var split = (TileView)sender.SuperView.SuperView;
+			var tileIndex = split.IndexOf(sender);
+
+			if(tileIndex == -1)
+			{
+				return;
+			}
+
+			if(orientation != split.Orientation)
+			{
+				split.TrySplitTile(tileIndex,1,out split);
+				split.Orientation = orientation;
+				tileIndex = 0;
+			}
+
+			var newTile = split.InsertTile(tileIndex + offset);
+			var newTabView = CreateNewTabView ();
+			tab.CloneTo (newTabView);
+			newTile.ContentView.Add(newTabView);
+
+			newTabView.EnsureFocus();
+			newTabView.FocusFirst();
+			newTabView.FocusNext();
+		}
+
+		private TabView CreateNewTabView ()
+		{
+			var tv = new TabView () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+			};
+
+			tv.TabClicked += TabView_TabClicked;
+			tv.SelectedTabChanged += TabView_SelectedTabChanged;
+			tv.Enter += (e) => focusedTabView = tv;
+			return tv;
+		}
+
 		private void New ()
 		{
-			Open ("", null, $"new {numbeOfNewTabs++}");
+			Open (null, $"new {numbeOfNewTabs++}");
 		}
 
 		private void Close ()
 		{
-			Close (tabView.SelectedTab);
+			Close (focusedTabView, focusedTabView.SelectedTab);
 		}
-		private void Close (TabView.Tab tabToClose)
+		private void Close (TabView tv, TabView.Tab tabToClose)
 		{
 			var tab = tabToClose as OpenedFile;
 
@@ -110,6 +195,8 @@ namespace UICatalog.Scenarios {
 				return;
 			}
 
+			focusedTabView = tv;
+
 			if (tab.UnsavedChanges) {
 
 				int result = MessageBox.Query ("Save Changes", $"Save changes to {tab.Text.ToString ().TrimEnd ('*')}", "Yes", "No", "Cancel");
@@ -121,13 +208,48 @@ namespace UICatalog.Scenarios {
 				}
 
 				if (result == 0) {
-					tab.Save ();
+					if(tab.File == null) {
+						SaveAs ();
+					} else {
+						tab.Save ();
+					}
 				}
 			}
 
 			// close and dispose the tab
-			tabView.RemoveTab (tab);
+			tv.RemoveTab (tab);
 			tab.View.Dispose ();
+			focusedTabView = tv;
+
+			if(tv.Tabs.Count == 0) {
+
+				var split = (TileView)tv.SuperView.SuperView;
+
+				// if it is the last TabView on screen don't drop it or we will
+				// be unable to open new docs!
+				if(split.IsRootTileView() && split.Tiles.Count == 1) {
+					return;
+				}
+
+				var tileIndex = split.IndexOf (tv);
+				split.RemoveTile (tileIndex);
+
+				if(split.Tiles.Count == 0) {
+					var parent = split.GetParentTileView ();
+
+					if (parent == null) {
+						return;
+					}
+
+					var idx = parent.IndexOf (split);
+
+					if (idx == -1) {
+						return;
+					}
+
+					parent.RemoveTile (idx);
+				}
+			}
 		}
 
 		private void Open ()
@@ -144,7 +266,8 @@ namespace UICatalog.Scenarios {
 						return;
 					}
 
-					Open (File.ReadAllText (path), new FileInfo (path), Path.GetFileName (path));
+					// TODO should open in focused TabView
+					Open (new FileInfo (path), Path.GetFileName (path));
 				}
 			}
 		}
@@ -152,49 +275,18 @@ namespace UICatalog.Scenarios {
 		/// <summary>
 		/// Creates a new tab with initial text
 		/// </summary>
-		/// <param name="initialText"></param>
 		/// <param name="fileInfo">File that was read or null if a new blank document</param>
-		private void Open (string initialText, FileInfo fileInfo, string tabName)
+		private void Open (FileInfo fileInfo, string tabName)
 		{
-			var textView = new TextView () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-				Text = initialText
-			};
-
-			var tab = new OpenedFile (tabName, fileInfo, textView);
-			tabView.AddTab (tab, true);
-
-			// when user makes changes rename tab to indicate unsaved
-			textView.KeyUp += (k) => {
-
-				// if current text doesn't match saved text
-				var areDiff = tab.UnsavedChanges;
-
-				if (areDiff) {
-					if (!tab.Text.ToString ().EndsWith ('*')) {
-
-						tab.Text = tab.Text.ToString () + '*';
-						tabView.SetNeedsDisplay ();
-					}
-				} else {
-
-					if (tab.Text.ToString ().EndsWith ('*')) {
-
-						tab.Text = tab.Text.ToString ().TrimEnd ('*');
-						tabView.SetNeedsDisplay ();
-					}
-				}
-			};
+			var tab = new OpenedFile (focusedTabView, tabName, fileInfo);
+			focusedTabView.AddTab (tab, true);
 		}
 
 		public void Save ()
 		{
-			Save (tabView.SelectedTab);
+			Save (focusedTabView, focusedTabView.SelectedTab);
 		}
-		public void Save (TabView.Tab tabToSave)
+		public void Save (TabView tabViewToSave, TabView.Tab tabToSave)
 		{
 			var tab = tabToSave as OpenedFile;
 
@@ -207,12 +299,12 @@ namespace UICatalog.Scenarios {
 			}
 
 			tab.Save ();
-			tabView.SetNeedsDisplay ();
+			tabViewToSave.SetNeedsDisplay ();
 		}
 
 		public bool SaveAs ()
 		{
-			var tab = tabView.SelectedTab as OpenedFile;
+			var tab = focusedTabView.SelectedTab as OpenedFile;
 
 			if (tab == null) {
 				return false;
@@ -224,6 +316,10 @@ namespace UICatalog.Scenarios {
 			if (string.IsNullOrWhiteSpace (fd.FilePath?.ToString ())) {
 				return false;
 			}
+			
+			if(fd.Canceled) {
+				return false;
+			}
 
 			tab.File = new FileInfo (fd.FilePath.ToString ());
 			tab.Text = fd.FileName.ToString ();
@@ -243,12 +339,64 @@ namespace UICatalog.Scenarios {
 
 			public bool UnsavedChanges => !string.Equals (SavedText, View.Text.ToString ());
 
-			public OpenedFile (string name, FileInfo file, TextView control) : base (name, control)
+			public OpenedFile (TabView parent, string name, FileInfo file) 
+				: base (name, CreateTextView(file))
 			{
+
 				File = file;
-				SavedText = control.Text.ToString ();
+				SavedText = View.Text.ToString ();
+				RegisterTextViewEvents (parent);
+			}
+
+			private void RegisterTextViewEvents (TabView parent)
+			{
+				var textView = (TextView)View;
+				// when user makes changes rename tab to indicate unsaved
+				textView.KeyUp += (k) => {
+
+					// if current text doesn't match saved text
+					var areDiff = this.UnsavedChanges;
+
+					if (areDiff) {
+						if (!this.Text.ToString ().EndsWith ('*')) {
+
+							this.Text = this.Text.ToString () + '*';
+							parent.SetNeedsDisplay ();
+						}
+					} else {
+						
+						if (Text.ToString ().EndsWith ('*')) {
+
+							Text = Text.ToString ().TrimEnd ('*');
+							parent.SetNeedsDisplay ();
+						}
+					}
+				};
 			}
 
+			private static View CreateTextView (FileInfo file)
+			{
+				string initialText = string.Empty;
+				if(file != null && file.Exists) {
+					
+					initialText = System.IO.File.ReadAllText (file.FullName);
+				}
+
+				return new TextView () {
+					X = 0,
+					Y = 0,
+					Width = Dim.Fill (),
+					Height = Dim.Fill (),
+					Text = initialText,
+					AllowsTab = false,
+				};
+			}
+			public OpenedFile CloneTo(TabView other)
+			{
+				var newTab = new OpenedFile (other, base.Text.ToString(), File);
+				other.AddTab (newTab, true);
+				return newTab;
+			}
 			internal void Save ()
 			{
 				var newText = View.Text.ToString ();

+ 130 - 0
UICatalog/Scenarios/TileViewExperiment.cs

@@ -0,0 +1,130 @@
+using System;
+using Terminal.Gui;
+using Terminal.Gui.Graphs;
+using System.Linq;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Tile View Experiments", Description: "Experiments with Tile View")]
+	[ScenarioCategory ("Controls")]
+	[ScenarioCategory ("LineView")]
+	public class TileViewExperiment : Scenario {
+
+
+		public override void Init (ColorScheme colorScheme)
+		{
+			Application.Init ();
+		}
+
+		/// <summary>
+		/// Setup the scenario.
+		/// </summary>
+		public override void Setup ()
+		{
+			var menu = new MenuBar (new MenuBarItem [] {
+			new MenuBarItem ("_File", new MenuItem [] {
+				new MenuItem ("_Quit", "", () => Application.RequestStop()),
+			}) });
+
+			Application.Top.Add (menu);
+
+			var frame1 = new FrameView () {
+				X = 0,
+				Y = 1,
+				Width = 15, //Dim.Fill (),
+				Height = 15, //Dim.Fill (),
+				//IgnoreBorderPropertyOnRedraw = true
+
+			};
+			frame1.Border.BorderStyle = BorderStyle.Double;
+
+			var frame2 = new FrameView () {
+				X = 0,
+				Y = Pos.Bottom (frame1) + 1,
+				Width = 15, //Dim.Fill (),
+				Height = 15, //Dim.Fill (),
+					     //IgnoreBorderPropertyOnRedraw = true
+
+			};
+			frame2.Border.BorderStyle = BorderStyle.Single;
+
+			ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler;
+
+			Application.Top.Add (frame1);
+
+			var view1 = new TextField () {
+				//Title = "View 1",
+				Text = "View1 30%/50% Single",
+				X = 0,
+				Y = 0,
+				Width = 14, //Dim.Percent (30) - 5,
+				Height = 14, //Dim.Percent (50) - 5,
+				ColorScheme = Colors.ColorSchemes ["Dialog"],
+				Border = new Border () { 
+					BorderStyle = BorderStyle.Single, 
+					//BorderThickness = new Thickness (1), 
+					DrawMarginFrame = true,
+					Padding = new Thickness(1),
+					BorderBrush = Color.BrightMagenta,
+					Title = "Border Title"
+				}
+			};
+
+			frame1.Add (view1);
+			frame2.Add (view1);
+
+			//var view12splitter = new SplitterEventArgs
+
+			//var view2 = new FrameView () {
+			//	Title = "View 2",
+			//	Text = "View2 right of view1, 30%/70% Single.",
+			//	X = Pos.Right (view1) - 1,
+			//	Y = -1,
+			//	Width = Dim.Percent (30),
+			//	Height = Dim.Percent (70),
+			//	ColorScheme = Colors.ColorSchemes ["Error"],
+			//	Border = new Border () { BorderStyle = BorderStyle.Single }
+			//};
+
+			//frame.Add (view2);
+
+			//var view3 = new FrameView () {
+			//	Title = "View 3",
+			//	Text = "View3 right of View2 Fill/Fill Single",
+			//	X = Pos.Right (view2) - 1,
+			//	Y = -1,
+			//	Width = Dim.Fill (-1),
+			//	Height = Dim.Fill (-1),
+			//	ColorScheme = Colors.ColorSchemes ["Menu"],
+			//	Border = new Border () { BorderStyle = BorderStyle.Single }
+			//};
+
+			//frame.Add (view3);
+
+			//var view4 = new FrameView () {
+			//	Title = "View 4",
+			//	Text = "View4 below View2 view2.Width/5 Single",
+			//	X = Pos.Left (view2),
+			//	Y = Pos.Bottom (view2) - 1,
+			//	Width = view2.Width,
+			//	Height = 5,
+			//	ColorScheme = Colors.ColorSchemes ["TopLevel"],
+			//	Border = new Border () { BorderStyle = BorderStyle.Single }
+			//};
+
+			//frame.Add (view4);
+
+			//var view5 = new FrameView () {
+			//	Title = "View 5",
+			//	Text = "View5 below View4 view4.Width/5 Double",
+			//	X = Pos.Left (view2),
+			//	Y = Pos.Bottom (view4) - 1,
+			//	Width = view4.Width,
+			//	Height = 5,
+			//	ColorScheme = Colors.ColorSchemes ["TopLevel"],
+			//	Border = new Border () { BorderStyle = BorderStyle.Double }
+			//};
+
+			//frame.Add (view5);
+		}
+	}
+}

+ 243 - 0
UICatalog/Scenarios/TileViewNesting.cs

@@ -0,0 +1,243 @@
+using System;
+using Terminal.Gui;
+using Terminal.Gui.Graphs;
+using System.Linq;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Tile View Nesting", Description: "Demonstrates recursive nesting of TileViews")]
+	[ScenarioCategory ("Controls")]
+	[ScenarioCategory ("LineView")]
+	public class TileViewNesting : Scenario {
+
+		private View workArea;
+		private TextField textField;
+		private CheckBox cbHorizontal;
+		private CheckBox cbBorder;
+		private CheckBox cbTitles;
+		private CheckBox cbUseLabels;
+
+		bool loaded = false;
+		int viewsCreated;
+		int viewsToCreate;
+
+		/// <summary>
+		/// Setup the scenario.
+		/// </summary>
+		public override void Setup ()
+		{
+			// Scenario Windows.
+			Win.Title = this.GetName ();
+			Win.Y = 1;
+
+			var lblViews = new Label ("Number Of Views:");
+			textField = new TextField {
+				X = Pos.Right (lblViews),
+				Width = 10,
+				Text = "2",
+			};
+
+			textField.TextChanged += (s) => SetupTileView ();
+
+
+			cbHorizontal = new CheckBox ("Horizontal") {
+				X = Pos.Right (textField) + 1
+			};
+			cbHorizontal.Toggled += (s) => SetupTileView ();
+
+			cbBorder = new CheckBox ("Border") {
+				X = Pos.Right (cbHorizontal) + 1
+			};
+			cbBorder.Toggled += (s) => SetupTileView ();
+
+			cbTitles = new CheckBox ("Titles") {
+				X = Pos.Right (cbBorder) + 1
+			};
+			cbTitles.Toggled += (s) => SetupTileView ();
+
+			cbUseLabels = new CheckBox ("Use Labels") {
+				X = Pos.Right (cbTitles) + 1
+			};
+			cbUseLabels.Toggled += (s) => SetupTileView ();
+
+			workArea = new View {
+				X = 0,
+				Y = 1,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+			};
+
+			var menu = new MenuBar (new MenuBarItem [] {
+			new MenuBarItem ("_File", new MenuItem [] {
+				new MenuItem ("_Quit", "", () => Quit()),
+			}) });
+
+			Win.Add (lblViews);
+			Win.Add (textField);
+			Win.Add (cbHorizontal);
+			Win.Add (cbBorder);
+			Win.Add (cbTitles);
+			Win.Add (cbUseLabels);
+			Win.Add (workArea);
+
+			SetupTileView ();
+
+			Application.Top.Add (menu);
+
+			Win.Loaded += () => loaded = true;
+		}
+
+		private void SetupTileView ()
+		{
+			int numberOfViews = GetNumberOfViews ();
+
+			bool titles = cbTitles.Checked;
+			bool border = cbBorder.Checked;
+			bool startHorizontal = cbHorizontal.Checked;
+
+			workArea.RemoveAll ();
+			
+			if (numberOfViews <= 0) {
+				return;
+			}
+
+			var root = CreateTileView (1,startHorizontal ?
+					Terminal.Gui.Graphs.Orientation.Horizontal :
+					Terminal.Gui.Graphs.Orientation.Vertical);
+
+			root.Tiles.ElementAt(0).ContentView.Add (CreateContentControl (1));
+			root.Tiles.ElementAt(0).Title = cbTitles.Checked ? $"View 1" : string.Empty;
+			root.Tiles.ElementAt (1).ContentView.Add (CreateContentControl (2));
+			root.Tiles.ElementAt(1).Title = cbTitles.Checked ? $"View 2" : string.Empty;
+			
+
+			root.Border.BorderStyle = border ? BorderStyle.Rounded : BorderStyle.None;
+
+
+			workArea.Add (root);
+
+			if (numberOfViews == 1) {
+				root.Tiles.ElementAt (1).ContentView.Visible = false;
+			}
+
+			if (numberOfViews > 2) {
+
+				viewsCreated = 2;
+				viewsToCreate = numberOfViews;
+				AddMoreViews (root);
+			}
+
+			if (loaded) {
+				workArea.LayoutSubviews ();
+			}
+		}
+
+		private View CreateContentControl (int number)
+		{
+			return cbUseLabels.Checked ?
+				CreateLabelView (number) :
+				CreateTextView (number);
+		}
+
+		private View CreateLabelView (int number)
+		{
+			return new Label {
+				Width = Dim.Fill (),
+				Height = 1,
+				AutoSize = false,
+				Text = number.ToString ().Repeat (1000),
+				CanFocus = true,
+			};
+		}
+		private View CreateTextView (int number)
+		{
+			return new TextView {
+				Width = Dim.Fill (),
+				Height = Dim.Fill(),
+				Text = number.ToString ().Repeat (1000),
+				AllowsTab = false,
+				//WordWrap = true,  // TODO: This is very slow (like 10s to render with 45 views)
+			};
+		}
+
+		private void AddMoreViews (TileView to)
+		{
+			if (viewsCreated == viewsToCreate) {
+				return;
+			}
+			if (!(to.Tiles.ElementAt(0).ContentView is TileView)) {
+				Split(to,true);
+			}
+
+			if (!(to.Tiles.ElementAt (1).ContentView is TileView)) {
+				Split(to,false);				
+			}
+
+			if (to.Tiles.ElementAt (0).ContentView is TileView && to.Tiles.ElementAt (1).ContentView is TileView) {
+
+				AddMoreViews ((TileView)to.Tiles.ElementAt (0).ContentView);
+				AddMoreViews ((TileView)to.Tiles.ElementAt (1).ContentView);
+			}
+
+		}
+		
+		private void Split(TileView to, bool left)
+		{
+			if (viewsCreated == viewsToCreate) {
+				return;
+			}
+
+			TileView newView;
+			
+			if (left) {
+				to.TrySplitTile(0,2,out newView);
+
+			}
+			else {
+				to.TrySplitTile (1,2,out newView);
+			}
+
+			viewsCreated++;
+
+			// During splitting the old Title will have been migrated to View1 so we only need
+			// to set the Title on View2 (the one that gets our new TextView)
+			newView.Tiles.ElementAt(1).Title = cbTitles.Checked ? $"View {viewsCreated}" : string.Empty;
+
+			// Flip orientation
+			newView.Orientation = to.Orientation == Orientation.Vertical ?
+				Orientation.Horizontal :
+				Orientation.Vertical;
+
+			newView.Tiles.ElementAt (1).ContentView.Add (CreateContentControl(viewsCreated));
+		}
+
+		private TileView CreateTileView (int titleNumber, Orientation orientation)
+		{
+			var toReturn = new TileView {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				// flip the orientation
+				Orientation = orientation
+			};
+
+			toReturn.Tiles.ElementAt(0).Title = cbTitles.Checked ? $"View {titleNumber}" : string.Empty;
+			toReturn.Tiles.ElementAt (1).Title = cbTitles.Checked ? $"View {titleNumber + 1}" : string.Empty;
+
+			return toReturn;
+		}
+
+		private int GetNumberOfViews ()
+		{
+			if (int.TryParse (textField.Text.ToString (), out var views) && views >= 0) {
+
+				return views;
+			} else {
+				return 0;
+			}
+		}
+
+		private void Quit ()
+		{
+			Application.RequestStop ();
+		}
+	}
+}

+ 23 - 29
UICatalog/UICatalog.cs

@@ -1,4 +1,4 @@
-using NStack;
+using NStack;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
@@ -151,9 +151,8 @@ namespace UICatalog {
 			public MenuItem miIsMouseDisabled;
 			public MenuItem miHeightAsBuffer;
 
-			public FrameView LeftPane;
+			public TileView ContentPane;
 			public ListView CategoryListView;
-			public FrameView RightPane;
 			public ListView ScenarioListView;
 
 			public StatusItem Capslock;
@@ -164,7 +163,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)
@@ -200,8 +199,7 @@ namespace UICatalog {
 					}),
 					new StatusItem(Key.F10, "~F10~ Status Bar", () => {
 						StatusBar.Visible = !StatusBar.Visible;
-						LeftPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
-						RightPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
+						ContentPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
 						LayoutSubviews();
 						SetChildNeedsDisplay();
 					}),
@@ -209,17 +207,18 @@ namespace UICatalog {
 					OS
 				};
 
-				LeftPane = new FrameView ("Categories") {
+				ContentPane = new TileView () {
 					X = 0,
 					Y = 1, // for menu
-					Width = 25,
+					Width = Dim.Fill (),
 					Height = Dim.Fill (1),
 					CanFocus = true,
-					Shortcut = Key.CtrlMask | Key.C
+					Shortcut = Key.CtrlMask | Key.C,
 				};
-				LeftPane.Title = $"{LeftPane.Title} ({LeftPane.ShortcutTag})";
-				LeftPane.ShortcutAction = () => LeftPane.SetFocus ();
-
+				ContentPane.Border.BorderStyle = BorderStyle.Single;
+				ContentPane.SetSplitterPos (0, 25);
+				ContentPane.ShortcutAction = () => ContentPane.SetFocus ();
+					
 				CategoryListView = new ListView (_categories) {
 					X = 0,
 					Y = 0,
@@ -229,21 +228,13 @@ namespace UICatalog {
 					CanFocus = true,
 				};
 				CategoryListView.OpenSelectedItem += (a) => {
-					RightPane.SetFocus ();
+					ScenarioListView.SetFocus ();
 				};
 				CategoryListView.SelectedItemChanged += CategoryListView_SelectedChanged;
-				LeftPane.Add (CategoryListView);
 
-				RightPane = new FrameView ("Scenarios") {
-					X = 25,
-					Y = 1, // for menu
-					Width = Dim.Fill (),
-					Height = Dim.Fill (1),
-					CanFocus = true,
-					Shortcut = Key.CtrlMask | Key.S
-				};
-				RightPane.Title = $"{RightPane.Title} ({RightPane.ShortcutTag})";
-				RightPane.ShortcutAction = () => RightPane.SetFocus ();
+				ContentPane.Tiles.ElementAt(0).Title = "Categories";
+				ContentPane.Tiles.ElementAt (0).MinSize = 2;
+				ContentPane.Tiles.ElementAt (0).ContentView.Add (CategoryListView);
 
 				ScenarioListView = new ListView () {
 					X = 0,
@@ -255,12 +246,15 @@ namespace UICatalog {
 				};
 
 				ScenarioListView.OpenSelectedItem += ScenarioListView_OpenSelectedItem;
-				RightPane.Add (ScenarioListView);
+
+				ContentPane.Tiles.ElementAt (1).Title = "Scenarios";
+				ContentPane.Tiles.ElementAt (1).ContentView.Add (ScenarioListView);
+				ContentPane.Tiles.ElementAt (1).MinSize = 2;
 
 				KeyDown += KeyDownHandler;
 				Add (MenuBar);
-				Add (LeftPane);
-				Add (RightPane);
+				Add (ContentPane);
+
 				Add (StatusBar);
 
 				Loaded += LoadedHandler;
@@ -269,7 +263,7 @@ namespace UICatalog {
 				CategoryListView.SelectedItem = _cachedCategoryIndex;
 				ScenarioListView.SelectedItem = _cachedScenarioIndex;
 			}
-
+ 
 			void LoadedHandler ()
 			{
 				Application.HeightAsBuffer = _heightAsBuffer;
@@ -288,7 +282,7 @@ namespace UICatalog {
 					_isFirstRunning = false;
 				}
 				if (!_isFirstRunning) {
-					RightPane.SetFocus ();
+					ScenarioListView.SetFocus ();
 				}
 				Loaded -= LoadedHandler;
 			}

+ 4 - 4
UICatalog/UICatalog.csproj

@@ -7,10 +7,10 @@
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- In the source tree the version will always be 2.0 for all projects. -->
     <!-- Do not modify these. -->
-    <AssemblyVersion>2.0</AssemblyVersion>
-    <FileVersion>2.0</FileVersion>
-    <Version>2.0</Version>
-    <InformationalVersion>2.0</InformationalVersion>
+    <AssemblyVersion>1.0</AssemblyVersion>
+    <FileVersion>1.0</FileVersion>
+    <Version>1.0</Version>
+    <InformationalVersion>1.0</InformationalVersion>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
     <DefineConstants>TRACE</DefineConstants>

+ 0 - 1
UnitTests/Application/ApplicationTests.cs

@@ -24,7 +24,6 @@ namespace Terminal.Gui.ApplicationTests {
 			Assert.Null (Application.Driver);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Current);
-			Assert.Throws<ArgumentNullException> (() => Application.HeightAsBuffer == true);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);

+ 326 - 0
UnitTests/Core/LineCanvasTests.cs

@@ -0,0 +1,326 @@
+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);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_ScreenCoords ()
+		{
+			var x = 3;
+			var y = 5;
+			var screenCanvas = new LineCanvas ();
+
+			// outer box
+			screenCanvas.AddLine (new Point (x, y), 9, Orientation.Horizontal, BorderStyle.Single);
+			screenCanvas.AddLine (new Point (x + 9, y + 0), 4, Orientation.Vertical, BorderStyle.Single);
+			screenCanvas.AddLine (new Point (x + 9, y + 4), -9, Orientation.Horizontal, BorderStyle.Single);
+			screenCanvas.AddLine (new Point (x + 0, y + 4), -4, Orientation.Vertical, BorderStyle.Single);
+
+			screenCanvas.AddLine (new Point (x + 5, y + 0), 4, Orientation.Vertical, BorderStyle.Single);
+			screenCanvas.AddLine (new Point (x + 0, y + 2), 9, Orientation.Horizontal, BorderStyle.Single);
+
+			screenCanvas.Draw (null, new Rect (x, y, 10, 5));
+
+			string looksLike =
+@$"    
+{new string ('\n', y)}{new string (' ', x)}┌────┬───┐
+{new string (' ', x)}│    │   │
+{new string (' ', x)}├────┼───┤
+{new string (' ', x)}│    │   │
+{new string (' ', x)}└────┴───┘";
+			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);
+		}
+
+		private View GetCanvas (out LineCanvas canvas, int x = 0, int y = 0)
+		{
+			var v = new View {
+				Width = 10,
+				Height = 5,
+				Bounds = new Rect (x, y, 10, 5)
+			};
+
+			var canvasCopy = canvas = new LineCanvas ();
+			v.DrawContentComplete += (r) => canvasCopy.Draw (v, v.Bounds);
+
+			return v;
+		}
+	}
+}

+ 69 - 5
UnitTests/Drivers/AttributeTests.cs

@@ -85,7 +85,34 @@ namespace Terminal.Gui.DriverTests {
 		}
 
 		[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.DriverTests {
 			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.DriverTests {
 			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.DriverTests {
 		}
 
 		[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.DriverTests {
 			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.Invalid);
+			Assert.False (attr.HasValidColors);
+
+			attr = new Attribute (Color.Invalid, Color.Green);
+			Assert.False (attr.HasValidColors);
+
+			attr = new Attribute (Color.Invalid, Color.Invalid);
+			Assert.False (attr.HasValidColors);
+		}
 	}
 }

+ 9 - 0
UnitTests/Drivers/ColorTests.cs

@@ -35,5 +35,14 @@ namespace Terminal.Gui.DriverTests {
 			Application.Shutdown ();
 		}
 
+		[Fact, AutoInitShutdown]
+		public void ColorScheme_New ()
+		{
+			var scheme = new ColorScheme ();
+			var lbl = new Label ();
+			lbl.ColorScheme = scheme;
+			lbl.Redraw (lbl.Bounds);
+		}
+
 	}
 }

+ 118 - 111
UnitTests/Drivers/ConsoleDriverTests.cs

@@ -440,6 +440,8 @@ namespace Terminal.Gui.DriverTests {
 			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
@@ -485,122 +487,127 @@ namespace Terminal.Gui.DriverTests {
 				if (iterations == 0) Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control);
 			};
 
-			Application.Run ();
-			Application.Shutdown ();
+
+			lock (packetLock) {
+				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 };
+				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 ();

+ 1 - 0
UnitTests/Drivers/KeyTests.cs

@@ -1,4 +1,5 @@
 using System;
+using Terminal.Gui;
 using Xunit;
 
 namespace Terminal.Gui.DriverTests {

+ 5 - 2
UnitTests/TestHelpers.cs

@@ -120,8 +120,11 @@ class TestHelpers {
 			expectedLook = expectedLook.Replace ("\r\n", "\n");
 			actualLook = actualLook.Replace ("\r\n", "\n");
 
-			output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
-			output?.WriteLine ("But Was:" + Environment.NewLine + actualLook);
+			// If test is about to fail show user what things looked like
+			if(!string.Equals(expectedLook,actualLook)) {
+				output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
+				output?.WriteLine ("But Was:" + Environment.NewLine + actualLook);
+			}
 
 			Assert.Equal (expectedLook, actualLook);
 		}

+ 42 - 5
UnitTests/TopLevels/ToplevelTests.cs

@@ -696,7 +696,7 @@ namespace Terminal.Gui.TopLevelTests {
 					((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,7 +712,7 @@ namespace Terminal.Gui.TopLevelTests {
 │                                      │
 └──────────────────────────────────────┘
  CTRL-N New                             ", output);
-else if (iterations == 2) {
+				else if (iterations == 2) {
 					Assert.Null (Application.MouseGrabView);
 					// Grab the mouse
 					ReflectionTools.InvokePrivate (
@@ -815,8 +815,8 @@ else if (iterations == 2) {
 
 					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 ();
@@ -956,7 +956,7 @@ else if (iterations == 9) 					Application.RequestStop ();
 
 					Assert.Null (Application.MouseGrabView);
 
-				} else if (iterations == 8) 					Application.RequestStop ();
+				} else if (iterations == 8) Application.RequestStop ();
 			};
 
 			Application.Run ();
@@ -974,5 +974,42 @@ else if (iterations == 9) 					Application.RequestStop ();
 			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);
+		}
 	}
 }

+ 86 - 0
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.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.ViewTests {
 			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);
+		}
 	}
 }

+ 2 - 2
UnitTests/Views/TabViewTests.cs

@@ -719,7 +719,7 @@ namespace Terminal.Gui.ViewTests {
 
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────────┐    
-│Les Misrables│    
+│Les Misérables│    
 ◄              └───┐
 │hi2               │
 └──────────────────┘", output);
@@ -756,7 +756,7 @@ namespace Terminal.Gui.ViewTests {
 ┌──────────────────┐
 │hi2               │
 ◄              ┌───┘
-│Les Misrables│    
+│Les Misérables│    
 └──────────────┘    ", output);
 		}
 

+ 118 - 1
UnitTests/Views/TextFieldTests.cs

@@ -1,9 +1,18 @@
-using System;
+using NStack;
+using System;
+using System.Globalization;
 using System.Reflection;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewTests {
 	public class TextFieldTests {
+		readonly ITestOutputHelper output;
+
+		public TextFieldTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
 
 		// This class enables test functions annotated with the [InitShutdown] attribute
 		// to have a function called before the test function is called and after.
@@ -1177,6 +1186,21 @@ namespace Terminal.Gui.ViewTests {
 			Assert.Equal ("-", newText);
 			Assert.Equal ("-1", oldText);
 			Assert.Equal ("-", tf.Text.ToString ());
+
+			// Delete word with accented char
+			tf.Text = "Les Misérables movie.";
+			Assert.True (tf.MouseEvent (new MouseEvent {
+				X = 7,
+				Y = 1,
+				Flags = MouseFlags.Button1DoubleClicked,
+				View = tf
+			}));
+			Assert.Equal ("Misérables ", tf.SelectedText);
+			Assert.Equal (11, tf.SelectedLength);
+			Assert.True (tf.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers ())));
+			Assert.Equal ("Les movie.", newText);
+			Assert.Equal ("Les Misérables movie.", oldText);
+			Assert.Equal ("Les movie.", tf.Text.ToString ());
 		}
 
 		[Fact]
@@ -1323,5 +1347,98 @@ namespace Terminal.Gui.ViewTests {
 			Assert.Equal (0, tf.SelectedLength);
 			Assert.Null (tf.SelectedText);
 		}
+
+
+		[Fact]
+		public void WordBackward_WordForward_SelectedText_With_Accent ()
+		{
+			string text = "Les Misérables movie.";
+			var tf = new TextField (text) { Width = 30 };
+
+			Assert.Equal (21, text.Length);
+			Assert.Equal (21, tf.Text.RuneCount);
+			Assert.Equal (21, tf.Text.ConsoleWidth);
+
+			var runes = tf.Text.ToRuneList ();
+			Assert.Equal (21, runes.Count);
+			Assert.Equal (22, tf.Text.Length);
+
+			for (int i = 0; i < runes.Count; i++) {
+				var cs = text [i];
+				var cus = (char)runes [i];
+				Assert.Equal (cs, cus);
+			}
+
+			var idx = 15;
+			Assert.Equal ('m', text [idx]);
+			Assert.Equal ('m', (char)runes [idx]);
+			Assert.Equal ("m", ustring.Make (runes [idx]));
+
+			Assert.True (tf.MouseEvent (new MouseEvent {
+				X = idx,
+				Y = 1,
+				Flags = MouseFlags.Button1DoubleClicked,
+				View = tf
+			}));
+			Assert.Equal ("movie.", tf.SelectedText);
+
+			Assert.True (tf.MouseEvent (new MouseEvent {
+				X = idx + 1,
+				Y = 1,
+				Flags = MouseFlags.Button1DoubleClicked,
+				View = tf
+			}));
+			Assert.Equal ("movie.", tf.SelectedText);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Words_With_Accents_Incorrect_Order_Will_Result_With_Wrong_Accent_Place ()
+		{
+			var tf = new TextField ("Les Misérables") { Width = 30 };
+			var top = Application.Top;
+			top.Add (tf);
+			Application.Begin (top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+Les Misérables", output);
+
+			tf.Text = "Les Mise" + char.ConvertFromUtf32 (int.Parse ("0301", NumberStyles.HexNumber)) + "rables";
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+Les Misérables", output);
+
+			// incorrect order will result with a wrong accent place
+			tf.Text = "Les Mis" + char.ConvertFromUtf32 (int.Parse ("0301", NumberStyles.HexNumber)) + "erables";
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+Les Miśerables", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Accented_Letter_With_Three_Combining_Unicode_Chars ()
+		{
+			var tf = new TextField ("ắ") { Width = 3 };
+			var top = Application.Top;
+			top.Add (tf);
+			Application.Begin (top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+ắ", output);
+
+			tf.Text = "\u1eaf";
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+ắ", output);
+
+			tf.Text = "\u0103\u0301";
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+ắ", output);
+
+			tf.Text = "\u0061\u0306\u0301";
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+ắ", output);
+		}
 	}
 }

+ 2239 - 0
UnitTests/Views/TileViewTests.cs

@@ -0,0 +1,2239 @@
+using System;
+using System.Linq;
+using Terminal.Gui;
+using Terminal.Gui.Graphs;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests {
+	public class TileViewTests {
+
+		readonly ITestOutputHelper output;
+
+		public TileViewTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Vertical ()
+		{
+			var tileView = Get11By3TileView (out var line);
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+11111│22222
+11111│22222
+     │     ";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Keyboard movement on splitter should have no effect if it is not focused
+			line.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Vertical_WithBorder ()
+		{
+			var tileView = Get11By3TileView (out var line, true);
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+┌────┬────┐
+│1111│2222│
+└────┴────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Keyboard movement on splitter should have no effect if it is not focused
+			line.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Vertical_Focused ()
+		{
+			var tileView = Get11By3TileView (out var line);
+			SetInputFocusLine (tileView);
+
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+11111│22222
+11111◊22222
+     │     ";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Now while focused move the splitter 1 unit right
+			line.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+111111│2222
+111111◊2222
+      │     ";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			// and 2 to the left
+			line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+1111│222222
+1111◊222222
+    │     ";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Vertical_Focused_WithBorder ()
+		{
+			var tileView = Get11By3TileView (out var line, true);
+			SetInputFocusLine (tileView);
+
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+┌────┬────┐
+│1111◊2222│
+└────┴────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Now while focused move the splitter 1 unit right
+			line.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌─────┬───┐
+│11111◊222│
+└─────┴───┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			// and 2 to the left
+			line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌───┬─────┐
+│111◊22222│
+└───┴─────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Vertical_Focused_50PercentSplit ()
+		{
+			var tileView = Get11By3TileView (out var line);
+			SetInputFocusLine (tileView);
+			tileView.SetSplitterPos (0, Pos.Percent (50));
+			Assert.IsType<Pos.PosFactor> (tileView.SplitterDistances.ElementAt (0));
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+11111│22222
+11111◊22222
+     │     ";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Now while focused move the splitter 1 unit right
+			line.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+111111│2222
+111111◊2222
+      │     ";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Even when moving the splitter location it should stay a Percentage based one
+			Assert.IsType<Pos.PosFactor> (tileView.SplitterDistances.ElementAt (0));
+
+
+			// and 2 to the left
+			line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+1111│222222
+1111◊222222
+    │     ";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+			// Even when moving the splitter location it should stay a Percentage based one
+			Assert.IsType<Pos.PosFactor> (tileView.SplitterDistances.ElementAt (0));
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Horizontal ()
+		{
+			var tileView = Get11By3TileView (out var line);
+			tileView.Orientation = Terminal.Gui.Graphs.Orientation.Horizontal;
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"    
+11111111111
+───────────
+22222222222";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Keyboard movement on splitter should have no effect if it is not focused
+			line.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Vertical_View1MinSize_Absolute ()
+		{
+			var tileView = Get11By3TileView (out var line);
+			SetInputFocusLine (tileView);
+			tileView.Tiles.ElementAt (0).MinSize = 6;
+
+			// distance is too small (below 6)
+			Assert.False (tileView.SetSplitterPos (0, 2));
+
+			// Should stay where it was originally at (50%)
+			Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0));
+
+			tileView.Redraw (tileView.Bounds);
+
+			// so should ignore the 2 distance and stick to 6
+			string looksLike =
+@"
+11111│22222
+11111◊22222
+     │     ";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Keyboard movement on splitter should have no effect because it
+			// would take us below the minimum splitter size
+			line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// but we can continue to move the splitter right if we want
+			line.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+111111│2222
+111111◊2222
+      │     ";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Vertical_View1MinSize_Absolute_WithBorder ()
+		{
+			var tileView = Get11By3TileView (out var line, true);
+			SetInputFocusLine (tileView);
+			tileView.Tiles.ElementAt (0).MinSize = 5;
+
+			// distance is too small (below 5)
+			Assert.False (tileView.SetSplitterPos (0, 2));
+
+			// Should stay where it was originally at (50%)
+			Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0));
+
+			tileView.Redraw (tileView.Bounds);
+
+			// so should ignore the 2 distance and stick to 5
+			string looksLike =
+@"
+┌────┬────┐
+│1111◊2222│
+└────┴────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Keyboard movement on splitter should have no effect because it
+			// would take us below the minimum splitter size
+			line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// but we can continue to move the splitter right if we want
+			line.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌─────┬───┐
+│11111◊222│
+└─────┴───┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Vertical_View2MinSize_Absolute ()
+		{
+			var tileView = Get11By3TileView (out var line);
+			SetInputFocusLine (tileView);
+			tileView.Tiles.ElementAt (1).MinSize = 6;
+
+			// distance leaves too little space for view2 (less than 6 would remain)
+			Assert.False (tileView.SetSplitterPos (0, 8));
+
+			//  Should stay where it was originally at (50%)
+			Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0));
+
+			tileView.Redraw (tileView.Bounds);
+
+			// so should ignore the 2 distance and stick to 6
+			string looksLike =
+@"
+11111│22222
+11111◊22222
+     │     ";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Keyboard movement on splitter should have no effect because it
+			// would take us below the minimum splitter size
+			line.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// but we can continue to move the splitter left if we want
+			line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+1111│222222
+1111◊222222
+    │    ";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Vertical_View2MinSize_Absolute_WithBorder ()
+		{
+			var tileView = Get11By3TileView (out var line, true);
+			SetInputFocusLine (tileView);
+			tileView.Tiles.ElementAt (1).MinSize = 5;
+
+			// distance leaves too little space for view2 (less than 5 would remain)
+			Assert.False (tileView.SetSplitterPos (0, 8));
+
+			//  Should stay where it was originally at (50%)
+			Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0));
+
+			tileView.Redraw (tileView.Bounds);
+
+			// so should ignore the 2 distance and stick to 6
+			string looksLike =
+@"
+┌────┬────┐
+│1111◊2222│
+└────┴────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Keyboard movement on splitter should have no effect because it
+			// would take us below the minimum splitter size
+			line.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// but we can continue to move the splitter left if we want
+			line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			tileView.SetNeedsDisplay ();
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌───┬─────┐
+│111◊22222│
+└───┴─────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_InsertPanelAtStart ()
+		{
+			var tileView = Get11By3TileView (out var line, true);
+			SetInputFocusLine (tileView);
+
+			tileView.InsertTile (0);
+
+			tileView.Redraw (tileView.Bounds);
+
+			// so should ignore the 2 distance and stick to 6
+			string looksLike =
+@"
+┌──┬───┬──┐
+│  │111│22│
+└──┴───┴──┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_InsertPanelMiddle ()
+		{
+			var tileView = Get11By3TileView (out var line, true);
+			SetInputFocusLine (tileView);
+
+			tileView.InsertTile (1);
+
+			tileView.Redraw (tileView.Bounds);
+
+			// so should ignore the 2 distance and stick to 6
+			string looksLike =
+@"
+┌──┬───┬──┐
+│11│   │22│
+└──┴───┴──┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_InsertPanelAtEnd ()
+		{
+			var tileView = Get11By3TileView (out var line, true);
+			SetInputFocusLine (tileView);
+
+			tileView.InsertTile (2);
+
+			tileView.Redraw (tileView.Bounds);
+
+			// so should ignore the 2 distance and stick to 6
+			string looksLike =
+@"
+┌──┬───┬──┐
+│11│222│  │
+└──┴───┴──┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Horizontal_Focused ()
+		{
+			var tileView = Get11By3TileView (out var line);
+
+			tileView.Orientation = Terminal.Gui.Graphs.Orientation.Horizontal;
+			SetInputFocusLine (tileView);
+
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"    
+11111111111
+─────◊─────
+22222222222";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Now move splitter line down
+			line.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()));
+
+			tileView.Redraw (tileView.Bounds);
+			looksLike =
+@"    
+11111111111
+11111111111
+─────◊─────";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// And 2 up
+			line.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ()));
+			line.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ()));
+			tileView.SetNeedsDisplay();
+			tileView.Redraw (tileView.Bounds);
+			looksLike =
+@"    
+─────◊─────
+22222222222
+22222222222";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_Horizontal_View1MinSize_Absolute ()
+		{
+			var tileView = Get11By3TileView (out var line);
+
+			tileView.Orientation = Terminal.Gui.Graphs.Orientation.Horizontal;
+			SetInputFocusLine (tileView);
+			tileView.Tiles.ElementAt (0).MinSize = 1;
+
+			// 0 should not be allowed because it brings us below minimum size of View1
+			Assert.False (tileView.SetSplitterPos (0, 0));
+			// position should remain where it was, at 50%
+			Assert.Equal (Pos.Percent (50f), tileView.SplitterDistances.ElementAt (0));
+
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"    
+11111111111
+─────◊─────
+22222222222";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Now move splitter line down (allowed
+			line.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()));
+			tileView.Redraw (tileView.Bounds);
+			looksLike =
+@"    
+11111111111
+11111111111
+─────◊─────";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// And up 2 (only 1 is allowed because of minimum size of 1 on view1)
+			line.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ()));
+			line.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ()));
+
+			tileView.SetNeedsDisplay();
+			tileView.Redraw (tileView.Bounds);
+			looksLike =
+@"    
+11111111111
+─────◊─────
+22222222222";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestTileView_CannotSetSplitterPosToFuncEtc ()
+		{
+			var tileView = Get11By3TileView ();
+
+			var ex = Assert.Throws<ArgumentException> (() => tileView.SetSplitterPos (0, Pos.Right (tileView)));
+			Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosCombine", ex.Message);
+
+
+			ex = Assert.Throws<ArgumentException> (() => tileView.SetSplitterPos (0, Pos.Function (() => 1)));
+			Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosFunc", ex.Message);
+
+			// Also not allowed because this results in a PosCombine
+			ex = Assert.Throws<ArgumentException> (() => tileView.SetSplitterPos (0, Pos.Percent (50) - 1));
+			Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosCombine", ex.Message);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestNestedContainer2LeftAnd1Right_RendersNicely ()
+		{
+			var tileView = GetNestedContainer2Left1Right (false);
+
+			Assert.Equal (20, tileView.Frame.Width);
+			Assert.Equal (10, tileView.Tiles.ElementAt (0).ContentView.Frame.Width);
+			Assert.Equal (9, tileView.Tiles.ElementAt (1).ContentView.Frame.Width);
+
+			Assert.IsType<TileView> (tileView.Tiles.ElementAt (0).ContentView);
+			var left = (TileView)tileView.Tiles.ElementAt (0).ContentView;
+			Assert.Same (left.SuperView, tileView);
+
+
+			Assert.Equal (2, left.Tiles.ElementAt (0).ContentView.Subviews.Count);
+			Assert.IsType<Label> (left.Tiles.ElementAt (0).ContentView.Subviews [0]);
+			Assert.IsType<Label> (left.Tiles.ElementAt (0).ContentView.Subviews [1]);
+			var onesTop = (Label)left.Tiles.ElementAt (0).ContentView.Subviews [0];
+			var onesBottom = (Label)left.Tiles.ElementAt (0).ContentView.Subviews [1];
+
+			Assert.Same (left.Tiles.ElementAt (0).ContentView, onesTop.SuperView);
+			Assert.Same (left.Tiles.ElementAt (0).ContentView, onesBottom.SuperView);
+
+			Assert.Equal (10, onesTop.Frame.Width);
+			Assert.Equal (10, onesBottom.Frame.Width);
+
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"    
+1111111111│222222222
+1111111111│222222222
+          │
+          │
+          │
+──────────┤
+          │
+          │
+          │
+          │";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+		}
+
+
+		[Fact, AutoInitShutdown]
+		public void TestNestedContainer3RightAnd1Down_RendersNicely ()
+		{
+			var tileView = GetNestedContainer3Right1Down (false);
+
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+111111│222222│333333
+111111│222222│333333
+111111│222222│333333
+111111│222222│333333
+111111│222222│333333
+111111│222222├──────
+111111│222222│444444
+111111│222222│444444
+111111│222222│444444
+111111│222222│444444
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// It looks good but lets double check the measurements incase
+			// anything is sticking out but drawn over
+
+			// 3 panels + 2 splitters
+			Assert.Equal (5, tileView.Subviews.Count);
+
+
+			// Check X and Widths of Tiles
+			Assert.Equal (0, tileView.Tiles.ElementAt (0).ContentView.Frame.X);
+			Assert.Equal (6, tileView.Tiles.ElementAt (0).ContentView.Frame.Width);
+
+			Assert.Equal (7, tileView.Tiles.ElementAt (1).ContentView.Frame.X);
+			Assert.Equal (6, tileView.Tiles.ElementAt (1).ContentView.Frame.Width);
+
+			Assert.Equal (14, tileView.Tiles.ElementAt (2).ContentView.Frame.X);
+			Assert.Equal (6, tileView.Tiles.ElementAt (2).ContentView.Frame.Width);
+
+
+			// Check Y and Heights of Tiles
+			Assert.Equal (0, tileView.Tiles.ElementAt (0).ContentView.Frame.Y);
+			Assert.Equal (10, tileView.Tiles.ElementAt (0).ContentView.Frame.Height);
+
+			Assert.Equal (0, tileView.Tiles.ElementAt (1).ContentView.Frame.Y);
+			Assert.Equal (10, tileView.Tiles.ElementAt (1).ContentView.Frame.Height);
+
+			Assert.Equal (0, tileView.Tiles.ElementAt (2).ContentView.Frame.Y);
+			Assert.Equal (10, tileView.Tiles.ElementAt (2).ContentView.Frame.Height);
+
+			// Check Sub containers in last panel
+			var subSplit = (TileView)tileView.Tiles.ElementAt (2).ContentView;
+			Assert.Equal (0, subSplit.Tiles.ElementAt (0).ContentView.Frame.X);
+			Assert.Equal (6, subSplit.Tiles.ElementAt (0).ContentView.Frame.Width);
+			Assert.Equal (0, subSplit.Tiles.ElementAt (0).ContentView.Frame.Y);
+			Assert.Equal (5, subSplit.Tiles.ElementAt (0).ContentView.Frame.Height);
+			Assert.IsType<TextView> (subSplit.Tiles.ElementAt (0).ContentView.Subviews.Single ());
+
+			Assert.Equal (0, subSplit.Tiles.ElementAt (1).ContentView.Frame.X);
+			Assert.Equal (6, subSplit.Tiles.ElementAt (1).ContentView.Frame.Width);
+			Assert.Equal (6, subSplit.Tiles.ElementAt (1).ContentView.Frame.Y);
+			Assert.Equal (4, subSplit.Tiles.ElementAt (1).ContentView.Frame.Height);
+			Assert.IsType<TextView> (subSplit.Tiles.ElementAt (1).ContentView.Subviews.Single ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestNestedContainer3RightAnd1Down_WithBorder_RendersNicely ()
+		{
+			var tileView = GetNestedContainer3Right1Down (true);
+
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+┌─────┬──────┬─────┐
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222├─────┤
+│11111│222222│44444│
+│11111│222222│44444│
+│11111│222222│44444│
+└─────┴──────┴─────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// It looks good but lets double check the measurements incase
+			// anything is sticking out but drawn over
+
+			// 3 panels + 2 splitters
+			Assert.Equal (5, tileView.Subviews.Count);
+
+			// Check X and Widths of Tiles
+			Assert.Equal (1, tileView.Tiles.ElementAt (0).ContentView.Frame.X);
+			Assert.Equal (5, tileView.Tiles.ElementAt (0).ContentView.Frame.Width);
+
+			Assert.Equal (7, tileView.Tiles.ElementAt (1).ContentView.Frame.X);
+			Assert.Equal (6, tileView.Tiles.ElementAt (1).ContentView.Frame.Width);
+
+			Assert.Equal (14, tileView.Tiles.ElementAt (2).ContentView.Frame.X);
+			Assert.Equal (5, tileView.Tiles.ElementAt (2).ContentView.Frame.Width);
+
+
+			// Check Y and Heights of Tiles
+			Assert.Equal (1, tileView.Tiles.ElementAt (0).ContentView.Frame.Y);
+			Assert.Equal (8, tileView.Tiles.ElementAt (0).ContentView.Frame.Height);
+
+			Assert.Equal (1, tileView.Tiles.ElementAt (1).ContentView.Frame.Y);
+			Assert.Equal (8, tileView.Tiles.ElementAt (1).ContentView.Frame.Height);
+
+			Assert.Equal (1, tileView.Tiles.ElementAt (2).ContentView.Frame.Y);
+			Assert.Equal (8, tileView.Tiles.ElementAt (2).ContentView.Frame.Height);
+
+			// Check Sub containers in last panel
+			var subSplit = (TileView)tileView.Tiles.ElementAt (2).ContentView;
+			Assert.Equal (0, subSplit.Tiles.ElementAt (0).ContentView.Frame.X);
+			Assert.Equal (5, subSplit.Tiles.ElementAt (0).ContentView.Frame.Width);
+			Assert.Equal (0, subSplit.Tiles.ElementAt (0).ContentView.Frame.Y);
+			Assert.Equal (4, subSplit.Tiles.ElementAt (0).ContentView.Frame.Height);
+			Assert.IsType<TextView> (subSplit.Tiles.ElementAt (0).ContentView.Subviews.Single ());
+
+			Assert.Equal (0, subSplit.Tiles.ElementAt (1).ContentView.Frame.X);
+			Assert.Equal (5, subSplit.Tiles.ElementAt (1).ContentView.Frame.Width);
+			Assert.Equal (5, subSplit.Tiles.ElementAt (1).ContentView.Frame.Y);
+			Assert.Equal (3, subSplit.Tiles.ElementAt (1).ContentView.Frame.Height);
+			Assert.IsType<TextView> (subSplit.Tiles.ElementAt (1).ContentView.Subviews.Single ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestNestedContainer3RightAnd1Down_WithTitledBorder_RendersNicely ()
+		{
+			var tileView = GetNestedContainer3Right1Down (true, true);
+
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+┌ T1 ─┬ T2 ──┬ T3 ─┐
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222├ T4 ─┤
+│11111│222222│44444│
+│11111│222222│44444│
+│11111│222222│44444│
+└─────┴──────┴─────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestNestedContainer3RightAnd1Down_WithBorder_RemovingTiles ()
+		{
+			var tileView = GetNestedContainer3Right1Down (true);
+
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+┌─────┬──────┬─────┐
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222├─────┤
+│11111│222222│44444│
+│11111│222222│44444│
+│11111│222222│44444│
+└─────┴──────┴─────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			var toRemove = tileView.Tiles.ElementAt (1);
+			var removed = tileView.RemoveTile (1);
+			Assert.Same (toRemove, removed);
+			Assert.DoesNotContain (removed, tileView.Tiles);
+
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌─────────┬────────┐
+│111111111│33333333│
+│111111111│33333333│
+│111111111│33333333│
+│111111111│33333333│
+│111111111├────────┤
+│111111111│44444444│
+│111111111│44444444│
+│111111111│44444444│
+└─────────┴────────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// cannot remove at this index because there is only one horizontal tile left
+			Assert.Null (tileView.RemoveTile (2));
+			tileView.RemoveTile (0);
+
+
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌──────────────────┐
+│333333333333333333│
+│333333333333333333│
+│333333333333333333│
+│333333333333333333│
+├──────────────────┤
+│444444444444444444│
+│444444444444444444│
+│444444444444444444│
+└──────────────────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			Assert.NotNull (tileView.RemoveTile (0));
+
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌──────────────────┐
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+└──────────────────┘";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// cannot remove
+			Assert.Null (tileView.RemoveTile (0));
+		}
+
+		[Theory, AutoInitShutdown]
+		[InlineData (true)]
+		[InlineData (false)]
+		public void TestTileView_IndexOf (bool recursive)
+		{
+			var tv = new TileView ();
+			var lbl1 = new Label ();
+			var lbl2 = new Label ();
+			var frame = new FrameView ();
+			var sub = new Label ();
+			frame.Add (sub);
+
+			// IndexOf returns -1 when view not found
+			Assert.Equal (-1, tv.IndexOf (lbl1, recursive));
+			Assert.Equal (-1, tv.IndexOf (lbl2, recursive));
+
+			// IndexOf supports looking for Tile.View
+			Assert.Equal (0, tv.IndexOf (tv.Tiles.ElementAt (0).ContentView, recursive));
+			Assert.Equal (1, tv.IndexOf (tv.Tiles.ElementAt (1).ContentView, recursive));
+
+			// IndexOf supports looking for Tile.View.Subviews
+			tv.Tiles.ElementAt (0).ContentView.Add (lbl1);
+			Assert.Equal (0, tv.IndexOf (lbl1, recursive));
+
+			tv.Tiles.ElementAt (1).ContentView.Add (lbl2);
+			Assert.Equal (1, tv.IndexOf (lbl2, recursive));
+
+			// IndexOf supports looking deep into subviews only when
+			// the recursive true value is passed
+			tv.Tiles.ElementAt (1).ContentView.Add (frame);
+			if (recursive) {
+				Assert.Equal (1, tv.IndexOf (sub, recursive));
+			} else {
+				Assert.Equal (-1, tv.IndexOf (sub, recursive));
+			}
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestNestedRoots_BothRoots_BothCanHaveBorders ()
+		{
+			var tv = new TileView { 
+				Width = 10, 
+				Height = 5, 
+				ColorScheme = new ColorScheme (), 
+				Border = new Border () { BorderStyle = BorderStyle.Single } 
+			};
+			var tv2 = new TileView {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				ColorScheme = new ColorScheme (),
+				Border = new Border () { BorderStyle = BorderStyle.Single },
+				Orientation = Orientation.Horizontal
+			};
+
+			Assert.True (tv.IsRootTileView ());
+			tv.Tiles.ElementAt (1).ContentView.Add (tv2);
+
+			Application.Top.Add (tv);
+			tv.BeginInit ();
+			tv.EndInit ();
+			tv.LayoutSubviews ();
+
+			tv.LayoutSubviews ();
+			tv.Tiles.ElementAt (1).ContentView.LayoutSubviews ();
+			tv2.LayoutSubviews ();
+
+			// tv2 is still considered a root because 
+			// it was manually created by API user. That
+			// means it will not have its lines joined to
+			// parents and it is permitted to have a border
+			Assert.True (tv2.IsRootTileView ());
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+┌────┬───┐
+│    │┌─┐│
+│    │├─┤│
+│    │└─┘│
+└────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter1 ()
+		{
+			var tv = Get5x1TilesView ();
+
+			tv.Tiles.ElementAt (0).MinSize = int.MaxValue;
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+┌────┬────┬────┬────┬───┐
+│1111│2222│3333│4444│555│
+│    │    │    │    │   │
+└────┴────┴────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 0; x <= 5; x++) {
+				// All these values would result in tile 0 getting smaller
+				// so are not allowed (tile[0] has a min size of Int.Max)
+				Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+			for (int x = 6; x < 10; x++) {
+				// All these values would result in tile 0 getting bigger
+				// so are allowed
+				Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 10; x < 100; x++) {
+				// These values would result in the first splitter moving past
+				// the second splitter so are not allowed
+				Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌────────┬┬────┬────┬───┐
+│11111111││3333│4444│555│
+│        ││    │    │   │
+└────────┴┴────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter1_NoBorder ()
+		{
+			var tv = Get5x1TilesView (false);
+
+
+			tv.Tiles.ElementAt (0).MinSize = int.MaxValue;
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+11111│2222│3333│4444│5555
+     │    │    │    │
+     │    │    │    │
+     │    │    │    │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 0; x <= 5; x++) {
+				// All these values would result in tile 0 getting smaller
+				// so are not allowed (tile[0] has a min size of Int.Max)
+				Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+			for (int x = 6; x < 10; x++) {
+				// All these values would result in tile 0 getting bigger
+				// so are allowed
+				Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 10; x < 100; x++) {
+				// These values would result in the first splitter moving past
+				// the second splitter so are not allowed
+				Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+111111111││3333│4444│5555
+         ││    │    │
+         ││    │    │
+         ││    │    │
+
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter1_CannotCrossBorder ()
+		{
+			var tv = Get5x1TilesView ();
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+┌────┬────┬────┬────┬───┐
+│1111│2222│3333│4444│555│
+│    │    │    │    │   │
+└────┴────┴────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 5; x > 0; x--) {
+				Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+			Assert.False (tv.SetSplitterPos (0, 0));
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌┬────────┬────┬────┬───┐
+││22222222│3333│4444│555│
+││        │    │    │   │
+└┴────────┴────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 6; x < 10; x++) {
+				Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 10; x < 100; x++) {
+				// These values would result in the first splitter moving past
+				// the second splitter so are not allowed
+				Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌────────┬┬────┬────┬───┐
+│11111111││3333│4444│555│
+│        ││    │    │   │
+└────────┴┴────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter1_CannotCrossBorder_NoBorder ()
+		{
+			var tv = Get5x1TilesView (false);
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+11111│2222│3333│4444│5555
+     │    │    │    │
+     │    │    │    │
+     │    │    │    │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 5; x >= 0; x--) {
+				Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+│222222222│3333│4444│5555
+│         │    │    │
+│         │    │    │
+│         │    │    │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 6; x < 10; x++) {
+				Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 10; x < 100; x++) {
+				// These values would result in the first splitter moving past
+				// the second splitter so are not allowed
+				Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+111111111││3333│4444│5555
+         ││    │    │
+         ││    │    │
+         ││    │    │
+
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter2_CannotMoveOverNeighbours ()
+		{
+			var tv = Get5x1TilesView ();
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+┌────┬────┬────┬────┬───┐
+│1111│2222│3333│4444│555│
+│    │    │    │    │   │
+└────┴────┴────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 10; x > 5; x--) {
+				Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			for (int x = 5; x > 0; x--) {
+				Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌────┬┬────────┬────┬───┐
+│1111││33333333│4444│555│
+│    ││        │    │   │
+└────┴┴────────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			for (int x = 10; x < 15; x++) {
+				Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 15; x < 25; x++) {
+				Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌────┬────────┬┬────┬───┐
+│1111│22222222││4444│555│
+│    │        ││    │   │
+└────┴────────┴┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter2_CannotMoveOverNeighbours_NoBorder ()
+		{
+			var tv = Get5x1TilesView (false);
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+11111│2222│3333│4444│5555
+     │    │    │    │
+     │    │    │    │
+     │    │    │    │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 10; x > 5; x--) {
+				Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			for (int x = 5; x > 0; x--) {
+				Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+11111││33333333│4444│5555
+     ││        │    │
+     ││        │    │
+     ││        │    │
+
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			for (int x = 10; x < 15; x++) {
+				Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 15; x < 25; x++) {
+				Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+11111│22222222││4444│5555
+     │        ││    │
+     │        ││    │
+     │        ││    │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter2 ()
+		{
+			var tv = Get5x1TilesView ();
+
+			tv.Tiles.ElementAt (1).MinSize = 2;
+			tv.Tiles.ElementAt (2).MinSize = 3;
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+┌────┬────┬────┬────┬───┐
+│1111│2222│3333│4444│555│
+│    │    │    │    │   │
+└────┴────┴────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 10; x > 7; x--) {
+				Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			for (int x = 7; x > 0; x--) {
+				Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌────┬──┬──────┬────┬───┐
+│1111│22│333333│4444│555│
+│    │  │      │    │   │
+└────┴──┴──────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			for (int x = 10; x < 12; x++) {
+				Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 12; x < 25; x++) {
+				Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌────┬─────┬───┬────┬───┐
+│1111│22222│333│4444│555│
+│    │     │   │    │   │
+└────┴─────┴───┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter2_NoBorder ()
+		{
+			var tv = Get5x1TilesView (false);
+
+			tv.Tiles.ElementAt (1).MinSize = 2;
+			tv.Tiles.ElementAt (2).MinSize = 3;
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+11111│2222│3333│4444│5555
+     │    │    │    │
+     │    │    │    │
+     │    │    │    │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 10; x > 7; x--) {
+				Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			for (int x = 7; x > 0; x--) {
+				Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+
+11111│22│333333│4444│5555
+     │  │      │    │
+     │  │      │    │
+     │  │      │    │
+
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			for (int x = 10; x < 12; x++) {
+				Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 12; x < 25; x++) {
+				Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"  
+11111│22222│333│4444│5555
+     │     │   │    │
+     │     │   │    │
+     │     │   │    │
+
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+
+
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter4_CannotMoveOverNeighbours ()
+		{
+			var tv = Get5x1TilesView ();
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+┌────┬────┬────┬────┬───┐
+│1111│2222│3333│4444│555│
+│    │    │    │    │   │
+└────┴────┴────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 20; x > 15; x--) {
+				Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+			for (int x = 15; x > 0; x--) {
+				Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌────┬────┬────┬┬───────┐
+│1111│2222│3333││5555555│
+│    │    │    ││       │
+└────┴────┴────┴┴───────┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			for (int x = 20; x < 24; x++) {
+				Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 24; x < 100; x++) {
+				Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌────┬────┬────┬───────┬┐
+│1111│2222│3333│4444444││
+│    │    │    │       ││
+└────┴────┴────┴───────┴┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter4_CannotMoveOverNeighbours_NoBorder ()
+		{
+			var tv = Get5x1TilesView (false);
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"   
+11111│2222│3333│4444│5555
+     │    │    │    │
+     │    │    │    │
+     │    │    │    │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 20; x > 15; x--) {
+				Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+			for (int x = 15; x > 0; x--) {
+				Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"  
+11111│2222│3333││55555555
+     │    │    ││
+     │    │    ││
+     │    │    ││
+
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			for (int x = 20; x < 25; x++) {
+				Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 25; x < 100; x++) {
+				Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+11111│2222│3333│44444444│
+     │    │    │        │
+     │    │    │        │
+     │    │    │        │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+
+
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter4 ()
+		{
+			var tv = Get5x1TilesView ();
+
+			tv.Tiles.ElementAt (3).MinSize = 2;
+			tv.Tiles.ElementAt (4).MinSize = 1;
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+┌────┬────┬────┬────┬───┐
+│1111│2222│3333│4444│555│
+│    │    │    │    │   │
+└────┴────┴────┴────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 20; x > 17; x--) {
+				Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+			for (int x = 17; x > 0; x--) {
+				Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌────┬────┬────┬──┬─────┐
+│1111│2222│3333│44│55555│
+│    │    │    │  │     │
+└────┴────┴────┴──┴─────┘
+
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 20; x < 23; x++) {
+				Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 23; x < 100; x++) {
+				Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+┌────┬────┬────┬──────┬─┐
+│1111│2222│3333│444444│5│
+│    │    │    │      │ │
+└────┴────┴────┴──────┴─┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+		[Fact, AutoInitShutdown]
+		public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter4_NoBorder ()
+		{
+			var tv = Get5x1TilesView (false);
+
+			tv.Tiles.ElementAt (3).MinSize = 2;
+			tv.Tiles.ElementAt (4).MinSize = 1;
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+11111│2222│3333│4444│5555
+     │    │    │    │
+     │    │    │    │
+     │    │    │    │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 20; x > 17; x--) {
+				Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+			for (int x = 17; x > 0; x--) {
+				Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"   
+11111│2222│3333│44│555555
+     │    │    │  │
+     │    │    │  │
+     │    │    │  │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			for (int x = 20; x < 24; x++) {
+				Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+
+			for (int x = 24; x < 100; x++) {
+				Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}");
+			}
+
+
+			tv.SetNeedsDisplay();
+			tv.Redraw (tv.Bounds);
+
+			looksLike =
+@"
+11111│2222│3333│4444444│5
+     │    │    │       │
+     │    │    │       │
+     │    │    │       │
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+		[Fact, AutoInitShutdown]
+		public void TestNestedNonRoots_OnlyOneRoot_OnlyRootCanHaveBorders ()
+		{
+			var tv = new TileView { Width = 10, Height = 5, ColorScheme = new ColorScheme (),
+				Border = new Border () { BorderStyle = BorderStyle.Single }
+			};
+
+			tv.TrySplitTile (1, 2, out var tv2);
+			tv2.ColorScheme = new ColorScheme ();
+			tv2.Border.BorderStyle = BorderStyle.Single; 
+			tv2.Orientation = Orientation.Horizontal;
+
+			Assert.True (tv.IsRootTileView ());
+
+			Application.Top.Add (tv);
+			tv.BeginInit ();
+			tv.EndInit ();
+			tv.LayoutSubviews ();
+
+			tv.LayoutSubviews ();
+			tv.Tiles.ElementAt (1).ContentView.LayoutSubviews ();
+			tv2.LayoutSubviews ();
+
+			// tv2 is not considered a root because 
+			// it was created via TrySplitTile so it
+			// will have its lines joined to
+			// parent and cannot have its own border
+			Assert.False (tv2.IsRootTileView ());
+
+			tv.Redraw (tv.Bounds);
+
+			var looksLike =
+@"
+┌────┬───┐
+│    │   │
+│    ├───┤
+│    │   │
+└────┴───┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+		[Fact, AutoInitShutdown]
+		public void TestTrySplit_ShouldRetainTitle ()
+		{
+			var tv = new TileView ();
+			tv.Tiles.ElementAt (0).Title = "flibble";
+			tv.TrySplitTile (0, 2, out var subTileView);
+
+			// We moved the content so the title should also have been moved
+			Assert.Equal ("flibble", subTileView.Tiles.ElementAt (0).Title);
+
+			// Secondly we should have cleared the old title (it should have been moved not copied)
+			Assert.Empty (tv.Tiles.ElementAt (0).Title);
+		}
+
+
+		[Fact, AutoInitShutdown]
+		public void TestNestedContainer3RightAnd1Down_TileVisibility_WithBorder ()
+		{
+			var tileView = GetNestedContainer3Right1Down (true);
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+┌─────┬──────┬─────┐
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222│33333│
+│11111│222222├─────┤
+│11111│222222│44444│
+│11111│222222│44444│
+│11111│222222│44444│
+└─────┴──────┴─────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = true;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌────────────┬─────┐
+│222222222222│33333│
+│222222222222│33333│
+│222222222222│33333│
+│222222222222│33333│
+│222222222222├─────┤
+│222222222222│44444│
+│222222222222│44444│
+│222222222222│44444│
+└────────────┴─────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = true;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌────────────┬─────┐
+│111111111111│33333│
+│111111111111│33333│
+│111111111111│33333│
+│111111111111│33333│
+│111111111111├─────┤
+│111111111111│44444│
+│111111111111│44444│
+│111111111111│44444│
+└────────────┴─────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = false;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌─────┬────────────┐
+│11111│222222222222│
+│11111│222222222222│
+│11111│222222222222│
+│11111│222222222222│
+│11111│222222222222│
+│11111│222222222222│
+│11111│222222222222│
+│11111│222222222222│
+└─────┴────────────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = false;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌──────────────────┐
+│111111111111111111│
+│111111111111111111│
+│111111111111111111│
+│111111111111111111│
+│111111111111111111│
+│111111111111111111│
+│111111111111111111│
+│111111111111111111│
+└──────────────────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = false;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌──────────────────┐
+│222222222222222222│
+│222222222222222222│
+│222222222222222222│
+│222222222222222222│
+│222222222222222222│
+│222222222222222222│
+│222222222222222222│
+│222222222222222222│
+└──────────────────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = true;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌──────────────────┐
+│333333333333333333│
+│333333333333333333│
+│333333333333333333│
+│333333333333333333│
+├──────────────────┤
+│444444444444444444│
+│444444444444444444│
+│444444444444444444│
+└──────────────────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = false;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+┌──────────────────┐
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+└──────────────────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+
+
+
+		[Fact, AutoInitShutdown]
+		public void TestNestedContainer3RightAnd1Down_TileVisibility_WithoutBorder ()
+		{
+			var tileView = GetNestedContainer3Right1Down (false);
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+111111│222222│333333
+111111│222222│333333
+111111│222222│333333
+111111│222222│333333
+111111│222222│333333
+111111│222222├──────
+111111│222222│444444
+111111│222222│444444
+111111│222222│444444
+111111│222222│444444";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = true;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+2222222222222│333333
+2222222222222│333333
+2222222222222│333333
+2222222222222│333333
+2222222222222│333333
+2222222222222├──────
+2222222222222│444444
+2222222222222│444444
+2222222222222│444444
+2222222222222│444444";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = true;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+1111111111111│333333
+1111111111111│333333
+1111111111111│333333
+1111111111111│333333
+1111111111111│333333
+1111111111111├──────
+1111111111111│444444
+1111111111111│444444
+1111111111111│444444
+1111111111111│444444";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = false;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+111111│2222222222222
+111111│2222222222222
+111111│2222222222222
+111111│2222222222222
+111111│2222222222222
+111111│2222222222222
+111111│2222222222222
+111111│2222222222222
+111111│2222222222222
+111111│2222222222222";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = false;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+11111111111111111111
+11111111111111111111
+11111111111111111111
+11111111111111111111
+11111111111111111111
+11111111111111111111
+11111111111111111111
+11111111111111111111
+11111111111111111111
+11111111111111111111";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = true;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = false;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+22222222222222222222
+22222222222222222222
+22222222222222222222
+22222222222222222222
+22222222222222222222
+22222222222222222222
+22222222222222222222
+22222222222222222222
+22222222222222222222
+22222222222222222222";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = true;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+33333333333333333333
+33333333333333333333
+33333333333333333333
+33333333333333333333
+33333333333333333333
+────────────────────
+44444444444444444444
+44444444444444444444
+44444444444444444444
+44444444444444444444";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			tileView.Tiles.ElementAt (0).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (1).ContentView.Visible = false;
+			tileView.Tiles.ElementAt (2).ContentView.Visible = false;
+			tileView.LayoutSubviews ();
+
+			tileView.Redraw (tileView.Bounds);
+
+			looksLike =
+@"
+ ";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestNestedContainer3RightAnd1Down_TitleDoesNotOverspill()
+		{
+			var tileView = GetNestedContainer3Right1Down (true,true,1);
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+┌ T1 ─┬ T3 ──┬ T2 ─┐
+│11111│333333│22222│
+│11111│333333│22222│
+│11111│333333│22222│
+│11111│333333│22222│
+│11111├ T4 ──┤22222│
+│11111│444444│22222│
+│11111│444444│22222│
+│11111│444444│22222│
+└─────┴──────┴─────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestNestedContainer3RightAnd1Down_TitleTriesToOverspill ()
+		{
+			var tileView = GetNestedContainer3Right1Down (true, true, 1);
+
+			tileView.Tiles.ElementAt (0).Title = new string ('x', 100);
+
+			((TileView)tileView.Tiles.ElementAt (1).ContentView)
+				.Tiles.ElementAt(1).Title = new string ('y', 100);
+
+			tileView.Redraw (tileView.Bounds);
+
+			string looksLike =
+@"
+┌ xxxx┬ T3 ──┬ T2 ─┐
+│11111│333333│22222│
+│11111│333333│22222│
+│11111│333333│22222│
+│11111│333333│22222│
+│11111├ yyyyy┤22222│
+│11111│444444│22222│
+│11111│444444│22222│
+│11111│444444│22222│
+└─────┴──────┴─────┘";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+
+		/// <summary>
+		/// Creates a vertical orientation root container with left pane split into
+		/// two (with horizontal splitter line).
+		/// </summary>
+		/// <param name="withBorder"></param>
+		/// <returns></returns>
+		private TileView GetNestedContainer2Left1Right (bool withBorder)
+		{
+			var container = GetTileView (20, 10, withBorder);
+			Assert.True (container.TrySplitTile (0, 2, out var newContainer));
+
+			newContainer.Orientation = Terminal.Gui.Graphs.Orientation.Horizontal;
+			newContainer.ColorScheme = new ColorScheme ();
+			container.ColorScheme = new ColorScheme ();
+
+			container.LayoutSubviews ();
+			return container;
+		}
+
+		/// <summary>
+		/// Creates a vertical orientation root container with 3 tiles.
+		/// The rightmost is split horizontally
+		/// </summary>
+		/// <param name="withBorder"></param>
+		/// <returns></returns>
+		private TileView GetNestedContainer3Right1Down (bool withBorder, bool withTitles = false, int split = 2)
+		{
+			var container =	new TileView (3) {
+				Width = 20,
+				Height = 10
+			};
+			container.Border.BorderStyle = withBorder ? BorderStyle.Single : BorderStyle.None;
+
+			Assert.True (container.TrySplitTile (split, 2, out var newContainer));
+
+			newContainer.Orientation = Terminal.Gui.Graphs.Orientation.Horizontal;
+
+			int i = 0;
+			foreach (var tile in container.Tiles.Union (newContainer.Tiles)) {
+				
+				if(tile.ContentView is TileView) {
+					continue;
+				}
+				i++;
+
+				if (withTitles) {
+					tile.Title = "T" + i;
+				}
+
+				tile.ContentView.Add (new TextView {
+					Width = Dim.Fill (),
+					Height = Dim.Fill (),
+					Text =
+						string.Join ('\n',
+						Enumerable.Repeat (
+							new string (i.ToString () [0], 100)
+							, 10).ToArray ()),
+					WordWrap = false
+				});
+			}
+
+			newContainer.ColorScheme = new ColorScheme ();
+			container.ColorScheme = new ColorScheme ();
+			container.LayoutSubviews ();
+			return container;
+		}
+
+		private LineView GetLine (TileView tileView)
+		{
+			return tileView.Subviews.OfType<LineView> ().Single ();
+		}
+
+		private void SetInputFocusLine (TileView tileView)
+		{
+			var line = GetLine (tileView);
+			line.SetFocus ();
+			Assert.True (line.HasFocus);
+		}
+
+
+		private TileView Get5x1TilesView (bool border = true)
+		{
+			var tv = new TileView (5) { Width = 25, Height = 4, ColorScheme = new ColorScheme (),
+				Border = new Border () { BorderStyle = BorderStyle.Single }
+			};
+
+			if (!border) {
+				tv.Border.BorderStyle = BorderStyle.None;
+			}
+
+			tv.Tiles.ElementAt (0).ContentView.Add (new Label (new string ('1', 100)) { AutoSize = false, Width = Dim.Fill (), Height = 1 });
+			tv.Tiles.ElementAt (1).ContentView.Add (new Label (new string ('2', 100)) { AutoSize = false, Width = Dim.Fill (), Height = 1 });
+			tv.Tiles.ElementAt (2).ContentView.Add (new Label (new string ('3', 100)) { AutoSize = false, Width = Dim.Fill (), Height = 1 });
+			tv.Tiles.ElementAt (3).ContentView.Add (new Label (new string ('4', 100)) { AutoSize = false, Width = Dim.Fill (), Height = 1 });
+			tv.Tiles.ElementAt (4).ContentView.Add (new Label (new string ('5', 100)) { AutoSize = false, Width = Dim.Fill (), Height = 1 });
+
+			Application.Top.Add (tv);
+			tv.BeginInit ();
+			tv.EndInit ();
+			tv.LayoutSubviews ();
+
+			return tv;
+		}
+
+		private TileView Get11By3TileView (out LineView line, bool withBorder = false)
+		{
+			var split = Get11By3TileView (withBorder);
+			line = GetLine (split);
+
+			return split;
+		}
+		private TileView Get11By3TileView (bool withBorder = false)
+		{
+			return GetTileView (11, 3, withBorder);
+		}
+		private TileView GetTileView (int width, int height, bool withBorder = false)
+		{
+			var container = new TileView () {
+				Width = width,
+				Height = height,
+			};
+
+			container.Border.BorderStyle = withBorder ? BorderStyle.Single : BorderStyle.None;
+
+			container.Tiles.ElementAt (0).ContentView.Add (new Label (new string ('1', 100)) { Width = Dim.Fill (), Height = 1, AutoSize = false });
+			container.Tiles.ElementAt (0).ContentView.Add (new Label (new string ('1', 100)) { Width = Dim.Fill (), Height = 1, AutoSize = false, Y = 1 });
+			container.Tiles.ElementAt (1).ContentView.Add (new Label (new string ('2', 100)) { Width = Dim.Fill (), Height = 1, AutoSize = false });
+			container.Tiles.ElementAt (1).ContentView.Add (new Label (new string ('2', 100)) { Width = Dim.Fill (), Height = 1, AutoSize = false, Y = 1 });
+
+			container.Tiles.ElementAt (0).MinSize = 0;
+			container.Tiles.ElementAt (1).MinSize = 0;
+
+			Application.Top.Add (container);
+			container.ColorScheme = new ColorScheme ();
+			container.LayoutSubviews ();
+			container.BeginInit ();
+			container.EndInit ();
+			return container;
+		}
+	}
+}

+ 316 - 3
UnitTests/Views/ViewTests.cs

@@ -1,4 +1,5 @@
-using System;
+using NStack;
+using System;
 using Xunit;
 using Xunit.Abstractions;
 //using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
@@ -1601,8 +1602,8 @@ Y
 					// Calling the Text constructor.
 					lbl = new Label (text);
 				}
-				lbl.ColorScheme = new ColorScheme ();
-				lbl.Redraw (lbl.Bounds);
+				Application.Top.Add (lbl);
+				Application.Top.Redraw (Application.Top.Bounds);
 
 				// should have the initial text
 				Assert.Equal ('t', driver.Contents [0, 0, 0]);
@@ -3991,6 +3992,7 @@ This is a tes
 			public bool IsKeyDown { get; set; }
 			public bool IsKeyPress { get; set; }
 			public bool IsKeyUp { get; set; }
+			public override ustring Text { get; set; }
 
 			public override bool OnKeyDown (KeyEvent keyEvent)
 			{
@@ -4009,6 +4011,41 @@ This is a tes
 				IsKeyUp = true;
 				return true;
 			}
+
+			public void CorrectRedraw (Rect bounds)
+			{
+				// Clear the old and new frame area
+				Clear (NeedDisplay);
+				DrawText ();
+			}
+
+			public void IncorrectRedraw (Rect bounds)
+			{
+				// Clear only the new frame area
+				Clear ();
+				DrawText ();
+			}
+
+			private void DrawText ()
+			{
+				var idx = 0;
+				for (int r = 0; r < Frame.Height; r++) {
+					for (int c = 0; c < Frame.Width; c++) {
+						if (idx < Text.Length) {
+							var rune = Text [idx];
+							if (rune != '\n') {
+								AddRune (c, r, Text [idx]);
+							}
+							idx++;
+							if (rune == '\n') {
+								break;
+							}
+						}
+					}
+				}
+				ClearLayoutNeeded ();
+				ClearNeedsDisplay ();
+			}
 		}
 
 		[Theory, AutoInitShutdown]
@@ -4176,5 +4213,281 @@ cccccccccccccccccccc", output);
 111111111111111111110", attributes);
 			}
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Correct_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Frame ()
+		{
+			var label = new Label ("At 0,0");
+			var view = new DerivedView () {
+				X = 2,
+				Y = 2,
+				Width = 30,
+				Height = 2,
+				Text = "A text with some long width\n and also with two lines."
+			};
+			var top = Application.Top;
+			top.Add (label, view);
+			Application.Begin (top);
+
+			view.CorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  ", output);
+
+			view.Frame = new Rect (1, 1, 10, 1);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
+			Assert.Equal (new Rect (1, 1, 31, 3), view.NeedDisplay);
+			view.CorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0     
+ A text wit", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Correct_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Pos_Dim ()
+		{
+			var label = new Label ("At 0,0");
+			var view = new DerivedView () {
+				X = 2,
+				Y = 2,
+				Width = 30,
+				Height = 2,
+				Text = "A text with some long width\n and also with two lines."
+			};
+			var top = Application.Top;
+			top.Add (label, view);
+			Application.Begin (top);
+
+			view.CorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  ", output);
+
+			view.X = 1;
+			view.Y = 1;
+			view.Width = 10;
+			view.Height = 1;
+			Assert.Equal (new Rect (1, 1, 10, 1), view.Frame);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
+			Assert.Equal (new Rect (1, 1, 31, 3), view.NeedDisplay);
+			view.CorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0     
+ A text wit", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Incorrect_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Frame ()
+		{
+			var label = new Label ("At 0,0");
+			var view = new DerivedView () {
+				X = 2,
+				Y = 2,
+				Width = 30,
+				Height = 2,
+				Text = "A text with some long width\n and also with two lines."
+			};
+			var top = Application.Top;
+			top.Add (label, view);
+			Application.Begin (top);
+
+			view.IncorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  ", output);
+
+			view.Frame = new Rect (1, 1, 10, 1);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
+			Assert.Equal (new Rect (1, 1, 31, 3), view.NeedDisplay);
+			view.IncorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+ A text wit                  
+  A text with some long width
+   and also with two lines.  ", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Incorrect_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Pos_Dim ()
+		{
+			var label = new Label ("At 0,0");
+			var view = new DerivedView () {
+				X = 2,
+				Y = 2,
+				Width = 30,
+				Height = 2,
+				Text = "A text with some long width\n and also with two lines."
+			};
+			var top = Application.Top;
+			top.Add (label, view);
+			Application.Begin (top);
+
+			view.IncorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  ", output);
+
+			view.X = 1;
+			view.Y = 1;
+			view.Width = 10;
+			view.Height = 1;
+			Assert.Equal (new Rect (1, 1, 10, 1), view.Frame);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
+			Assert.Equal (new Rect (1, 1, 31, 3), view.NeedDisplay);
+			view.IncorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+ A text wit                  
+  A text with some long width
+   and also with two lines.  ", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Correct_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Frame ()
+		{
+			var label = new Label ("At 0,0");
+			var view = new DerivedView () {
+				X = 2,
+				Y = 2,
+				Width = 30,
+				Height = 2,
+				Text = "A text with some long width\n and also with two lines."
+			};
+			var top = Application.Top;
+			top.Add (label, view);
+			Application.Begin (top);
+
+			view.CorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  ", output);
+
+			view.Frame = new Rect (3, 3, 10, 1);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
+			Assert.Equal (new Rect (2, 2, 30, 2), view.NeedDisplay);
+			view.CorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0       
+             
+             
+   A text wit", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Correct_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Pos_Dim ()
+		{
+			var label = new Label ("At 0,0");
+			var view = new DerivedView () {
+				X = 2,
+				Y = 2,
+				Width = 30,
+				Height = 2,
+				Text = "A text with some long width\n and also with two lines."
+			};
+			var top = Application.Top;
+			top.Add (label, view);
+			Application.Begin (top);
+
+			view.CorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  ", output);
+
+			view.X = 3;
+			view.Y = 3;
+			view.Width = 10;
+			view.Height = 1;
+			Assert.Equal (new Rect (3, 3, 10, 1), view.Frame);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
+			Assert.Equal (new Rect (2, 2, 30, 2), view.NeedDisplay);
+			view.CorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0       
+             
+             
+   A text wit", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Incorrect_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Frame ()
+		{
+			var label = new Label ("At 0,0");
+			var view = new DerivedView () {
+				X = 2,
+				Y = 2,
+				Width = 30,
+				Height = 2,
+				Text = "A text with some long width\n and also with two lines."
+			};
+			var top = Application.Top;
+			top.Add (label, view);
+			Application.Begin (top);
+
+			view.IncorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  ", output);
+
+			view.Frame = new Rect (3, 3, 10, 1);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
+			Assert.Equal (new Rect (2, 2, 30, 2), view.NeedDisplay);
+			view.IncorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+                             
+  A text with some long width
+   A text witith two lines.  ", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Incorrect_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Pos_Dim ()
+		{
+			var label = new Label ("At 0,0");
+			var view = new DerivedView () {
+				X = 2,
+				Y = 2,
+				Width = 30,
+				Height = 2,
+				Text = "A text with some long width\n and also with two lines."
+			};
+			var top = Application.Top;
+			top.Add (label, view);
+			Application.Begin (top);
+
+			view.IncorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  ", output);
+
+			view.X = 3;
+			view.Y = 3;
+			view.Width = 10;
+			view.Height = 1;
+			Assert.Equal (new Rect (3, 3, 10, 1), view.Frame);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
+			Assert.Equal (new Rect (2, 2, 30, 2), view.NeedDisplay);
+			view.IncorrectRedraw (view.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+At 0,0                       
+                             
+  A text with some long width
+   A text witith two lines.  ", output);
+		}
 	}
 }

+ 154 - 0
docfx/v2specs/View.md

@@ -0,0 +1,154 @@
+# V2 Spec for View refactor
+
+IMPORTANT: I am critical of the existing codebase below. Do not take any of this personally. It is about the code, not the amazing people who wrote the code.
+
+ALSO IMPORTANT: I've written this to encourage and drive DEBATE. My style is to "Have strong opinions, weakly held." If you read something here you don't understand or don't agree with, SAY SO. Tell me why. Take a stand. 
+
+This covers my thinking on how we will refactor `View` and the classes in the `View` heirarchy(inclidng `Responder`). It does not cover 
+  * Text formatting which will be covered in another spec. 
+  * TrueColor support which will be covered separately.
+  * ConsoleDriver refactor.
+
+## Terminal.Gui v2 View-related Lexicon & Taxonomy
+
+  * *View* - The most basic visual element in Terminal.Gui. Implemented in the `View` base-class. 
+  * *SubView* - A View that is contained in antoher view and will be rendered as part of the containing view's *ContentArea*. SubViews are added to another view via the `View.Add` method. A View may only be a SubView of a single View. 
+  * *SuperView* - The View that a *SubView* was added to. 
+  * *Child View* - A view that is held by another view in a parent/child relationshiop, but is NOT a SubView. Examples of this are sub-menus of `MenuBar`. 
+  * *Parent View* - A view that holds a reference to another view in a parent/child relationship, but is NOT a SuperView of the child. 
+  * *Thickness* - Describes how thick a rectangle is on each of the rectangle's four sides. Valid thickness values are >= 0. 
+  * *Margin* - Means the Thickness that separtes a View from other SubViews of the same SUperView. 
+  * *Title* - Means text that is displayed for the View that describes the View to users. For most Views the Title is displayed at the top-left, overlaying the Border. 
+  * *Border* - Means the Thickness where a visual border (drawn using line-drawing glyphs) and the Title are drawn. The Border expands inward; in other words if `Border.Thickness.Top == 2` the border & title will take up the first row and the second row will be filled with spaces. 
+  * *Adornments* - The Thickness between the Margin and Padding. The Adornments property of `View` is a `View`-subclass that hosts SubViews that are not part of the View's content and are rendered within the Adornment Thickness. Examples of Adornments:
+    * A `TitleBar` renders the View's `Title` and a horizontal line defining the top of the View. Adds thickness to the top of Adornments. 
+    * One or more `LineView`s that render the View's border (NOTE: The magic of `LineCanvas` lets us automatically have the right joins for these and `TitleBar`!).
+    * A `Vertical Scrollbar` adds thickness to `Adornments.Right` (or `.Left` when right-to-left language support is added). 
+    * A `Horizontal Scrollbar` adds thickness to `Adornments.Bottom` when enabled.
+    * A `MenuBar` adds thickness to `Adornments.Top` (NOTE: This is a change from v1 where `subview.Y = 1` is required).
+    * A `StatusBar` adds thickness ot `Adornments.Bottom` and is rendered at the bottom of Padding.
+    * NOTE: The use of `View.Add` in v1 to add adornments to Views is the cause of much code complexity. Changing the API such that `View.Add` is ONLY for subviews and adding a `View.Adornments.Add` API for menu, statusbar, scroll bar... will enable us to signficantly simplify the codebase.
+  * *Padding* - Means the Thickness inside of an element that offsets the `Content` from the Border. (NOTE: in v1 `Padding` is OUTSIDE of the `Border`). Padding is `{0, 0, 0, 0}` by default.
+  * *Frame* - Means the `Rect` that defines the location and size of the `View` including all of the margin, border, padding, and content area. The coordinates are relative to the SuperView of the View (or, in the case of `Application.Top`, `ConsoleDriver.Row == 0; ConsoleDriver.Col == 0`). The Frame's location and size are controlled by either `Absolute` or `Computed` positioning via the `.X`, `.Y`, `.Height`, and `.Width` properties of the View. 
+  * *VisibleArea* - Means the area inside of the Margin + Border (Title) + Padding. `VisibleArea.Location` is always `{0, 0}`. `VisibleArea.Size` is the `View.Frame.Size` shrunk by Margin + Border + Padding. 
+  * *ContentArea* - The `Rect` that describes the location and size of the View's content, relative to `VisibleRect`. If `ContentArea.Location` is negative, anything drawn there will be clipped and any subview positioned in the negative area will cause (optional) scrollbars to appear (making the Thickness of Padding thicker on the appropriate sides). If `ContentArea.Size` is changed such that the dimensions fall outside of `Frame.Size shrunk by Margin + Border + Padding`, drawning will be clipped and (optional) scrollbars will appear.
+  * *Bounds* - Synomous with *VisibleArea*. (Debate: Do we rename `Bounds` to `VisbleArea` in v2?)
+  * *ClipArea* - Means the currently vislble portion of the *Content*. This is defined as a`Rect` in coordinates relative to *ContentArea* (NOT *VisibleArea*) (e.g. `ClipArea {X = 0, Y = 0} == ContentArea {X = 0, Y = 0}`). This `Rect` is passed to `View.Redraw` (and should be named "clipArea" not "bounds"). It defines the clip-region the caller desires the `Redraw` implementation to clip itself to (see notes on clipping below).
+  * *Modal* - The term used when describing a View that was created using the `Application.Run(view)` or `Application.Run<T>` APIs. When a View is running as a modal, user input is restricted to just that View until `Application.Run` exits. 
+  * *TopLevel* - The term used to describe a view that is both Modal and can have a MenuBar and/or StatusBar. 
+  * *Window* - A View that is 
+
+
+  ### Questions
+
+  * @bdisp - Why does `TopLevel.Activate/Deactivate` exist? Why is this just not `Focus`?
+  * @bdisp - is the "Mdi" concept, really about "non-modal views that have z-order and can overlap"? "Not Mdi" means "non-modal views that have the same-zorder and are tiled"
+
+
+        * `View.MouseEvent` etc... should always use `ContentBounds`-relative coordinates and should constrained by `GetClipRect`.
+  *  
+* After many fits and starts we have clipping working well. But the logic is convoluted and full of spaghetti code. We should simplfiy this by being super clear on how clipping works. 
+  * `View.Redraw(clipRect)` specifies a `clipRect` that is outside of `View.GetClipRect` it has no impact (only a `clipRect` where `View.ClipRect.Union(clipRect)` makes the rect smaller does anything). 
+  * Changing `Driver.ClipRect` from within a `Draw` implementation to draw outside of the `ContentBounds` should be disallowed (see non-ContentBounds drawing below).
+* Border (margin, padding, frame, title, etc...) is confusing and incorrect.
+  * The only reason FrameView exists is because the original architecture didn't support offsetting `View.Bounds` 
+  such that a border could be drawn and the interior content would clip correctly. Thus Miguel (or someone) built
+  FrameView with nested `ContentView` that was at `new Rect(+1, +1, -2, -2)`. 
+    * `Border` was added later, but couldn't be retrofitted into `View` such that if `View.Border ~= null` just worked like `FrameView`
+    * Thus devs are forced to use the clunky `FrameView` instead of just setting `View.Border`.
+  * It's not possilbe for a `View` to utilize `Border.BorderBrush`.
+  * `Border` has a bunch of confusing concepts that don't match other systems (esp the Web/HTML)
+    * `Margin` on the web means the space between elements - `Border` doesn't have a margin property, but does has the confusing `DrawMarginFrame` property.
+    * `Border` on the web means the space where a border is drawn. The current implementaiton confuses the term `Frame` and `Border`. `BorderThickness` is provided. In the new world, 
+    but because of the confusion between `Padding` and `Margin` it doesn't work as expectedc.
+    * `Padding` on the web means the padding inside of an element between the `Border` and `Content`. In the current implementation `Padding` is actually OUTSIDE of the `Border`. This means it's not possible for a view to offset internally by simply changing `Bounds`. 
+    * `Content` on the web means the area inside of the Margin + Border + Padding. `View` does not currently have a concept of this (but `FrameView` and `Window` do via thier private `ContentView`s.
+    * `Border` has a `Title` property. If `View` had a standard `Title` property could this be simplified (or should views that implement their own Title property just leverage `Border.Title`?).
+    * It is not possilble for a class drived from View to orverride the drawing of the "Border" (frame, title, padding, etc...). Multiple devs have asked to be able to have the border frame to be drawn with a different color than `View.ColorScheme`.
+* API should explicitly enable devs to override the drawing of `Border` independently of the `View.Draw` method. See how `WM_NCDRAW` works in wWindows (Draw non-client). It should be easy to do this from within a `View` sub-class (e.g. override `OnDrawBorder`) and externally (e.g. `DrawBorder += () => ...`. 
+
+* `AutoSize` mostly works, but only because of heroic special-casing logic all over the place by @bdisp. This should be massively simplified.
+* `FrameView` is superlufous and should be removed from the heirarchy (instead devs should just be able to manipulate `View.Border` to achieve what `FrameView` provides). The internal `FrameView.ContentView` is a bug-farm and un-needed if `View.Border` worked correctly. 
+* `TopLevel` is currently built around several concepts that are muddled:
+  * Views that host a Menu and StatusBar. It is not clear why this is and if it's needed as a concept. 
+  * Views that can be run via `Application.Run<TopLevel>`. It is not clear why ANY VIEW can't be run this way
+  * Views that can be used as a pop-up (modal) (e.g. `Dialog`). As proven by `Wizard`, it is possible to build a View that works well both ways. But it's way too hard to do this today.
+  * Views that can be moved by the user.
+  * If `View` class is REALLY required for enabling one more of the above concepts (e.g. `Window`) it should be as thin and simple as possilbe (e.g.  should inherit from `FrameView` (or just use `View.Border` effecively assuming `FrameView` is nuked) instead of containing duplicate code).
+* The `MdiContainer` stuff is complex, perhaps overly so. It's also mis-named because Terminal.Gui doesn't actually support "documents" nor does it have a full "MDI" system like Windows (did). It seems to represent features useful in overlapping Views, but it is super confusing on how this works, and the naming doesn't help. This all can be refactored to support specific scenarios and thus be simplified.
+* There is no facility for users' resizing of Views. @tznind's awesome work on `LineCanvas` and `TileView` combined with @tig's experiments show it could be done in a great way for both modal (overlapping) and tiled Views. 
+* `DrawFrame` and `DrawTitle` are implemented in `ConsoleDriver` and can be replaced by a combination of `LineCanvas` and `Border`.
+* Colors - 
+  * As noted above each of Margin, Border, Padding, and Content should support independent colors.
+  * Many View sub-classes bastardize the exiting ColorSchemes to get look/feel that works (e.g. `TextView` and `Wizard`). Separately we should revamp ColorSchemes to enable more scenarios. 
+* `Responder` is supposed to be where all common, non-visual-related, code goes. We should ensure this is the case.
+* `View` should have default support for scroll bars. e.g. assume in the new world `View.ContentBounds` is the clip area (defined by `VIew.Frame` minus `Margin` + `Border` + `Padding`) then if any view is added with `View.Add` that has Frame coordinates outside of `ContentBounds` the appropriate scroll bars show up automatgically (optioally of course). Without any code, scrolling just works. 
+* We have many requests to support non-full-screen apps. We need to ensure the `View` class heirachy suppports this in a simple, understandable way. In a world with non-full-screen (where screen is defined as the visible terminal view) apps, the idea that `Frame` is "screen relative" is broken. Although we COULD just define "screen" as "the area that bounds the Terminal.GUI app.".  
+  * Question related to this: If `View.Border` works correctly (margin, border, padding, content) and if non-full-screen apps are supported, what happens if the margin of `Application.Top` is not zero (e.g. `Border.Margin = new Thickness(1,1)`). It feels more pure that such a margin would make the top-left corner of `Application.Top`'s border be att `ConsoleDriver.Row = 1, Column = 1`). If this is thw path, then "screen" means `Application.Top.Frame`). This is my preference. 
+
+## Thoughts on Built-in Views
+* `LineView` can be replaced by `LineCanvas`?
+* `Button` and `Label` can be merged. 
+* `StatusBar` and `Menu` could be combined. If not, then at least made more consistent (e.g. in how hotkeys are specified).
+
+## Design
+
+* `Responder`("Responder base class implemented by objects that want to participate on keyboard and mouse input.") remains mostly unchanged, with minor changes:
+   * Methods that take `View` parametsrs (e.g. `OnEnter`) change to take `Responder` (bad OO design).
+   * Nuke `IsOverriden` (bad OO design)
+   * Move `View.Data` to `Responder` (primitive)
+   * Move `Command` and `KeyBinding` stuff from `View`.
+   * Move generic mouse and keyboard stuff from `View` (e.g. `WantMousePositionReports`)
+
+
+## Example of creating Adornments
+```cs
+// ends up looking just like the v1 default Window with a menu & status bar
+// and a vertical scrollbar. In v2 the Window class would do all of this automatically.
+var top = new TitleBar() {
+    X = 0, Y = 0,
+    Width = Dim.Fill(),
+    Height = 1
+    LineStyle = LineStyle.Single
+};
+var left = new LineView() {
+    X = 0, Y = 0,
+    Width = 1,
+    Height = Dim.Fill(),
+    LineStyle = LineStyle.Single
+};
+var right = new LineView() {
+    X = Pos.AnchorEnd(), Y = 0,
+    Width = 1,
+    Height = Dim.Fill(),
+    LineStyle = LineStyle.Single
+};
+var bottom = new LineView() {
+    X = 0, Y = Pos.AnchorEnd(),
+    Width = Dim.Fill(),
+    Height = 1,
+    LineStyle = LineStyle.Single
+};
+
+var menu = new MenuBar() { 
+    X = Pos.Right(left), Y = Pos.Bottom(top)
+};
+var status = new StatusBar () {
+    X = Pos.Right(left), Y = Pos.Top(bottom)
+};
+var vscroll = new ScrollBarView () {
+    X = Pos.Left(right),
+    Y = Dim.Fill(2) // for menu & status bar
+};
+
+Adornments.Add(titleBar);
+Adornments.Add(left);
+Adornments.Add(right);
+Adornments.Add(bottom);
+Adornments.Add(vscroll);
+
+var treeView = new TreeView () {
+    X = 0, Y = 0, Width = Dim.Fill(), Height = Dim.Fill()
+};
+Add (treeView);
+```