Browse Source

Merge branch 'v2_develop' of tig:gui-cs/Terminal.Gui into v2_develop

Tig Kindel 1 năm trước cách đây
mục cha
commit
913a715d18
100 tập tin đã thay đổi với 12903 bổ sung12137 xóa
  1. 1 1
      ReactiveExample/LoginView.cs
  2. 2 2
      ReactiveExample/ReactiveExample.csproj
  3. 99 68
      Terminal.Gui/Application.cs
  4. 79 81
      Terminal.Gui/Configuration/AttributeJsonConverter.cs
  5. 18 6
      Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs
  6. 29 35
      Terminal.Gui/Configuration/ConfigProperty.cs
  7. 146 191
      Terminal.Gui/Configuration/ConfigurationManager.cs
  8. 1 1
      Terminal.Gui/Configuration/ThemeManager.cs
  9. 10 5
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  10. 2 0
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  11. 2 1
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  12. 3 0
      Terminal.Gui/Drawing/Cell.cs
  13. 300 511
      Terminal.Gui/Drawing/Color.cs
  14. 246 0
      Terminal.Gui/Drawing/ColorScheme.cs
  15. 2 2
      Terminal.Gui/Drawing/Thickness.cs
  16. 16 7
      Terminal.Gui/Resources/Strings.Designer.cs
  17. 3 0
      Terminal.Gui/Resources/Strings.fr-FR.resx
  18. 3 0
      Terminal.Gui/Resources/Strings.ja-JP.resx
  19. 3 0
      Terminal.Gui/Resources/Strings.pt-PT.resx
  20. 10 7
      Terminal.Gui/Resources/Strings.resx
  21. 3 0
      Terminal.Gui/Resources/Strings.zh-Hans.resx
  22. 1 1
      Terminal.Gui/Terminal.Gui.csproj
  23. 1 0
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs
  24. 1 12
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  25. 81 40
      Terminal.Gui/Text/TextFormatter.cs
  26. 177 0
      Terminal.Gui/View/Adornment/Adornment.cs
  27. 345 0
      Terminal.Gui/View/Adornment/Border.cs
  28. 37 0
      Terminal.Gui/View/Adornment/Margin.cs
  29. 37 0
      Terminal.Gui/View/Adornment/Padding.cs
  30. 0 446
      Terminal.Gui/View/Frame.cs
  31. 488 400
      Terminal.Gui/View/Layout/PosDim.cs
  32. 381 354
      Terminal.Gui/View/Layout/ViewLayout.cs
  33. 27 26
      Terminal.Gui/View/SuperViewChangedEventArgs.cs
  34. 388 329
      Terminal.Gui/View/View.cs
  35. 161 118
      Terminal.Gui/View/ViewDrawing.cs
  36. 1 1
      Terminal.Gui/View/ViewKeyboard.cs
  37. 98 92
      Terminal.Gui/View/ViewMouse.cs
  38. 608 612
      Terminal.Gui/View/ViewSubViews.cs
  39. 168 85
      Terminal.Gui/View/ViewText.cs
  40. 80 88
      Terminal.Gui/Views/Button.cs
  41. 0 2
      Terminal.Gui/Views/CheckBox.cs
  42. 227 233
      Terminal.Gui/Views/ColorPicker.cs
  43. 6 4
      Terminal.Gui/Views/ComboBox.cs
  44. 56 145
      Terminal.Gui/Views/DateField.cs
  45. 240 0
      Terminal.Gui/Views/DatePicker.cs
  46. 2 2
      Terminal.Gui/Views/Dialog.cs
  47. 1153 1187
      Terminal.Gui/Views/FileDialog.cs
  48. 5 3
      Terminal.Gui/Views/FileSystemColorProvider.cs
  49. 1 1
      Terminal.Gui/Views/FrameView.cs
  50. 30 37
      Terminal.Gui/Views/GraphView/Annotations.cs
  51. 280 271
      Terminal.Gui/Views/GraphView/GraphView.cs
  52. 21 14
      Terminal.Gui/Views/HexView.cs
  53. 98 123
      Terminal.Gui/Views/Label.cs
  54. 2 2
      Terminal.Gui/Views/Line.cs
  55. 1 1
      Terminal.Gui/Views/Menu/ContextMenu.cs
  56. 1 1
      Terminal.Gui/Views/Menu/Menu.cs
  57. 3 3
      Terminal.Gui/Views/Menu/MenuBar.cs
  58. 6 6
      Terminal.Gui/Views/MessageBox.cs
  59. 9 2
      Terminal.Gui/Views/ProgressBar.cs
  60. 55 65
      Terminal.Gui/Views/RadioGroup.cs
  61. 708 700
      Terminal.Gui/Views/ScrollBarView.cs
  62. 16 19
      Terminal.Gui/Views/Slider.cs
  63. 1 1
      Terminal.Gui/Views/StatusBar.cs
  64. 9 19
      Terminal.Gui/Views/Tab.cs
  65. 737 545
      Terminal.Gui/Views/TabView.cs
  66. 1223 1147
      Terminal.Gui/Views/TextField.cs
  67. 508 807
      Terminal.Gui/Views/TextView.cs
  68. 1 1
      Terminal.Gui/Views/TileView.cs
  69. 337 277
      Terminal.Gui/Views/TimeField.cs
  70. 145 132
      Terminal.Gui/Views/Toplevel.cs
  71. 137 183
      Terminal.Gui/Views/ToplevelOverlapped.cs
  72. 50 58
      Terminal.Gui/Views/TreeView/TreeNode.cs
  73. 1242 1221
      Terminal.Gui/Views/TreeView/TreeView.cs
  74. 4 3
      Terminal.Gui/Views/Window.cs
  75. 2 2
      Terminal.Gui/Views/Wizard/Wizard.cs
  76. 0 1
      Terminal.sln
  77. 4 0
      UICatalog/Properties/launchSettings.json
  78. 257 232
      UICatalog/Scenario.cs
  79. 1 1
      UICatalog/Scenarios/ASCIICustomButton.cs
  80. 440 0
      UICatalog/Scenarios/Adornments.cs
  81. 47 53
      UICatalog/Scenarios/AllViewsTester.cs
  82. 1 1
      UICatalog/Scenarios/AutoSizeAndDirectionText.cs
  83. 3 3
      UICatalog/Scenarios/BackgroundWorkerCollection.cs
  84. 3 2
      UICatalog/Scenarios/BasicColors.cs
  85. 12 13
      UICatalog/Scenarios/Buttons.cs
  86. 2 2
      UICatalog/Scenarios/CharacterMap.cs
  87. 6 6
      UICatalog/Scenarios/Clipping.cs
  88. 1 1
      UICatalog/Scenarios/CollectionNavigatorTester.cs
  89. 126 129
      UICatalog/Scenarios/ColorPicker.cs
  90. 1 1
      UICatalog/Scenarios/ComboBoxIteration.cs
  91. 13 13
      UICatalog/Scenarios/ComputedLayout.cs
  92. 21 0
      UICatalog/Scenarios/DatePickers.cs
  93. 6 7
      UICatalog/Scenarios/Dialogs.cs
  94. 2 2
      UICatalog/Scenarios/DynamicMenuBar.cs
  95. 1 1
      UICatalog/Scenarios/DynamicStatusBar.cs
  96. 3 3
      UICatalog/Scenarios/Editor.cs
  97. 0 399
      UICatalog/Scenarios/Frames.cs
  98. 568 519
      UICatalog/Scenarios/GraphViewExample.cs
  99. 6 4
      UICatalog/Scenarios/InvertColors.cs
  100. 5 5
      UICatalog/Scenarios/Keys.cs

+ 1 - 1
ReactiveExample/LoginView.cs

@@ -122,7 +122,7 @@ namespace ReactiveExample {
 				.DisposeWith (_disposable);
 			ViewModel
 				.WhenAnyValue (x => x.IsValid)	
-				.Select (valid => valid ? Colors.Base : Colors.Error)
+				.Select (valid => valid ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"])
 				.BindTo (validationLabel, x => x.ColorScheme)
 				.DisposeWith (_disposable);
 			Add (validationLabel);

+ 2 - 2
ReactiveExample/ReactiveExample.csproj

@@ -11,8 +11,8 @@
     <InformationalVersion>2.0</InformationalVersion>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="ReactiveUI.Fody" Version="19.5.31" />
-    <PackageReference Include="ReactiveUI" Version="19.5.31" />
+    <PackageReference Include="ReactiveUI.Fody" Version="19.5.39" />
+    <PackageReference Include="ReactiveUI" Version="19.5.39" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.3.1" PrivateAssets="all" />
   </ItemGroup>
   <ItemGroup>

+ 99 - 68
Terminal.Gui/Application.cs

@@ -32,6 +32,79 @@ namespace Terminal.Gui;
 /// TODO: Flush this out.
 /// </remarks>
 public static partial class Application {
+
+	// IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
+	// Encapsulate all setting of initial state for Application; Having
+	// this in a function like this ensures we don't make mistakes in
+	// guaranteeing that the state of this singleton is deterministic when Init
+	// starts running and after Shutdown returns.
+	internal static void ResetState ()
+	{
+		// Shutdown is the bookend for Init. As such it needs to clean up all resources
+		// Init created. Apps that do any threading will need to code defensively for this.
+		// e.g. see Issue #537
+		foreach (var t in _topLevels) {
+			t.Running = false;
+			t.Dispose ();
+		}
+		_topLevels.Clear ();
+		Current = null;
+		Top?.Dispose ();
+		Top = null;
+
+		// MainLoop stuff
+		MainLoop?.Dispose ();
+		MainLoop = null;
+		_mainThreadId = -1;
+		Iteration = null;
+		EndAfterFirstIteration = false;
+		
+		// Driver stuff
+		if (Driver != null) {
+			Driver.SizeChanged -= Driver_SizeChanged;
+			Driver.KeyDown -= Driver_KeyDown;
+			Driver.KeyUp -= Driver_KeyUp;
+			Driver.MouseEvent -= Driver_MouseEvent;
+			Driver?.End ();
+			Driver = null;
+		}
+		// Don't reset ForceDriver; it needs to be set before Init is called.
+		//ForceDriver = string.Empty;
+		Force16Colors = false;
+		_forceFakeConsole = false;
+		
+		// Run State stuff
+		NotifyNewRunState = null;
+		NotifyStopRunState = null;
+		MouseGrabView = null;
+		_initialized = false;
+
+		// Mouse
+		_mouseEnteredView = null;
+		WantContinuousButtonPressedView = null;
+		MouseEvent = null;
+		GrabbedMouse = null;
+		UnGrabbingMouse = null;
+		GrabbedMouse = null;
+		UnGrabbedMouse = null;
+
+		// Keyboard
+		AlternateBackwardKey = Key.Empty;
+		AlternateForwardKey = Key.Empty;
+		QuitKey = Key.Empty;
+		KeyDown = null;
+		KeyUp = null;
+		SizeChanging = null;
+
+		Colors.Reset ();
+
+		// Reset synchronization context to allow the user to run async/await,
+		// as the main loop has been ended, the synchronization context from 
+		// gui.cs does no longer process any callbacks. See #1084 for more details:
+		// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
+		SynchronizationContext.SetSynchronizationContext (syncContext: null);
+	}
+
 	/// <summary>
 	/// Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.
 	/// </summary>
@@ -66,7 +139,7 @@ public static partial class Application {
 	/// </summary>
 	public static List<CultureInfo> SupportedCultures => _cachedSupportedCultures;
 
-	static List<CultureInfo> GetSupportedCultures ()
+	internal static List<CultureInfo> GetSupportedCultures ()
 	{
 		var culture = CultureInfo.GetCultures (CultureTypes.AllCultures);
 
@@ -109,7 +182,7 @@ public static partial class Application {
 	/// </para>
 	/// <param name="driver">The <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are specified the default driver for the platform will be used.</param>
 	/// <param name="driverName">The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are specified the default driver for the platform will be used.</param>
-	public static void Init (ConsoleDriver driver = null, string driverName = null) => InternalInit (Toplevel.Create, driver, driverName);
+	public static void Init (ConsoleDriver driver = null, string driverName = null) => InternalInit (() => new Toplevel (), driver, driverName);
 
 	internal static bool _initialized = false;
 	internal static int _mainThreadId = -1;
@@ -194,6 +267,10 @@ public static partial class Application {
 
 		Top = topLevelFactory ();
 		Current = Top;
+
+		// Ensure Top's layout is up to date.
+		Current.SetRelativeLayout (Driver.Bounds);
+
 		_cachedSupportedCultures = GetSupportedCultures ();
 		_mainThreadId = Thread.CurrentThread.ManagedThreadId;
 		_initialized = true;
@@ -237,55 +314,6 @@ public static partial class Application {
 		ResetState ();
 		PrintJsonErrors ();
 	}
-
-	// Encapsulate all setting of initial state for Application; Having
-	// this in a function like this ensures we don't make mistakes in
-	// guaranteeing that the state of this singleton is deterministic when Init
-	// starts running and after Shutdown returns.
-	static void ResetState ()
-	{
-		// Shutdown is the bookend for Init. As such it needs to clean up all resources
-		// Init created. Apps that do any threading will need to code defensively for this.
-		// e.g. see Issue #537
-		foreach (var t in _topLevels) {
-			t.Running = false;
-			t.Dispose ();
-		}
-		_topLevels.Clear ();
-		Current = null;
-		Top?.Dispose ();
-		Top = null;
-
-		// BUGBUG: OverlappedTop is not cleared here, but it should be?
-
-		MainLoop?.Dispose ();
-		MainLoop = null;
-		if (Driver != null) {
-			Driver.SizeChanged -= Driver_SizeChanged;
-			Driver.KeyDown -= Driver_KeyDown;
-			Driver.KeyUp -= Driver_KeyUp;
-			Driver.MouseEvent -= Driver_MouseEvent;
-			Driver?.End ();
-			Driver = null;
-		}
-		Iteration = null;
-		MouseEvent = null;
-		KeyDown = null;
-		KeyUp = null;
-		SizeChanging = null;
-		_mainThreadId = -1;
-		NotifyNewRunState = null;
-		NotifyStopRunState = null;
-		_initialized = false;
-		MouseGrabView = null;
-		_mouseEnteredView = null;
-
-		// Reset synchronization context to allow the user to run async/await,
-		// as the main loop has been ended, the synchronization context from 
-		// gui.cs does no longer process any callbacks. See #1084 for more details:
-		// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
-		SynchronizationContext.SetSynchronizationContext (syncContext: null);
-	}
 	#endregion Initialization (Init/Shutdown)
 
 	#region Run (Begin, Run, End, Stop)
@@ -397,9 +425,9 @@ public static partial class Application {
 			MoveCurrent (Current);
 		}
 
-		if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
-			Toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows));
-		}
+		//if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
+		Toplevel.SetRelativeLayout (Driver.Bounds);
+		//}
 		Toplevel.LayoutSubviews ();
 		Toplevel.PositionToplevels ();
 		Toplevel.FocusFirst ();
@@ -443,7 +471,7 @@ public static partial class Application {
 	/// platform will be used (<see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>).
 	/// Must be <see langword="null"/> if <see cref="Init"/> has already been called. 
 	/// </param>
-	public static void Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new ()
+	public static void Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new()
 	{
 		if (_initialized) {
 			if (Driver != null) {
@@ -710,7 +738,7 @@ public static partial class Application {
 					&& (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height)
 					&& (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) {
 
-			state.Toplevel.Clear (new Rect (Point.Empty, new Size (Driver.Cols, Driver.Rows)));
+			state.Toplevel.Clear (Driver.Bounds);
 		}
 
 		if (state.Toplevel.NeedsDisplay ||
@@ -877,7 +905,7 @@ public static partial class Application {
 	/// </summary>
 	// BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What
 	// about TopLevels that are just a SubView of another View?
-	static readonly Stack<Toplevel> _topLevels = new Stack<Toplevel> ();
+	internal static readonly Stack<Toplevel> _topLevels = new Stack<Toplevel> ();
 
 	/// <summary>
 	/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
@@ -1137,7 +1165,7 @@ public static partial class Application {
 	}
 
 	// Used by OnMouseEvent to track the last view that was clicked on.
-	static View _mouseEnteredView;
+	internal static View _mouseEnteredView;
 
 	/// <summary>
 	/// Event fired when a mouse move or click occurs. Coordinates are screen relative.
@@ -1223,7 +1251,7 @@ public static partial class Application {
 			}
 		}
 
-		bool FrameHandledMouseEvent (Frame frame)
+		bool AdornmentHandledMouseEvent(Adornment frame)
 		{
 			if (frame?.Thickness.Contains (frame.FrameToScreen (), a.MouseEvent.X, a.MouseEvent.Y) ?? false) {
 				var boundsPoint = frame.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y);
@@ -1244,10 +1272,10 @@ public static partial class Application {
 		if (view != null) {
 			// Work inside-out (Padding, Border, Margin)
 			// TODO: Debate whether inside-out or outside-in is the right strategy
-			if (FrameHandledMouseEvent (view?.Padding)) {
+			if (AdornmentHandledMouseEvent(view?.Padding)) {
 				return;
 			}
-			if (FrameHandledMouseEvent (view?.Border)) {
+			if (AdornmentHandledMouseEvent(view?.Border)) {
 				if (view is Toplevel) {
 					// TODO: This is a temporary hack to work around the fact that 
 					// drag handling is handled in Toplevel (See Issue #2537)
@@ -1286,7 +1314,7 @@ public static partial class Application {
 				return;
 			}
 
-			if (FrameHandledMouseEvent (view?.Margin)) {
+			if (AdornmentHandledMouseEvent(view?.Margin)) {
 				return;
 			}
 
@@ -1329,12 +1357,13 @@ public static partial class Application {
 	#endregion Mouse handling
 
 	#region Keyboard handling
-	static Key _alternateForwardKey = new Key (KeyCode.PageDown | KeyCode.CtrlMask);
+	static Key _alternateForwardKey = Key.Empty; // Defined in config.json
 
 	/// <summary>
 	/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+	[JsonConverter (typeof (KeyJsonConverter))]
 	public static Key AlternateForwardKey {
 		get => _alternateForwardKey;
 		set {
@@ -1353,12 +1382,13 @@ public static partial class Application {
 		}
 	}
 
-	static Key _alternateBackwardKey = new Key (KeyCode.PageUp | KeyCode.CtrlMask);
+	static Key _alternateBackwardKey = Key.Empty; // Defined in config.json
 
 	/// <summary>
 	/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+	[JsonConverter (typeof (KeyJsonConverter))]
 	public static Key AlternateBackwardKey {
 		get => _alternateBackwardKey;
 		set {
@@ -1377,12 +1407,13 @@ public static partial class Application {
 		}
 	}
 
-	static Key _quitKey = new Key (KeyCode.Q | KeyCode.CtrlMask);
+	static Key _quitKey = Key.Empty; // Defined in config.json
 
 	/// <summary>
 	/// Gets or sets the key to quit the application.
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+	[JsonConverter (typeof (KeyJsonConverter))]
 	public static Key QuitKey {
 		get => _quitKey;
 		set {

+ 79 - 81
Terminal.Gui/Configuration/AttributeJsonConverter.cs

@@ -1,99 +1,97 @@
 using System;
 using System.Text.Json;
 using System.Text.Json.Serialization;
-using Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Json converter fro the <see cref="Attribute"/> class.
-	/// </summary>
-	class AttributeJsonConverter : JsonConverter<Attribute> {
-		private static AttributeJsonConverter instance;
+namespace Terminal.Gui; 
 
-		/// <summary>
-		/// 
-		/// </summary>
-		public static AttributeJsonConverter Instance {
-			get {
-				if (instance == null) {
-					instance = new AttributeJsonConverter ();
-				}
+/// <summary>
+/// Json converter fro the <see cref="Attribute"/> class.
+/// </summary>
+class AttributeJsonConverter : JsonConverter<Attribute> {
+	static AttributeJsonConverter _instance;
 
-				return instance;
+	/// <summary>
+	/// 
+	/// </summary>
+	public static AttributeJsonConverter Instance {
+		get {
+			if (_instance == null) {
+				_instance = new AttributeJsonConverter ();
 			}
+
+			return _instance;
 		}
+	}
 
-		public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-		{
-			if (reader.TokenType != JsonTokenType.StartObject) {
-				throw new JsonException ($"Unexpected StartObject token when parsing Attribute: {reader.TokenType}.");
-			}
+	public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+	{
+		if (reader.TokenType != JsonTokenType.StartObject) {
+			throw new JsonException ($"Unexpected StartObject token when parsing Attribute: {reader.TokenType}.");
+		}
 
-			Attribute attribute = new Attribute ();
-			Color foreground = null;
-			Color background = null;
-			while (reader.Read ()) {
-				if (reader.TokenType == JsonTokenType.EndObject) {
-					if (foreground == null || background == null) {
-						throw new JsonException ($"Both Foreground and Background colors must be provided.");
-					} 
-					return new Attribute (foreground, background);
+		var attribute = new Attribute ();
+		Color? foreground = null;
+		Color? background = null;
+		while (reader.Read ()) {
+			if (reader.TokenType == JsonTokenType.EndObject) {
+				if (foreground == null || background == null) {
+					throw new JsonException ("Both Foreground and Background colors must be provided.");
 				}
+				return new Attribute (foreground.Value, background.Value);
+			}
 
-				if (reader.TokenType != JsonTokenType.PropertyName) {
-					throw new JsonException ($"Unexpected token when parsing Attribute: {reader.TokenType}.");
-				}
+			if (reader.TokenType != JsonTokenType.PropertyName) {
+				throw new JsonException ($"Unexpected token when parsing Attribute: {reader.TokenType}.");
+			}
 
-				string propertyName = reader.GetString ();
-				reader.Read ();
-				string color = $"\"{reader.GetString ()}\"";
+			var propertyName = reader.GetString ();
+			reader.Read ();
+			var color = $"\"{reader.GetString ()}\"";
 
-				switch (propertyName.ToLower ()) {
-				case "foreground":
-					foreground = JsonSerializer.Deserialize<Color> (color, options);
-					break;
-				case "background":
-					background = JsonSerializer.Deserialize<Color> (color, options);
-					break;
-				//case "bright":
-				//case "bold":
-				//	attribute.Bright = reader.GetBoolean ();
-				//	break;
-				//case "dim":
-				//	attribute.Dim = reader.GetBoolean ();
-				//	break;
-				//case "underline":
-				//	attribute.Underline = reader.GetBoolean ();
-				//	break;
-				//case "blink":
-				//	attribute.Blink = reader.GetBoolean ();
-				//	break;
-				//case "reverse":
-				//	attribute.Reverse = reader.GetBoolean ();
-				//	break;
-				//case "hidden":
-				//	attribute.Hidden = reader.GetBoolean ();
-				//	break;
-				//case "strike-through":
-				//	attribute.StrikeThrough = reader.GetBoolean ();
-				//	break;				
-				default:
-					throw new JsonException ($"Unknown Attribute property {propertyName}.");
-				}
+			switch (propertyName?.ToLower ()) {
+			case "foreground":
+				foreground = JsonSerializer.Deserialize<Color> (color, options);
+				break;
+			case "background":
+				background = JsonSerializer.Deserialize<Color> (color, options);
+				break;
+			//case "bright":
+			//case "bold":
+			//	attribute.Bright = reader.GetBoolean ();
+			//	break;
+			//case "dim":
+			//	attribute.Dim = reader.GetBoolean ();
+			//	break;
+			//case "underline":
+			//	attribute.Underline = reader.GetBoolean ();
+			//	break;
+			//case "blink":
+			//	attribute.Blink = reader.GetBoolean ();
+			//	break;
+			//case "reverse":
+			//	attribute.Reverse = reader.GetBoolean ();
+			//	break;
+			//case "hidden":
+			//	attribute.Hidden = reader.GetBoolean ();
+			//	break;
+			//case "strike-through":
+			//	attribute.StrikeThrough = reader.GetBoolean ();
+			//	break;				
+			default:
+				throw new JsonException ($"Unknown Attribute property {propertyName}.");
 			}
-			throw new JsonException ();
-		}
-
-		public override void Write (Utf8JsonWriter writer, Attribute value, JsonSerializerOptions options)
-		{
-			writer.WriteStartObject ();
-			writer.WritePropertyName (nameof(Attribute.Foreground));
-			ColorJsonConverter.Instance.Write (writer, value.Foreground, options);
-			writer.WritePropertyName (nameof (Attribute.Background));
-			ColorJsonConverter.Instance.Write (writer, value.Background, options);
-			
-			writer.WriteEndObject ();
 		}
+		throw new JsonException ();
 	}
-}
 
+	public override void Write (Utf8JsonWriter writer, Attribute value, JsonSerializerOptions options)
+	{
+		writer.WriteStartObject ();
+		writer.WritePropertyName (nameof (Attribute.Foreground));
+		ColorJsonConverter.Instance.Write (writer, value.Foreground, options);
+		writer.WritePropertyName (nameof (Attribute.Background));
+		ColorJsonConverter.Instance.Write (writer, value.Background, options);
+
+		writer.WriteEndObject ();
+	}
+}

+ 18 - 6
Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs

@@ -28,10 +28,22 @@ namespace Terminal.Gui {
 				throw new JsonException ($"Unexpected StartObject token when parsing ColorScheme: {reader.TokenType}.");
 			}
 
-			var colorScheme = new ColorScheme ();
+			Attribute normal = Attribute.Default;
+			Attribute focus = Attribute.Default;
+			Attribute hotNormal = Attribute.Default;
+			Attribute hotFocus = Attribute.Default;
+			Attribute disabled = Attribute.Default;
 
 			while (reader.Read ()) {
 				if (reader.TokenType == JsonTokenType.EndObject) {
+					var colorScheme = new ColorScheme () {
+						Normal = normal,
+						Focus = focus,
+						HotNormal = hotNormal,
+						HotFocus = hotFocus,
+						Disabled = disabled
+					};
+
 					return colorScheme;
 				}
 
@@ -45,19 +57,19 @@ namespace Terminal.Gui {
 
 				switch (propertyName.ToLower()) {
 				case "normal":
-					colorScheme.Normal = attribute;
+					normal = attribute;
 					break;
 				case "focus":
-					colorScheme.Focus = attribute;
+					focus = attribute;
 					break;
 				case "hotnormal":
-					colorScheme.HotNormal = attribute;
+					hotNormal = attribute;
 					break;
 				case "hotfocus":
-					colorScheme.HotFocus = attribute;
+					hotFocus = attribute;
 					break;
 				case "disabled":
-					colorScheme.Disabled = attribute;
+					disabled = attribute;
 					break;
 				default:
 					throw new JsonException ($"Unrecognized ColorScheme Attribute name: {propertyName}.");

+ 29 - 35
Terminal.Gui/Configuration/ConfigProperty.cs

@@ -1,31 +1,41 @@
-using System;
+#nullable enable
+
+using System;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-#nullable enable
-
 namespace Terminal.Gui;
 
 /// <summary>
-/// Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> 
+/// Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/>
 /// to get and set the property's value.
 /// </summary>
 /// <remarks>
-/// Configuration properties must be <see langword="public"/> and <see langword="static"/> 
+/// Configuration properties must be <see langword="public"/> and <see langword="static"/>
 /// and have the <see cref="SerializableConfigurationProperty"/>
-/// attribute. If the type of the property requires specialized JSON serialization, 
-/// a <see cref="JsonConverter"/> must be provided using 
+/// attribute. If the type of the property requires specialized JSON serialization,
+/// a <see cref="JsonConverter"/> must be provided using
 /// the <see cref="JsonConverterAttribute"/> attribute.
 /// </remarks>
 public class ConfigProperty {
-	private object? propertyValue;
 
 	/// <summary>
 	/// Describes the property.
 	/// </summary>
 	public PropertyInfo? PropertyInfo { get; set; }
 
+	/// <summary>
+	/// Holds the property's value as it was either read from the class's implementation or from a config file.
+	/// If the property has not been set (e.g. because no configuration file specified a value),
+	/// this will be <see langword="null"/>.
+	/// </summary>
+	/// <remarks>
+	/// On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements of
+	/// the object that are non-null).
+	/// </remarks>
+	public object? PropertyValue { get; set; }
+
 	/// <summary>
 	/// Helper to get either the Json property named (specified by [JsonPropertyName(name)]
 	/// or the actual property name.
@@ -38,22 +48,6 @@ public class ConfigProperty {
 		return jpna?.Name ?? pi.Name;
 	}
 
-	/// <summary>
-	/// Holds the property's value as it was either read from the class's implementation or from a config file. 
-	/// If the property has not been set (e.g. because no configuration file specified a value), 
-	/// this will be <see langword="null"/>.
-	/// </summary>
-	/// <remarks>
-	/// On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements of 
-	/// the object that are non-null).
-	/// </remarks>
-	public object? PropertyValue {
-		get => propertyValue;
-		set {
-			propertyValue = value;
-		}
-	}
-
 	internal object? UpdateValueFrom (object source)
 	{
 		if (source == null) {
@@ -61,11 +55,11 @@ public class ConfigProperty {
 		}
 
 		var ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
-		if (source.GetType () != PropertyInfo!.PropertyType && (ut != null && source.GetType () != ut)) {
+		if (source.GetType () != PropertyInfo!.PropertyType && ut != null && source.GetType () != ut) {
 			throw new ArgumentException ($"The source object ({PropertyInfo!.DeclaringType}.{PropertyInfo!.Name}) is not of type {PropertyInfo!.PropertyType}.");
 		}
-		if (PropertyValue != null && source != null) {
-			PropertyValue = ConfigurationManager.DeepMemberwiseCopy (source, PropertyValue);
+		if (PropertyValue != null) {
+			PropertyValue = DeepMemberwiseCopy (source, PropertyValue);
 		} else {
 			PropertyValue = source;
 		}
@@ -78,10 +72,7 @@ public class ConfigProperty {
 	/// into <see cref="PropertyValue"/>.
 	/// </summary>
 	/// <returns></returns>
-	public object? RetrieveValue ()
-	{
-		return PropertyValue = PropertyInfo!.GetValue (null);
-	}
+	public object? RetrieveValue () => PropertyValue = PropertyInfo!.GetValue (null);
 
 	/// <summary>
 	/// Applies the <see cref="PropertyValue"/> to the property described by <see cref="PropertyInfo"/>.
@@ -91,12 +82,14 @@ public class ConfigProperty {
 	{
 		if (PropertyValue != null) {
 			try {
-				PropertyInfo?.SetValue (null, ConfigurationManager.DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
+				if (PropertyInfo?.GetValue (null) != null) {
+					PropertyInfo?.SetValue (null, DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
+				}
 			} catch (TargetInvocationException tie) {
 				// Check if there is an inner exception
 				if (tie.InnerException != null) {
 					// Handle the inner exception separately without catching the outer exception
-					Exception innerException = tie.InnerException;
+					var innerException = tie.InnerException;
 
 					// Handle the inner exception here
 					throw new JsonException ($"Error Applying Configuration Change: {innerException.Message}", innerException);
@@ -104,9 +97,10 @@ public class ConfigProperty {
 
 				// Handle the outer exception or rethrow it if needed
 				throw new JsonException ($"Error Applying Configuration Change: {tie.Message}", tie);
+			} catch (ArgumentException ae) {
+				throw new JsonException ($"Error Applying Configuration Change ({PropertyInfo?.Name}): {ae.Message}", ae);
 			}
 		}
 		return PropertyValue != null;
 	}
-
-}
+}

+ 146 - 191
Terminal.Gui/Configuration/ConfigurationManager.cs

@@ -1,6 +1,5 @@
 global using static Terminal.Gui.ConfigurationManager;
 global using CM = Terminal.Gui.ConfigurationManager;
-
 using System;
 using System.Collections;
 using System.Collections.Generic;
@@ -12,7 +11,6 @@ using System.Text;
 using System.Text.Encodings.Web;
 using System.Text.Json;
 using System.Text.Json.Serialization;
-using static Terminal.Gui.SpinnerStyle;
 
 
 #nullable enable
@@ -20,87 +18,124 @@ using static Terminal.Gui.SpinnerStyle;
 namespace Terminal.Gui;
 
 /// <summary>
-/// Provides settings and configuration management for Terminal.Gui applications. 
+/// Provides settings and configuration management for Terminal.Gui applications.
 /// <para>
-/// Users can set Terminal.Gui settings on a global or per-application basis by providing JSON formatted configuration files.
-/// The configuration files can be placed in at <c>.tui</c> folder in the user's home directory (e.g. <c>C:/Users/username/.tui</c>, 
+/// Users can set Terminal.Gui settings on a global or per-application basis by providing JSON formatted configuration
+/// files.
+/// The configuration files can be placed in at <c>.tui</c> folder in the user's home directory (e.g.
+/// <c>C:/Users/username/.tui</c>,
 /// or <c>/usr/username/.tui</c>),
 /// the folder where the Terminal.Gui application was launched from (e.g. <c>./.tui</c>), or as a resource
-/// within the Terminal.Gui application's main assembly. 
+/// within the Terminal.Gui application's main assembly.
 /// </para>
 /// <para>
-/// Settings are defined in JSON format, according to this schema: 
-///	https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
+/// Settings are defined in JSON format, according to this schema:
+/// https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
 /// </para>
 /// <para>
-/// Settings that will apply to all applications (global settings) reside in files named <c>config.json</c>. Settings 
+/// Settings that will apply to all applications (global settings) reside in files named <c>config.json</c>. Settings
 /// that will apply to a specific Terminal.Gui application reside in files named <c>appname.config.json</c>,
 /// where <c>appname</c> is the assembly name of the application (e.g. <c>UICatalog.config.json</c>).
 /// </para>
 /// Settings are applied using the following precedence (higher precedence settings
 /// overwrite lower precedence settings):
 /// <para>
-///	1. Application configuration found in the users's home directory (<c>~/.tui/appname.config.json</c>) -- Highest precedence 
+/// 1. Application configuration found in the users's home directory (<c>~/.tui/appname.config.json</c>) -- Highest
+/// precedence
 /// </para>
 /// <para>
-///	2. Application configuration found in the directory the app was launched from (<c>./.tui/appname.config.json</c>).
+/// 2. Application configuration found in the directory the app was launched from (<c>./.tui/appname.config.json</c>).
 /// </para>
 /// <para>
-///	3. Application configuration found in the applications's resources (<c>Resources/config.json</c>). 
+/// 3. Application configuration found in the applications's resources (<c>Resources/config.json</c>).
 /// </para>
 /// <para>
-///	4. Global configuration found in the user's home directory (<c>~/.tui/config.json</c>).
+/// 4. Global configuration found in the user's home directory (<c>~/.tui/config.json</c>).
 /// </para>
 /// <para>
-///	5. Global configuration found in the directory the app was launched from (<c>./.tui/config.json</c>).
+/// 5. Global configuration found in the directory the app was launched from (<c>./.tui/config.json</c>).
 /// </para>
 /// <para>
-///     6. Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest Precidence.
+/// 6. Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest
+/// Precidence.
 /// </para>
 /// </summary>
-public static partial class ConfigurationManager {
+public static class ConfigurationManager {
+
+	/// <summary>
+	/// Describes the location of the configuration files. The constants can be
+	/// combined (bitwise) to specify multiple locations.
+	/// </summary>
+	[Flags]
+	public enum ConfigLocations {
+		/// <summary>
+		/// No configuration will be loaded.
+		/// </summary>
+		/// <remarks>
+		/// Used for development and testing only. For Terminal,Gui to function properly, at least
+		/// <see cref="DefaultOnly"/> should be set.
+		/// </remarks>
+		None = 0,
+
+		/// <summary>
+		/// Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest
+		/// Precedence.
+		/// </summary>
+		DefaultOnly,
+
+		/// <summary>
+		/// This constant is a combination of all locations
+		/// </summary>
+		All = -1
 
-	private static readonly string _configFilename = "config.json";
+	}
+
+	static readonly string _configFilename = "config.json";
 
-	internal static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions {
+	internal static readonly JsonSerializerOptions _serializerOptions = new() {
 		ReadCommentHandling = JsonCommentHandling.Skip,
 		PropertyNameCaseInsensitive = true,
 		DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
 		WriteIndented = true,
 		Converters = {
-				// We override the standard Rune converter to support specifying Glyphs in
-				// a flexible way
-				new RuneJsonConverter(),
-				// Override Key to support "Ctrl+Q" format.
-				new KeyJsonConverter()
-			},
+			// We override the standard Rune converter to support specifying Glyphs in
+			// a flexible way
+			new RuneJsonConverter (),
+			// Override Key to support "Ctrl+Q" format.
+			new KeyJsonConverter ()
+		},
 		// Enables Key to be "Ctrl+Q" vs "Ctrl\u002BQ"
 		Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
 
-};
+	};
 
 	/// <summary>
-	/// A dictionary of all properties in the Terminal.Gui project that are decorated with the <see cref="SerializableConfigurationProperty"/> attribute.
-	/// The keys are the property names pre-pended with the class that implements the property (e.g. <c>Application.UseSystemConsole</c>).
+	/// A dictionary of all properties in the Terminal.Gui project that are decorated with the
+	/// <see cref="SerializableConfigurationProperty"/> attribute.
+	/// The keys are the property names pre-pended with the class that implements the property (e.g.
+	/// <c>Application.UseSystemConsole</c>).
 	/// The values are instances of <see cref="ConfigProperty"/> which hold the property's value and the
 	/// <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> to get and set the property's value.
 	/// </summary>
 	/// <remarks>
-	/// Is <see langword="null"/> until <see cref="Initialize"/> is called. 
+	/// Is <see langword="null"/> until <see cref="Initialize"/> is called.
 	/// </remarks>
 	internal static Dictionary<string, ConfigProperty>? _allConfigProperties;
 
 	/// <summary>
-	/// The backing property for <see cref="Settings"/>. 
+	/// The backing property for <see cref="Settings"/>.
 	/// </summary>
 	/// <remarks>
 	/// Is <see langword="null"/> until <see cref="Reset"/> is called. Gets set to a new instance by
 	/// deserialization (see <see cref="Load"/>).
 	/// </remarks>
-	private static SettingsScope? _settings;
+	static SettingsScope? _settings;
+
+	internal static StringBuilder jsonErrors = new ();
 
 	/// <summary>
-	/// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties with the <see cref="SettingsScope"/>
+	/// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties with the
+	/// <see cref="SettingsScope"/>
 	/// attribute value.
 	/// </summary>
 	public static SettingsScope? Settings {
@@ -110,9 +145,7 @@ public static partial class ConfigurationManager {
 			}
 			return _settings;
 		}
-		set {
-			_settings = value!;
-		}
+		set => _settings = value!;
 	}
 
 	/// <summary>
@@ -124,15 +157,34 @@ public static partial class ConfigurationManager {
 	/// <summary>
 	/// Application-specific configuration settings scope.
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true), JsonPropertyName ("AppSettings")]
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] [JsonPropertyName ("AppSettings")]
 	public static AppScope? AppSettings { get; set; }
 
 	/// <summary>
-	/// The set of glyphs used to draw checkboxes, lines, borders, etc...See also <seealso cref="Terminal.Gui.GlyphDefinitions"/>.
+	/// The set of glyphs used to draw checkboxes, lines, borders, etc...See also
+	/// <seealso cref="Terminal.Gui.GlyphDefinitions"/>.
+	/// </summary>
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] [JsonPropertyName ("Glyphs")]
+	public static GlyphDefinitions Glyphs { get; set; } = new ();
+
+	/// <summary>
+	/// Gets or sets whether the <see cref="ConfigurationManager"/> should throw an exception if it encounters
+	/// an error on deserialization. If <see langword="false"/> (the default), the error is logged and printed to the
+	/// console when <see cref="Application.Shutdown"/> is called.
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true),
-		JsonPropertyName ("Glyphs")]
-	public static GlyphDefinitions Glyphs { get; set; } = new GlyphDefinitions ();
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+	public static bool? ThrowOnJsonErrors { get; set; } = false;
+
+	/// <summary>
+	/// Name of the running application. By default this property is set to the application's assembly name.
+	/// </summary>
+	public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
+
+	/// <summary>
+	/// Gets and sets the locations where <see cref="ConfigurationManager"/> will look for config files.
+	/// The value is <see cref="ConfigLocations.All"/>.
+	/// </summary>
+	public static ConfigLocations Locations { get; set; } = ConfigLocations.All;
 
 	/// <summary>
 	/// Initializes the internal state of ConfigurationManager. Nominally called once as part of application
@@ -143,13 +195,13 @@ public static partial class ConfigurationManager {
 		_allConfigProperties = new Dictionary<string, ConfigProperty> ();
 		_settings = null;
 
-		Dictionary<string, Type> classesWithConfigProps = new Dictionary<string, Type> (StringComparer.InvariantCultureIgnoreCase);
+		var classesWithConfigProps = new Dictionary<string, Type> (StringComparer.InvariantCultureIgnoreCase);
 		// Get Terminal.Gui.dll classes
 
 		var types = from assembly in AppDomain.CurrentDomain.GetAssemblies ()
-			    from type in assembly.GetTypes ()
-			    where type.GetProperties ().Any (prop => prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) != null)
-			    select type;
+			from type in assembly.GetTypes ()
+			where type.GetProperties ().Any (prop => prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) != null)
+			select type;
 
 		foreach (var classWithConfig in types) {
 			classesWithConfigProps.Add (classWithConfig.Name, classWithConfig);
@@ -159,11 +211,11 @@ public static partial class ConfigurationManager {
 		classesWithConfigProps.ToList ().ForEach (x => Debug.WriteLine ($"  Class: {x.Key}"));
 
 		foreach (var p in from c in classesWithConfigProps
-				  let props = c.Value.GetProperties (BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).Where (prop =>
-					prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty)
-				  let enumerable = props
-				  from p in enumerable
-				  select p) {
+			let props = c.Value.GetProperties (BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).Where (prop =>
+				prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty)
+			let enumerable = props
+			from p in enumerable
+			select p) {
 			if (p.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty scp) {
 				if (p.GetGetMethod (true)!.IsStatic) {
 					// If the class name is omitted, JsonPropertyName is allowed. 
@@ -186,18 +238,18 @@ public static partial class ConfigurationManager {
 	}
 
 	/// <summary>
-	/// Creates a JSON document with the configuration specified. 
+	/// Creates a JSON document with the configuration specified.
 	/// </summary>
 	/// <returns></returns>
 	internal static string ToJson ()
 	{
-		Debug.WriteLine ($"ConfigurationManager.ToJson()");
-		return JsonSerializer.Serialize<SettingsScope> (Settings!, _serializerOptions);
+		Debug.WriteLine ("ConfigurationManager.ToJson()");
+		return JsonSerializer.Serialize (Settings!, _serializerOptions);
 	}
 
 	internal static Stream ToStream ()
 	{
-		var json = JsonSerializer.Serialize<SettingsScope> (Settings!, _serializerOptions);
+		var json = JsonSerializer.Serialize (Settings!, _serializerOptions);
 		// turn it into a stream
 		var stream = new MemoryStream ();
 		var writer = new StreamWriter (stream);
@@ -207,16 +259,6 @@ public static partial class ConfigurationManager {
 		return stream;
 	}
 
-	/// <summary>
-	/// Gets or sets whether the <see cref="ConfigurationManager"/> should throw an exception if it encounters 
-	/// an error on deserialization. If <see langword="false"/> (the default), the error is logged and printed to the 
-	/// console when <see cref="Application.Shutdown"/> is called. 
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-	public static bool? ThrowOnJsonErrors { get; set; } = false;
-
-	internal static StringBuilder jsonErrors = new StringBuilder ();
-
 	internal static void AddJsonError (string error)
 	{
 		Debug.WriteLine ($"ConfigurationManager: {error}");
@@ -229,15 +271,12 @@ public static partial class ConfigurationManager {
 	public static void PrintJsonErrors ()
 	{
 		if (jsonErrors.Length > 0) {
-			Console.WriteLine ($"Terminal.Gui ConfigurationManager encountered the following errors while deserializing configuration files:");
+			Console.WriteLine (@"Terminal.Gui ConfigurationManager encountered the following errors while deserializing configuration files:");
 			Console.WriteLine (jsonErrors.ToString ());
 		}
 	}
 
-	private static void ClearJsonErrors ()
-	{
-		jsonErrors.Clear ();
-	}
+	static void ClearJsonErrors () => jsonErrors.Clear ();
 
 	/// <summary>
 	/// Called when the configuration has been updated from a configuration file. Invokes the <see cref="Updated"/>
@@ -245,12 +284,12 @@ public static partial class ConfigurationManager {
 	/// </summary>
 	public static void OnUpdated ()
 	{
-		Debug.WriteLine ($"ConfigurationManager.OnApplied()");
+		Debug.WriteLine (@"ConfigurationManager.OnApplied()");
 		Updated?.Invoke (null, new ConfigurationManagerEventArgs ());
 	}
 
 	/// <summary>
-	/// Event fired when the configuration has been updated from a configuration source.  
+	/// Event fired when the configuration has been updated from a configuration source.
 	/// application.
 	/// </summary>
 	public static event EventHandler<ConfigurationManagerEventArgs>? Updated;
@@ -265,9 +304,9 @@ public static partial class ConfigurationManager {
 	/// </remarks>
 	public static void Reset ()
 	{
-		Debug.WriteLine ($"ConfigurationManager.Reset()");
+		Debug.WriteLine (@"ConfigurationManager.Reset()");
 		if (_allConfigProperties == null) {
-			ConfigurationManager.Initialize ();
+			Initialize ();
 		}
 
 		ClearJsonErrors ();
@@ -291,15 +330,16 @@ public static partial class ConfigurationManager {
 	/// <see cref="Locations"/> is set to <see cref="ConfigLocations.None"/>.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	/// This method is only really useful when using ConfigurationManagerTests
-	/// to generate the JSON doc that is embedded into Terminal.Gui (during development). 
-	/// </para>
-	/// <para>
-	/// WARNING: The <c>Terminal.Gui.Resources.config.json</c> resource has setting definitions (Themes)
-	/// that are NOT generated by this function. If you use this function to regenerate <c>Terminal.Gui.Resources.config.json</c>,
-	/// make sure you copy the Theme definitions from the existing <c>Terminal.Gui.Resources.config.json</c> file.
-	/// </para>		
+	///         <para>
+	///         This method is only really useful when using ConfigurationManagerTests
+	///         to generate the JSON doc that is embedded into Terminal.Gui (during development).
+	///         </para>
+	///         <para>
+	///         WARNING: The <c>Terminal.Gui.Resources.config.json</c> resource has setting definitions (Themes)
+	///         that are NOT generated by this function. If you use this function to regenerate
+	///         <c>Terminal.Gui.Resources.config.json</c>,
+	///         make sure you copy the Theme definitions from the existing <c>Terminal.Gui.Resources.config.json</c> file.
+	///         </para>
 	/// </remarks>
 	internal static void GetHardCodedDefaults ()
 	{
@@ -341,12 +381,12 @@ public static partial class ConfigurationManager {
 	}
 
 	/// <summary>
-	/// Called when an updated configuration has been applied to the  
+	/// Called when an updated configuration has been applied to the
 	/// application. Fires the <see cref="Applied"/> event.
 	/// </summary>
 	public static void OnApplied ()
 	{
-		Debug.WriteLine ($"ConfigurationManager.OnApplied()");
+		Debug.WriteLine ("ConfigurationManager.OnApplied()");
 		Applied?.Invoke (null, new ConfigurationManagerEventArgs ());
 
 		// TODO: Refactor ConfigurationManager to not use an event handler for this.
@@ -355,62 +395,26 @@ public static partial class ConfigurationManager {
 	}
 
 	/// <summary>
-	/// Event fired when an updated configuration has been applied to the  
+	/// Event fired when an updated configuration has been applied to the
 	/// application.
 	/// </summary>
 	public static event EventHandler<ConfigurationManagerEventArgs>? Applied;
 
 	/// <summary>
-	/// Name of the running application. By default this property is set to the application's assembly name.
-	/// </summary>
-	public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
-
-	/// <summary>
-	/// Describes the location of the configuration files. The constants can be
-	/// combined (bitwise) to specify multiple locations.
-	/// </summary>
-	[Flags]
-	public enum ConfigLocations {
-		/// <summary>
-		/// No configuration will be loaded.
-		/// </summary>
-		/// <remarks>
-		///  Used for development and testing only. For Terminal,Gui to function properly, at least
-		///  <see cref="DefaultOnly"/> should be set.
-		/// </remarks>
-		None = 0,
-
-		/// <summary>
-		/// Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest Precidence.
-		/// </summary>
-		DefaultOnly,
-
-		/// <summary>
-		/// This constant is a combination of all locations
-		/// </summary>
-		All = -1
-
-	}
-
-	/// <summary>
-	/// Gets and sets the locations where <see cref="ConfigurationManager"/> will look for config files.
-	/// The value is <see cref="ConfigLocations.All"/>.
-	/// </summary>
-	public static ConfigLocations Locations { get; set; } = ConfigLocations.All;
-
-	/// <summary>
-	/// Loads all settings found in the various configuration storage locations to 
+	/// Loads all settings found in the various configuration storage locations to
 	/// the <see cref="ConfigurationManager"/>. Optionally,
 	/// resets all settings attributed with <see cref="SerializableConfigurationProperty"/> to the defaults.
 	/// </summary>
 	/// <remarks>
 	/// Use <see cref="Apply"/> to cause the loaded settings to be applied to the running application.
 	/// </remarks>
-	/// <param name="reset">If <see langword="true"/> the state of <see cref="ConfigurationManager"/> will
-	/// be reset to the defaults.</param>
+	/// <param name="reset">
+	/// If <see langword="true"/> the state of <see cref="ConfigurationManager"/> will
+	/// be reset to the defaults.
+	/// </param>
 	public static void Load (bool reset = false)
 	{
-		Debug.WriteLine ($"ConfigurationManager.Load()");
+		Debug.WriteLine ("ConfigurationManager.Load()");
 
 		if (reset) {
 			Reset ();
@@ -446,16 +450,16 @@ public static partial class ConfigurationManager {
 	{
 		var emptyScope = new SettingsScope ();
 		emptyScope.Clear ();
-		return JsonSerializer.Serialize<SettingsScope> (emptyScope, _serializerOptions);
+		return JsonSerializer.Serialize (emptyScope, _serializerOptions);
 	}
 
 	/// <summary>
 	/// System.Text.Json does not support copying a deserialized object to an existing instance.
-	/// To work around this, we implement a 'deep, memberwise copy' method. 
+	/// To work around this, we implement a 'deep, memberwise copy' method.
 	/// </summary>
 	/// <remarks>
 	/// TOOD: When System.Text.Json implements `PopulateObject` revisit
-	///	https://github.com/dotnet/corefx/issues/37627
+	/// https://github.com/dotnet/corefx/issues/37627
 	/// </remarks>
 	/// <param name="source"></param>
 	/// <param name="destination"></param>
@@ -488,12 +492,10 @@ public static partial class ConfigurationManager {
 		// Dictionary
 		if (source.GetType ().IsGenericType && source.GetType ().GetGenericTypeDefinition ().IsAssignableFrom (typeof (Dictionary<,>))) {
 			foreach (var srcKey in ((IDictionary)source).Keys) {
-				if (srcKey is string) {
-
-				}
-				if (((IDictionary)destination).Contains (srcKey))
+				if (srcKey is string) { }
+				if (((IDictionary)destination).Contains (srcKey)) {
 					((IDictionary)destination) [srcKey] = DeepMemberwiseCopy (((IDictionary)source) [srcKey], ((IDictionary)destination) [srcKey]);
-				else {
+				} else {
 					((IDictionary)destination).Add (srcKey, ((IDictionary)source) [srcKey]);
 				}
 			}
@@ -503,7 +505,7 @@ public static partial class ConfigurationManager {
 		// ALl other object types
 		var sourceProps = source?.GetType ().GetProperties ().Where (x => x.CanRead).ToList ();
 		var destProps = destination?.GetType ().GetProperties ().Where (x => x.CanWrite).ToList ()!;
-		foreach (var (sourceProp, destProp) in
+		foreach ((var sourceProp, var destProp) in
 			from sourceProp in sourceProps
 			where destProps.Any (x => x.Name == sourceProp.Name)
 			let destProp = destProps.First (x => x.Name == sourceProp.Name)
@@ -513,65 +515,18 @@ public static partial class ConfigurationManager {
 			var sourceVal = sourceProp.GetValue (source);
 			var destVal = destProp.GetValue (destination);
 			if (sourceVal != null) {
-				if (destVal != null) {
-					// Recurse
-					destProp.SetValue (destination, DeepMemberwiseCopy (sourceVal, destVal));
-				} else {
-					destProp.SetValue (destination, sourceVal);
+				try {
+					if (destVal != null) {
+						// Recurse
+						destProp.SetValue (destination, DeepMemberwiseCopy (sourceVal, destVal));
+					} else {
+						destProp.SetValue (destination, sourceVal);
+					}
+				} catch (ArgumentException e) {
+					throw new JsonException ($"Error Applying Configuration Change: {e.Message}", e);
 				}
 			}
 		}
 		return destination!;
 	}
-
-	//public class ConfiguraitonLocation
-	//{
-	//	public string Name { get; set; } = string.Empty;
-
-	//	public string? Path { get; set; }
-
-	//	public async Task<SettingsScope> UpdateAsync (Stream stream)
-	//	{
-	//		var scope = await JsonSerializer.DeserializeAsync<SettingsScope> (stream, serializerOptions);
-	//		if (scope != null) {
-	//			ConfigurationManager.Settings?.UpdateFrom (scope);
-	//			return scope;
-	//		}
-	//		return new SettingsScope ();
-	//	}
-
-	//}
-
-	//public class StreamConfiguration {
-	//	private bool _reset;
-
-	//	public StreamConfiguration (bool reset)
-	//	{
-	//		_reset = reset;
-	//	}
-
-	//	public StreamConfiguration UpdateAppResources ()
-	//	{
-	//		if (Locations.HasFlag (ConfigLocations.AppResources)) LoadAppResources ();
-	//		return this;
-	//	}
-
-	//	public StreamConfiguration UpdateAppDirectory ()
-	//	{
-	//		if (Locations.HasFlag (ConfigLocations.AppDirectory)) LoadAppDirectory ();
-	//		return this;
-	//	}
-
-	//	// Additional update methods for each location here
-
-	//	private void LoadAppResources ()
-	//	{
-	//		// Load AppResources logic here
-	//	}
-
-	//	private void LoadAppDirectory ()
-	//	{
-	//		// Load AppDirectory logic here
-	//	}
-	//}
-}
+}

+ 1 - 1
Terminal.Gui/Configuration/ThemeManager.cs

@@ -133,7 +133,7 @@ public class ThemeManager : IDictionary<string, ThemeScope> {
 	internal static void Reset ()
 	{
 		Debug.WriteLine ($"Themes.Reset()");
-
+		Colors.Reset ();
 		Themes?.Clear ();
 		SelectedTheme = string.Empty;
 	}

+ 10 - 5
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -62,7 +62,7 @@ public abstract class ConsoleDriver {
 		get => _cols;
 		internal set {
 			_cols = value;
-			ClearContents();
+			ClearContents ();
 		}
 	}
 
@@ -73,7 +73,7 @@ public abstract class ConsoleDriver {
 		get => _rows;
 		internal set {
 			_rows = value;
-			ClearContents();
+			ClearContents ();
 		}
 	}
 
@@ -467,7 +467,7 @@ public abstract class ConsoleDriver {
 	public virtual Attribute MakeColor (Color foreground, Color background) =>
 		// Encode the colors into the int value.
 		new (
-			0, // only used by cursesdriver!
+			-1, // only used by cursesdriver!
 			foreground,
 			background
 		);
@@ -535,13 +535,13 @@ public abstract class ConsoleDriver {
 		Off = 0b_0000_0000,
 
 		/// <summary>
-		/// When enabled, <see cref="Frame.OnDrawFrames"/> will draw a 
+		/// When enabled, <see cref="View.OnDrawAdornments"/> will draw a 
 		/// ruler in the frame for any side with a padding value greater than 0.
 		/// </summary>
 		FrameRuler = 0b_0000_0001,
 
 		/// <summary>
-		/// When enabled, <see cref="Frame.OnDrawFrames"/> will draw a 
+		/// When enabled, <see cref="View.OnDrawAdornments"/> will draw a 
 		/// 'L', 'R', 'T', and 'B' when clearing <see cref="Thickness"/>'s instead of ' '.
 		/// </summary>
 		FramePadding = 0b_0000_0010
@@ -552,6 +552,11 @@ public abstract class ConsoleDriver {
 	/// </summary>
 	public static DiagnosticFlags Diagnostics { get; set; }
 
+	/// <summary>
+	/// Gets the dimensions of the terminal.
+	/// </summary>
+	public Rect Bounds => new Rect (0, 0, Cols, Rows);
+
 	/// <summary>
 	/// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.
 	/// </summary>

+ 2 - 0
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -43,6 +43,8 @@ public class FakeDriver : ConsoleDriver {
 
 	public FakeDriver ()
 	{
+		Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
+		Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
 		if (FakeBehaviors.UseFakeClipboard) {
 			Clipboard = new FakeClipboard (FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException, FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse);
 		} else {

+ 2 - 1
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -825,7 +825,8 @@ internal class WindowsDriver : ConsoleDriver {
 
 		// TODO: if some other Windows-based terminal supports true color, update this logic to not
 		// force 16color mode (.e.g ConEmu which really doesn't work well at all).
-		_isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") != null;
+		_isWindowsTerminal = _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") != null ||
+		                                          Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null;
 		if (!_isWindowsTerminal) {
 			Force16Colors = true;
 		}

+ 3 - 0
Terminal.Gui/Drawing/Cell.cs

@@ -40,4 +40,7 @@ public class Cell {
 	/// been modified since the last time it was drawn.
 	/// </summary>
 	public bool IsDirty { get; set; }
+
+	/// <inheritdoc />
+	public override string ToString () => $"[{Rune}, {Attribute}]";
 }

+ 300 - 511
Terminal.Gui/Drawing/Color.cs

@@ -4,7 +4,6 @@ using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
-using System.Runtime.CompilerServices;
 using System.Text.Json.Serialization;
 using System.Text.RegularExpressions;
 
@@ -15,82 +14,100 @@ namespace Terminal.Gui;
 /// foreground and background colors in Terminal.Gui apps. Used with <see cref="Color"/>.
 /// </summary>
 /// <remarks>
-/// <para>
-/// These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.
-/// </para>
-/// <para>
-/// For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be configured using the
-/// <see cref="Color.Colors"/> property.
-/// </para>
+///         <para>
+///         These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.
+///         </para>
+///         <para>
+///         For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be configured
+///         using the
+///         <see cref="Color.Colors"/> property.
+///         </para>
 /// </remarks>
 public enum ColorName {
 	/// <summary>
 	/// The black color. ANSI escape sequence: <c>\u001b[30m</c>.
 	/// </summary>
 	Black,
+
 	/// <summary>
 	/// The blue color. ANSI escape sequence: <c>\u001b[34m</c>.
 	/// </summary>
 	Blue,
+
 	/// <summary>
 	/// The green color. ANSI escape sequence: <c>\u001b[32m</c>.
 	/// </summary>
 	Green,
+
 	/// <summary>
 	/// The cyan color. ANSI escape sequence: <c>\u001b[36m</c>.
 	/// </summary>
 	Cyan,
+
 	/// <summary>
 	/// The red color. ANSI escape sequence: <c>\u001b[31m</c>.
 	/// </summary>
 	Red,
+
 	/// <summary>
 	/// The magenta color. ANSI escape sequence: <c>\u001b[35m</c>.
 	/// </summary>
 	Magenta,
+
 	/// <summary>
 	/// The yellow color (also known as Brown). ANSI escape sequence: <c>\u001b[33m</c>.
 	/// </summary>
 	Yellow,
+
 	/// <summary>
 	/// The gray color (also known as White). ANSI escape sequence: <c>\u001b[37m</c>.
 	/// </summary>
 	Gray,
+
 	/// <summary>
 	/// The dark gray color (also known as Bright Black). ANSI escape sequence: <c>\u001b[30;1m</c>.
 	/// </summary>
 	DarkGray,
+
 	/// <summary>
 	/// The bright blue color. ANSI escape sequence: <c>\u001b[34;1m</c>.
 	/// </summary>
 	BrightBlue,
+
 	/// <summary>
 	/// The bright green color. ANSI escape sequence: <c>\u001b[32;1m</c>.
 	/// </summary>
 	BrightGreen,
+
 	/// <summary>
 	/// The bright cyan color. ANSI escape sequence: <c>\u001b[36;1m</c>.
 	/// </summary>
 	BrightCyan,
+
 	/// <summary>
 	/// The bright red color. ANSI escape sequence: <c>\u001b[31;1m</c>.
 	/// </summary>
 	BrightRed,
+
 	/// <summary>
 	/// The bright magenta color. ANSI escape sequence: <c>\u001b[35;1m</c>.
 	/// </summary>
 	BrightMagenta,
+
 	/// <summary>
 	/// The bright yellow color. ANSI escape sequence: <c>\u001b[33;1m</c>.
 	/// </summary>
 	BrightYellow,
+
 	/// <summary>
 	/// The White color (also known as Bright White). ANSI escape sequence: <c>\u001b[37;1m</c>.
 	/// </summary>
 	White
 }
+
 /// <summary>
-/// The 16 foreground color codes used by ANSI Esc sequences for 256 color terminals. Add 10 to these values for background color.
+/// The 16 foreground color codes used by ANSI Esc sequences for 256 color terminals. Add 10 to these values for background
+/// color.
 /// </summary>
 public enum AnsiColorCode {
 	/// <summary>
@@ -102,69 +119,134 @@ public enum AnsiColorCode {
 	/// The ANSI color code for Red.
 	/// </summary>
 	RED = 31,
+
 	/// <summary>
 	/// The ANSI color code for Green.
 	/// </summary>
 	GREEN = 32,
+
 	/// <summary>
 	/// The ANSI color code for Yellow.
 	/// </summary>
 	YELLOW = 33,
+
 	/// <summary>
 	/// The ANSI color code for Blue.
 	/// </summary>
 	BLUE = 34,
+
 	/// <summary>
 	/// The ANSI color code for Magenta.
 	/// </summary>
 	MAGENTA = 35,
+
 	/// <summary>
 	/// The ANSI color code for Cyan.
 	/// </summary>
 	CYAN = 36,
+
 	/// <summary>
 	/// The ANSI color code for White.
 	/// </summary>
 	WHITE = 37,
+
 	/// <summary>
 	/// The ANSI color code for Bright Black.
 	/// </summary>
 	BRIGHT_BLACK = 90,
+
 	/// <summary>
 	/// The ANSI color code for Bright Red.
 	/// </summary>
 	BRIGHT_RED = 91,
+
 	/// <summary>
 	/// The ANSI color code for Bright Green.
 	/// </summary>
 	BRIGHT_GREEN = 92,
+
 	/// <summary>
 	/// The ANSI color code for Bright Yellow.
 	/// </summary>
 	BRIGHT_YELLOW = 93,
+
 	/// <summary>
 	/// The ANSI color code for Bright Blue.
 	/// </summary>
 	BRIGHT_BLUE = 94,
+
 	/// <summary>
 	/// The ANSI color code for Bright Magenta.
 	/// </summary>
 	BRIGHT_MAGENTA = 95,
+
 	/// <summary>
 	/// The ANSI color code for Bright Cyan.
 	/// </summary>
 	BRIGHT_CYAN = 96,
+
 	/// <summary>
 	/// The ANSI color code for Bright White.
 	/// </summary>
 	BRIGHT_WHITE = 97
 }
+
 /// <summary>
-/// Represents a 24-bit color. Provides automatic mapping between the legacy 4-bit (16 color) system and 24-bit colors (see <see cref="ColorName"/>).
-/// Used with <see cref="Attribute"/>. 
+/// Represents a 24-bit color. Provides automatic mapping between the legacy 4-bit (16 color) system and 24-bit colors (see
+/// <see cref="ColorName"/>). Used with <see cref="Attribute"/>.
 /// </summary>
 [JsonConverter (typeof (ColorJsonConverter))]
-public class Color : IEquatable<Color> {
+public readonly struct Color : IEquatable<Color> {
+
+	// TODO: Make this map configurable via ConfigurationManager
+	// TODO: This does not need to be a Dictionary, but can be an 16 element array.
+	/// <summary>
+	/// Maps legacy 16-color values to the corresponding 24-bit RGB value.
+	/// </summary>
+	internal static ImmutableDictionary<Color, ColorName> _colorToNameMap = new Dictionary<Color, ColorName> {
+		// using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
+		// See also: https://en.wikipedia.org/wiki/ANSI_escape_code
+		{ new Color (12, 12, 12), ColorName.Black },
+		{ new Color (0, 55, 218), ColorName.Blue },
+		{ new Color (19, 161, 14), ColorName.Green },
+		{ new Color (58, 150, 221), ColorName.Cyan },
+		{ new Color (197, 15, 31), ColorName.Red },
+		{ new Color (136, 23, 152), ColorName.Magenta },
+		{ new Color (128, 64, 32), ColorName.Yellow },
+		{ new Color (204, 204, 204), ColorName.Gray },
+		{ new Color (118, 118, 118), ColorName.DarkGray },
+		{ new Color (59, 120, 255), ColorName.BrightBlue },
+		{ new Color (22, 198, 12), ColorName.BrightGreen },
+		{ new Color (97, 214, 214), ColorName.BrightCyan },
+		{ new Color (231, 72, 86), ColorName.BrightRed },
+		{ new Color (180, 0, 158), ColorName.BrightMagenta },
+		{ new Color (249, 241, 165), ColorName.BrightYellow },
+		{ new Color (242, 242, 242), ColorName.White }
+	}.ToImmutableDictionary ();
+
+
+	/// <summary>
+	/// Defines the 16 legacy color names and values that can be used to set the
+	/// </summary>
+	internal static ImmutableDictionary<ColorName, AnsiColorCode> _colorNameToAnsiColorMap = new Dictionary<ColorName, AnsiColorCode> {
+		{ ColorName.Black, AnsiColorCode.BLACK },
+		{ ColorName.Blue, AnsiColorCode.BLUE },
+		{ ColorName.Green, AnsiColorCode.GREEN },
+		{ ColorName.Cyan, AnsiColorCode.CYAN },
+		{ ColorName.Red, AnsiColorCode.RED },
+		{ ColorName.Magenta, AnsiColorCode.MAGENTA },
+		{ ColorName.Yellow, AnsiColorCode.YELLOW },
+		{ ColorName.Gray, AnsiColorCode.WHITE },
+		{ ColorName.DarkGray, AnsiColorCode.BRIGHT_BLACK },
+		{ ColorName.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
+		{ ColorName.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
+		{ ColorName.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
+		{ ColorName.BrightRed, AnsiColorCode.BRIGHT_RED },
+		{ ColorName.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
+		{ ColorName.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
+		{ ColorName.White, AnsiColorCode.BRIGHT_WHITE }
+	}.ToImmutableDictionary ();
+
 	/// <summary>
 	/// Initializes a new instance of the <see cref="Color"/> class.
 	/// </summary>
@@ -184,7 +266,13 @@ public class Color : IEquatable<Color> {
 	/// Initializes a new instance of the <see cref="Color"/> class with an encoded 24-bit color value.
 	/// </summary>
 	/// <param name="rgba">The encoded 24-bit color value (see <see cref="Rgba"/>).</param>
-	public Color (int rgba) => Rgba = rgba;
+	public Color (int rgba)
+	{
+		A = (byte)(rgba >> 24 & 0xFF);
+		R = (byte)(rgba >> 16 & 0xFF);
+		G = (byte)(rgba >> 8 & 0xFF);
+		B = (byte)(rgba & 0xFF);
+	}
 
 	/// <summary>
 	/// Initializes a new instance of the <see cref="Color"/> color from a legacy 16-color value.
@@ -200,7 +288,8 @@ public class Color : IEquatable<Color> {
 	}
 
 	/// <summary>
-	/// Initializes a new instance of the <see cref="Color"/> color from string. See <see cref="TryParse(string, out Color)"/> for details.
+	/// Initializes a new instance of the <see cref="Color"/> color from string. See <see cref="TryParse(string, out Color)"/>
+	/// for details.
 	/// </summary>
 	/// <param name="colorString"></param>
 	/// <exception cref="Exception"></exception>
@@ -229,15 +318,17 @@ public class Color : IEquatable<Color> {
 	/// <summary>
 	/// Red color component.
 	/// </summary>
-	public int R { get; set; }
+	public int R { get; }
+
 	/// <summary>
 	/// Green color component.
 	/// </summary>
-	public int G { get; set; }
+	public int G { get; }
+
 	/// <summary>
 	/// Blue color component.
 	/// </summary>
-	public int B { get; set; }
+	public int B { get; }
 
 	/// <summary>
 	/// Alpha color component.
@@ -245,7 +336,7 @@ public class Color : IEquatable<Color> {
 	/// <remarks>
 	/// The Alpha channel is not supported by Terminal.Gui.
 	/// </remarks>
-	public int A { get; set; } = 0xFF; // Not currently supported; here for completeness.
+	public int A { get; } // Not currently supported; here for completeness.
 
 	/// <summary>
 	/// Gets or sets the color value encoded as ARGB32.
@@ -253,64 +344,8 @@ public class Color : IEquatable<Color> {
 	/// (&lt;see cref="A"/&gt; &lt;&lt; 24) | (&lt;see cref="R"/&gt; &lt;&lt; 16) | (&lt;see cref="G"/&gt; &lt;&lt; 8) | &lt;see cref="B"/&gt;
 	/// </code>
 	/// </summary>
-	public int Rgba {
-		get => A << 24 | R << 16 | G << 8 | B;
-		set {
-			A = (byte)(value >> 24 & 0xFF);
-			R = (byte)(value >> 16 & 0xFF);
-			G = (byte)(value >> 8 & 0xFF);
-			B = (byte)(value & 0xFF);
-		}
-	}
-
-	// TODO: Make this map configurable via ConfigurationManager
-	// TODO: This does not need to be a Dictionary, but can be an 16 element array.
-	/// <summary>
-	/// Maps legacy 16-color values to the corresponding 24-bit RGB value.
-	/// </summary>
-	internal static ImmutableDictionary<Color, ColorName> _colorToNameMap = new Dictionary<Color, ColorName> () {
-		// using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
-		// See also: https://en.wikipedia.org/wiki/ANSI_escape_code
-		{ new Color (12, 12, 12), ColorName.Black },
-		{ new Color (0, 55, 218), ColorName.Blue },
-		{ new Color (19, 161, 14), ColorName.Green },
-		{ new Color (58, 150, 221), ColorName.Cyan },
-		{ new Color (197, 15, 31), ColorName.Red },
-		{ new Color (136, 23, 152), ColorName.Magenta },
-		{ new Color (128, 64, 32), ColorName.Yellow },
-		{ new Color (204, 204, 204), ColorName.Gray },
-		{ new Color (118, 118, 118), ColorName.DarkGray },
-		{ new Color (59, 120, 255), ColorName.BrightBlue },
-		{ new Color (22, 198, 12), ColorName.BrightGreen },
-		{ new Color (97, 214, 214), ColorName.BrightCyan },
-		{ new Color (231, 72, 86), ColorName.BrightRed },
-		{ new Color (180, 0, 158), ColorName.BrightMagenta },
-		{ new Color (249, 241, 165), ColorName.BrightYellow },
-		{ new Color (242, 242, 242), ColorName.White }
-	}.ToImmutableDictionary ();
-
-
-	/// <summary>
-	/// Defines the 16 legacy color names and values that can be used to set the
-	/// </summary>
-	internal static ImmutableDictionary<ColorName, AnsiColorCode> _colorNameToAnsiColorMap = new Dictionary<ColorName, AnsiColorCode> {
-		{ ColorName.Black, AnsiColorCode.BLACK },
-		{ ColorName.Blue, AnsiColorCode.BLUE },
-		{ ColorName.Green, AnsiColorCode.GREEN },
-		{ ColorName.Cyan, AnsiColorCode.CYAN },
-		{ ColorName.Red, AnsiColorCode.RED },
-		{ ColorName.Magenta, AnsiColorCode.MAGENTA },
-		{ ColorName.Yellow, AnsiColorCode.YELLOW },
-		{ ColorName.Gray, AnsiColorCode.WHITE },
-		{ ColorName.DarkGray, AnsiColorCode.BRIGHT_BLACK },
-		{ ColorName.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
-		{ ColorName.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
-		{ ColorName.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
-		{ ColorName.BrightRed, AnsiColorCode.BRIGHT_RED },
-		{ ColorName.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
-		{ ColorName.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
-		{ ColorName.White, AnsiColorCode.BRIGHT_WHITE }
-	}.ToImmutableDictionary ();
+	[JsonIgnore]
+	public int Rgba => A << 24 | R << 16 | G << 8 | B;
 
 	/// <summary>
 	/// Gets or sets the 24-bit color value for each of the legacy 16-color values.
@@ -332,6 +367,28 @@ public class Color : IEquatable<Color> {
 		}
 	}
 
+	/// <summary>
+	/// Gets the <see cref="Color"/> using a legacy 16-color <see cref="Gui.ColorName"/> value.
+	/// <see langword="get"/> will return the closest 16 color match to the true color when no exact value is found.
+	/// </summary>
+	/// <remarks>
+	/// Get returns the <see cref="ColorName"/> of the closest 24-bit color value. Set sets the RGB value using a hard-coded
+	/// map.
+	/// </remarks>
+	[JsonIgnore]
+	public ColorName ColorName => FindClosestColor (this);
+
+	/// <summary>
+	/// Gets the <see cref="Color"/> using a legacy 16-color <see cref="Gui.ColorName"/> value.
+	/// <see langword="get"/> will return the closest 16 color match to the true color when no exact value is found.
+	/// </summary>
+	/// <remarks>
+	/// Get returns the <see cref="ColorName"/> of the closest 24-bit color value. Set sets the RGB value using a hard-coded
+	/// map.
+	/// </remarks>
+	[JsonIgnore]
+	public AnsiColorCode AnsiColorCode => _colorNameToAnsiColorMap [ColorName];
+
 	/// <summary>
 	/// Converts a legacy <see cref="Gui.ColorName"/> to a 24-bit <see cref="Color"/>.
 	/// </summary>
@@ -346,10 +403,10 @@ public class Color : IEquatable<Color> {
 	internal static ColorName FindClosestColor (Color inputColor)
 	{
 		var closestColor = ColorName.Black; // Default to Black
-		double closestDistance = double.MaxValue;
+		var closestDistance = double.MaxValue;
 
 		foreach (var colorEntry in _colorToNameMap) {
-			double distance = CalculateColorDistance (inputColor, colorEntry.Key);
+			var distance = CalculateColorDistance (inputColor, colorEntry.Key);
 			if (distance < closestDistance) {
 				closestDistance = distance;
 				closestColor = colorEntry.Value;
@@ -362,41 +419,128 @@ public class Color : IEquatable<Color> {
 	static double CalculateColorDistance (Color color1, Color color2)
 	{
 		// Calculate the Euclidean distance between two colors
-		double deltaR = (double)color1.R - (double)color2.R;
-		double deltaG = (double)color1.G - (double)color2.G;
-		double deltaB = (double)color1.B - (double)color2.B;
+		var deltaR = color1.R - (double)color2.R;
+		var deltaG = color1.G - (double)color2.G;
+		var deltaB = color1.B - (double)color2.B;
 
 		return Math.Sqrt (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
 	}
 
 	/// <summary>
-	/// Gets or sets the <see cref="Color"/> using a legacy 16-color <see cref="Gui.ColorName"/> value.
-	/// <see langword="get"/> will return the closest 16 color match to the true color when no exact value is found.
+	/// Converts the provided string to a new <see cref="Color"/> instance.
 	/// </summary>
+	/// <param name="text">
+	/// The text to analyze. Formats supported are
+	/// "#RGB", "#RRGGBB", "#RGBA", "#RRGGBBAA", "rgb(r,g,b)", "rgb(r,g,b,a)", and any of the
+	/// <see cref="Gui.ColorName"/>.
+	/// </param>
+	/// <param name="color">The parsed value.</param>
+	/// <returns>A boolean value indicating whether parsing was successful.</returns>
 	/// <remarks>
-	/// Get returns the <see cref="ColorName"/> of the closest 24-bit color value. Set sets the RGB value using a hard-coded map.
+	/// While <see cref="Color"/> supports the alpha channel <see cref="A"/>, Terminal.Gui does not.
 	/// </remarks>
-	public ColorName ColorName {
-		get => FindClosestColor (this);
-		set {
+	public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
+	{
+		// empty color
+		if (string.IsNullOrEmpty (text)) {
+			color = new Color ();
+			return false;
+		}
+
+		// #RRGGBB, #RGB
+		if (text [0] == '#' && text.Length is 7 or 4) {
+			if (text.Length == 7) {
+				var r = Convert.ToInt32 (text.Substring (1, 2), 16);
+				var g = Convert.ToInt32 (text.Substring (3, 2), 16);
+				var b = Convert.ToInt32 (text.Substring (5, 2), 16);
+				color = new Color (r, g, b);
+			} else {
+				var rText = char.ToString (text [1]);
+				var gText = char.ToString (text [2]);
+				var bText = char.ToString (text [3]);
+
+				var r = Convert.ToInt32 (rText + rText, 16);
+				var g = Convert.ToInt32 (gText + gText, 16);
+				var b = Convert.ToInt32 (bText + bText, 16);
+				color = new Color (r, g, b);
+			}
+			return true;
+		}
+
+		// #RRGGBB, #RGBA
+		if (text [0] == '#' && text.Length is 8 or 5) {
+			if (text.Length == 7) {
+				var r = Convert.ToInt32 (text.Substring (1, 2), 16);
+				var g = Convert.ToInt32 (text.Substring (3, 2), 16);
+				var b = Convert.ToInt32 (text.Substring (5, 2), 16);
+				var a = Convert.ToInt32 (text.Substring (7, 2), 16);
+				color = new Color (a, r, g, b);
+			} else {
+				var rText = char.ToString (text [1]);
+				var gText = char.ToString (text [2]);
+				var bText = char.ToString (text [3]);
+				var aText = char.ToString (text [4]);
+
+				var r = Convert.ToInt32 (aText + aText, 16);
+				var g = Convert.ToInt32 (rText + rText, 16);
+				var b = Convert.ToInt32 (gText + gText, 16);
+				var a = Convert.ToInt32 (bText + bText, 16);
+				color = new Color (r, g, b, a);
+			}
+			return true;
+		}
 
-			var c = FromColorName (value);
-			R = c.R;
-			G = c.G;
-			B = c.B;
-			A = c.A;
+		// rgb(r,g,b)
+		var match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+)\)");
+		if (match.Success) {
+			var r = int.Parse (match.Groups [1].Value);
+			var g = int.Parse (match.Groups [2].Value);
+			var b = int.Parse (match.Groups [3].Value);
+			color = new Color (r, g, b);
+			return true;
 		}
+
+		// rgb(r,g,b,a)
+		match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+),(\d+)\)");
+		if (match.Success) {
+			var r = int.Parse (match.Groups [1].Value);
+			var g = int.Parse (match.Groups [2].Value);
+			var b = int.Parse (match.Groups [3].Value);
+			var a = int.Parse (match.Groups [4].Value);
+			color = new Color (r, g, b, a);
+			return true;
+		}
+
+		if (Enum.TryParse<ColorName> (text, true, out var colorName)) {
+			color = new Color (colorName);
+			return true;
+		}
+
+		color = new Color ();
+		return false;
 	}
 
 	/// <summary>
-	/// Gets or sets the <see cref="Color"/> using a legacy 16-color <see cref="Gui.ColorName"/> value.
-	/// <see langword="get"/> will return the closest 16 color match to the true color when no exact value is found.
+	/// Converts the color to a string representation.
 	/// </summary>
 	/// <remarks>
-	/// Get returns the <see cref="ColorName"/> of the closest 24-bit color value. Set sets the RGB value using a hard-coded map.
+	///         <para>
+	///         If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string.
+	///         </para>
+	///         <para>
+	///         <see cref="A"/> (Alpha channel) is ignored and the returned string will not include it.
+	///         </para>
 	/// </remarks>
-	[JsonIgnore]
-	public AnsiColorCode AnsiColorCode => _colorNameToAnsiColorMap [ColorName];
+	/// <returns></returns>
+	public override string ToString ()
+	{
+		// If Values has an exact match with a named color (in _colorNames), use that.
+		if (_colorToNameMap.TryGetValue (this, out var colorName)) {
+			return Enum.GetName (typeof (ColorName), colorName);
+		}
+		// Otherwise return as an RGB hex value.
+		return $"#{R:X2}{G:X2}{B:X2}";
+	}
 
 	#region Legacy Color Names
 	/// <summary>
@@ -408,201 +552,111 @@ public class Color : IEquatable<Color> {
 	/// The blue color.
 	/// </summary>
 	public const ColorName Blue = ColorName.Blue;
+
 	/// <summary>
 	/// The green color.
 	/// </summary>
 	public const ColorName Green = ColorName.Green;
+
 	/// <summary>
 	/// The cyan color.
 	/// </summary>
 	public const ColorName Cyan = ColorName.Cyan;
+
 	/// <summary>
 	/// The red color.
 	/// </summary>
 	public const ColorName Red = ColorName.Red;
+
 	/// <summary>
 	/// The magenta color.
 	/// </summary>
 	public const ColorName Magenta = ColorName.Magenta;
+
 	/// <summary>
 	/// The yellow color.
 	/// </summary>
 	public const ColorName Yellow = ColorName.Yellow;
+
 	/// <summary>
 	/// The gray color.
 	/// </summary>
 	public const ColorName Gray = ColorName.Gray;
+
 	/// <summary>
 	/// The dark gray color.
 	/// </summary>
 	public const ColorName DarkGray = ColorName.DarkGray;
+
 	/// <summary>
 	/// The bright bBlue color.
 	/// </summary>
 	public const ColorName BrightBlue = ColorName.BrightBlue;
+
 	/// <summary>
 	/// The bright green color.
 	/// </summary>
 	public const ColorName BrightGreen = ColorName.BrightGreen;
+
 	/// <summary>
 	/// The bright cyan color.
 	/// </summary>
 	public const ColorName BrightCyan = ColorName.BrightCyan;
+
 	/// <summary>
 	/// The bright red color.
 	/// </summary>
 	public const ColorName BrightRed = ColorName.BrightRed;
+
 	/// <summary>
 	/// The bright magenta color.
 	/// </summary>
 	public const ColorName BrightMagenta = ColorName.BrightMagenta;
+
 	/// <summary>
 	/// The bright yellow color.
 	/// </summary>
 	public const ColorName BrightYellow = ColorName.BrightYellow;
+
 	/// <summary>
 	/// The White color.
 	/// </summary>
 	public const ColorName White = ColorName.White;
 	#endregion
 
-	/// <summary>
-	/// Converts the provided string to a new <see cref="Color"/> instance.
-	/// </summary>
-	/// <param name="text">The text to analyze. Formats supported are
-	/// "#RGB", "#RRGGBB", "#RGBA", "#RRGGBBAA", "rgb(r,g,b)", "rgb(r,g,b,a)", and any of the
-	/// <see cref="Gui.ColorName"/>.</param>
-	/// <param name="color">The parsed value.</param>
-	/// <returns>A boolean value indicating whether parsing was successful.</returns>
-	/// <remarks>
-	/// While <see cref="Color"/> supports the alpha channel <see cref="A"/>, Terminal.Gui does not.
-	/// </remarks>
-	public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
-	{
-		// empty color
-		if (text == null || text.Length == 0) {
-			color = null;
-			return false;
-		}
-
-		// #RRGGBB, #RGB
-		if (text [0] == '#' && text.Length is 7 or 4) {
-			if (text.Length == 7) {
-				int r = Convert.ToInt32 (text.Substring (1, 2), 16);
-				int g = Convert.ToInt32 (text.Substring (3, 2), 16);
-				int b = Convert.ToInt32 (text.Substring (5, 2), 16);
-				color = new Color (r, g, b);
-			} else {
-				string rText = char.ToString (text [1]);
-				string gText = char.ToString (text [2]);
-				string bText = char.ToString (text [3]);
-
-				int r = Convert.ToInt32 (rText + rText, 16);
-				int g = Convert.ToInt32 (gText + gText, 16);
-				int b = Convert.ToInt32 (bText + bText, 16);
-				color = new Color (r, g, b);
-			}
-			return true;
-		}
-
-		// #RRGGBB, #RGBA
-		if (text [0] == '#' && text.Length is 8 or 5) {
-			if (text.Length == 7) {
-				int r = Convert.ToInt32 (text.Substring (1, 2), 16);
-				int g = Convert.ToInt32 (text.Substring (3, 2), 16);
-				int b = Convert.ToInt32 (text.Substring (5, 2), 16);
-				int a = Convert.ToInt32 (text.Substring (7, 2), 16);
-				color = new Color (a, r, g, b);
-			} else {
-				string rText = char.ToString (text [1]);
-				string gText = char.ToString (text [2]);
-				string bText = char.ToString (text [3]);
-				string aText = char.ToString (text [4]);
-
-				int r = Convert.ToInt32 (aText + aText, 16);
-				int g = Convert.ToInt32 (rText + rText, 16);
-				int b = Convert.ToInt32 (gText + gText, 16);
-				int a = Convert.ToInt32 (bText + bText, 16);
-				color = new Color (r, g, b, a);
-			}
-			return true;
-		}
-
-		// rgb(r,g,b)
-		var match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+)\)");
-		if (match.Success) {
-			int r = int.Parse (match.Groups [1].Value);
-			int g = int.Parse (match.Groups [2].Value);
-			int b = int.Parse (match.Groups [3].Value);
-			color = new Color (r, g, b);
-			return true;
-		}
-
-		// rgb(r,g,b,a)
-		match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+),(\d+)\)");
-		if (match.Success) {
-			int r = int.Parse (match.Groups [1].Value);
-			int g = int.Parse (match.Groups [2].Value);
-			int b = int.Parse (match.Groups [3].Value);
-			int a = int.Parse (match.Groups [4].Value);
-			color = new Color (r, g, b, a);
-			return true;
-		}
-
-		if (Enum.TryParse<ColorName> (text, true, out var colorName)) {
-			color = new Color (colorName);
-			return true;
-		}
-
-		color = null;
-		return false;
-	}
-
+	// TODO: Verify implict/explicit are correct for below
 	#region Operators
 	/// <summary>
 	/// Cast from int.
 	/// </summary>
 	/// <param name="rgba"></param>
-	public static implicit operator Color (int rgba) => new Color (rgba);
+	public static implicit operator Color (int rgba) => new (rgba);
 
 	/// <summary>
-	/// Cast to int.
+	/// Cast to int. 
 	/// </summary>
 	/// <param name="color"></param>
-	public static explicit operator int (Color color) => color.Rgba;
+	public static implicit operator int (Color color) => color.Rgba;
 
 	/// <summary>
-	/// Cast from <see cref="Gui.ColorName"/>.
+	/// Cast from <see cref="Gui.ColorName"/>. May fail if the color is not a named color.
 	/// </summary>
 	/// <param name="colorName"></param>
-	public static explicit operator Color (ColorName colorName) => new Color (colorName);
+	public static explicit operator Color (ColorName colorName) => new (colorName);
 
 	/// <summary>
-	/// Cast to <see cref="Gui.ColorName"/>.
+	/// Cast to <see cref="Gui.ColorName"/>. May fail if the color is not a named color.
 	/// </summary>
 	/// <param name="color"></param>
 	public static explicit operator ColorName (Color color) => color.ColorName;
 
-
 	/// <summary>
 	/// Equality operator for two <see cref="Color"/> objects..
 	/// </summary>
 	/// <param name="left"></param>
 	/// <param name="right"></param>
 	/// <returns></returns>
-	public static bool operator == (Color left, Color right)
-	{
-		if (left is null && right is null) {
-			return true;
-		}
-
-		if (left is null || right is null) {
-			return false;
-		}
-
-		return left.Equals (right);
-	}
-
+	public static bool operator == (Color left, Color right) => left.Equals (right);
 
 	/// <summary>
 	/// Inequality operator for two <see cref="Color"/> objects.
@@ -610,18 +664,7 @@ public class Color : IEquatable<Color> {
 	/// <param name="left"></param>
 	/// <param name="right"></param>
 	/// <returns></returns>
-	public static bool operator != (Color left, Color right)
-	{
-		if (left is null && right is null) {
-			return false;
-		}
-
-		if (left is null || right is null) {
-			return true;
-		}
-
-		return !left.Equals (right);
-	}
+	public static bool operator != (Color left, Color right) => !left.Equals (right);
 
 	/// <summary>
 	/// Equality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
@@ -661,50 +704,30 @@ public class Color : IEquatable<Color> {
 
 	/// <inheritdoc/>
 	public bool Equals (Color other) => R == other.R &&
-					G == other.G &&
-					B == other.B &&
-					A == other.A;
+	                                    G == other.G &&
+	                                    B == other.B &&
+	                                    A == other.A;
 
 	/// <inheritdoc/>
 	public override int GetHashCode () => HashCode.Combine (R, G, B, A);
 	#endregion
-
-	/// <summary>
-	/// Converts the color to a string representation.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string.
-	/// </para>
-	/// <para>
-	/// <see cref="A"/> (Alpha channel) is ignored and the returned string will not include it.
-	/// </para>
-	/// </remarks>
-	/// <returns></returns>
-	public override string ToString ()
-	{
-		// If Values has an exact match with a named color (in _colorNames), use that.
-		if (_colorToNameMap.TryGetValue (this, out var colorName)) {
-			return Enum.GetName (typeof (ColorName), colorName);
-		}
-		// Otherwise return as an RGB hex value.
-		return $"#{R:X2}{G:X2}{B:X2}";
-	}
 }
+
 /// <summary>
-/// Attributes represent how text is styled when displayed in the terminal. 
+/// Attributes represent how text is styled when displayed in the terminal.
 /// </summary>
 /// <remarks>
-///   <see cref="Attribute"/> provides a platform independent representation of colors (and someday other forms of text styling).
-///   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.
+/// <see cref="Attribute"/> provides a platform independent representation of colors (and someday other forms of text
+/// styling).
+/// 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>
 [JsonConverter (typeof (AttributeJsonConverter))]
 public readonly struct Attribute : IEquatable<Attribute> {
 	/// <summary>
 	/// Default empty attribute.
 	/// </summary>
-	public static readonly Attribute Default = new Attribute (Color.White, Color.Black);
+	public static readonly Attribute Default = new (Color.White, Color.Black);
 
 	/// <summary>
 	/// The <see cref="ConsoleDriver"/>-specific color value.
@@ -716,23 +739,32 @@ public readonly struct Attribute : IEquatable<Attribute> {
 	/// The foreground color.
 	/// </summary>
 	[JsonConverter (typeof (ColorJsonConverter))]
-	public Color Foreground { get; private init; }
+	public Color Foreground { get; }
 
 	/// <summary>
 	/// The background color.
 	/// </summary>
 	[JsonConverter (typeof (ColorJsonConverter))]
-	public Color Background { get; private init; }
+	public Color Background { get; }
 
 	/// <summary>
-	///  Initializes a new instance with default values.
+	/// Initializes a new instance with default values.
 	/// </summary>
 	public Attribute ()
 	{
 		PlatformColor = -1;
-		var d = Default;
-		Foreground = new Color (d.Foreground.ColorName);
-		Background = new Color (d.Background.ColorName);
+		Foreground = new Color (Default.Foreground.ColorName);
+		Background = new Color (Default.Background.ColorName);
+	}
+
+	/// <summary>
+	/// Initializes a new instance from an existing instance.
+	/// </summary>
+	public Attribute (Attribute attr)
+	{
+		PlatformColor = -1;
+		Foreground = new Color (attr.Foreground.ColorName);
+		Background = new Color (attr.Background.ColorName);
 	}
 
 	/// <summary>
@@ -742,9 +774,8 @@ public readonly struct Attribute : IEquatable<Attribute> {
 	internal Attribute (int platformColor)
 	{
 		PlatformColor = platformColor;
-		var d = Default;
-		Foreground = new Color (d.Foreground.ColorName);
-		Background = new Color (d.Background.ColorName);
+		Foreground = new Color (Default.Foreground.ColorName);
+		Background = new Color (Default.Background.ColorName);
 	}
 
 	/// <summary>
@@ -819,7 +850,7 @@ public readonly struct Attribute : IEquatable<Attribute> {
 
 	/// <summary>
 	/// Initializes a new instance of the <see cref="Attribute"/> struct
-	///  with the same colors for the foreground and background.
+	/// with the same colors for the foreground and background.
 	/// </summary>
 	/// <param name="color">The color.</param>
 	public Attribute (Color color) : this (color, color) { }
@@ -841,261 +872,19 @@ public readonly struct Attribute : IEquatable<Attribute> {
 	/// <returns></returns>
 	public static bool operator != (Attribute left, Attribute right) => !(left == right);
 
-	/// <inheritdoc />
+	/// <inheritdoc/>
 	public override bool Equals (object obj) => obj is Attribute other && Equals (other);
 
-	/// <inheritdoc />
+	/// <inheritdoc/>
 	public bool Equals (Attribute other) => PlatformColor == other.PlatformColor &&
-						Foreground == other.Foreground &&
-						Background == other.Background;
+	                                        Foreground == other.Foreground &&
+	                                        Background == other.Background;
 
-	/// <inheritdoc />
+	/// <inheritdoc/>
 	public override int GetHashCode () => HashCode.Combine (PlatformColor, Foreground, Background);
 
-	/// <inheritdoc />
+	/// <inheritdoc/>
 	public override string ToString () =>
-		// Note, Unit tests are dependent on this format
-		$"{Foreground},{Background}";
-}
-/// <summary>
-/// Defines the <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>
-[JsonConverter (typeof (ColorSchemeJsonConverter))]
-public class ColorScheme : IEquatable<ColorScheme> {
-	Attribute _normal = Attribute.Default;
-	Attribute _focus = Attribute.Default;
-	Attribute _hotNormal = Attribute.Default;
-	Attribute _hotFocus = Attribute.Default;
-	Attribute _disabled = Attribute.Default;
-
-	/// <summary>
-	/// Used by <see cref="Colors.SetColorScheme(ColorScheme, string)"/> and <see cref="Colors.GetColorScheme(string)"/> to track which ColorScheme 
-	/// is being accessed.
-	/// </summary>
-	internal string _schemeBeingSet = "";
-
-	/// <summary>
-	/// Creates a new instance.
-	/// </summary>
-	public ColorScheme () : this (Attribute.Default) { }
-
-	/// <summary>
-	/// Creates a new instance, initialized with the values from <paramref name="scheme"/>.
-	/// </summary>
-	/// <param name="scheme">The scheme to initialize the new instance with.</param>
-	public ColorScheme (ColorScheme scheme) : base ()
-	{
-		if (scheme != null) {
-			_normal = scheme.Normal;
-			_focus = scheme.Focus;
-			_hotNormal = scheme.HotNormal;
-			_disabled = scheme.Disabled;
-			_hotFocus = scheme.HotFocus;
-		}
-	}
-
-	/// <summary>
-	/// Creates a new instance, initialized with the values from <paramref name="attribute"/>.
-	/// </summary>
-	/// <param name="attribute">The attribute to initialize the new instance with.</param>
-	public ColorScheme (Attribute attribute)
-	{
-		_normal = attribute;
-		_focus = attribute;
-		_hotNormal = attribute;
-		_disabled = attribute;
-		_hotFocus = attribute;
-	}
-
-	/// <summary>
-	/// The foreground and background color for text when the view is not focused, hot, or disabled.
-	/// </summary>
-	public Attribute Normal {
-		get => _normal;
-		set => _normal = value;
-	}
-
-	/// <summary>
-	/// The foreground and background color for text when the view has the focus.
-	/// </summary>
-	public Attribute Focus {
-		get => _focus;
-		set => _focus = value;
-	}
-
-	/// <summary>
-	/// The foreground and background color for text when the view is highlighted (hot).
-	/// </summary>
-	public Attribute HotNormal {
-		get => _hotNormal;
-		set => _hotNormal = value;
-	}
-
-	/// <summary>
-	/// The foreground and background color for text when the view is highlighted (hot) and has focus.
-	/// </summary>
-	public Attribute HotFocus {
-		get => _hotFocus;
-		set => _hotFocus = value;
-	}
-
-	/// <summary>
-	/// The default foreground and background color for text, when the view is disabled.
-	/// </summary>
-	public Attribute Disabled {
-		get => _disabled;
-		set => _disabled = value;
-	}
-
-	/// <summary>
-	/// Compares two <see cref="ColorScheme"/> objects for equality.
-	/// </summary>
-	/// <param name="obj"></param>
-	/// <returns>true if the two objects are equal</returns>
-	public override bool Equals (object obj) => Equals (obj as ColorScheme);
-
-	/// <summary>
-	/// Compares two <see cref="ColorScheme"/> objects for equality.
-	/// </summary>
-	/// <param name="other"></param>
-	/// <returns>true if the two objects are equal</returns>
-	public bool Equals (ColorScheme other) => other != null &&
-						EqualityComparer<Attribute>.Default.Equals (_normal, other._normal) &&
-						EqualityComparer<Attribute>.Default.Equals (_focus, other._focus) &&
-						EqualityComparer<Attribute>.Default.Equals (_hotNormal, other._hotNormal) &&
-						EqualityComparer<Attribute>.Default.Equals (_hotFocus, other._hotFocus) &&
-						EqualityComparer<Attribute>.Default.Equals (_disabled, other._disabled);
-
-	/// <summary>
-	/// Returns a hashcode for this instance.
-	/// </summary>
-	/// <returns>hashcode for this instance</returns>
-	public override int GetHashCode ()
-	{
-		int hashCode = -1242460230;
-		hashCode = hashCode * -1521134295 + _normal.GetHashCode ();
-		hashCode = hashCode * -1521134295 + _focus.GetHashCode ();
-		hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode ();
-		hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode ();
-		hashCode = hashCode * -1521134295 + _disabled.GetHashCode ();
-		return hashCode;
-	}
-
-	/// <summary>
-	/// Compares two <see cref="ColorScheme"/> objects for equality.
-	/// </summary>
-	/// <param name="left"></param>
-	/// <param name="right"></param>
-	/// <returns><c>true</c> if the two objects are equivalent</returns>
-	public static bool operator == (ColorScheme left, ColorScheme right) => EqualityComparer<ColorScheme>.Default.Equals (left, right);
-
-	/// <summary>
-	/// Compares two <see cref="ColorScheme"/> objects for inequality.
-	/// </summary>
-	/// <param name="left"></param>
-	/// <param name="right"></param>
-	/// <returns><c>true</c> if the two objects are not equivalent</returns>
-	public static bool operator != (ColorScheme left, ColorScheme right) => !(left == right);
+		// Note: Unit tests are dependent on this format
+		$"[{Foreground},{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 {
-	class SchemeNameComparerIgnoreCase : IEqualityComparer<string> {
-		public bool Equals (string x, string y)
-		{
-			if (x != null && y != null) {
-				return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase);
-			}
-			return false;
-		}
-
-		public int GetHashCode (string obj) => 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. 
-		typeof (Colors).GetProperties ()
-				.Where (p => p.PropertyType == typeof (ColorScheme))
-				.Select (p => new KeyValuePair<string, ColorScheme> (p.Name, new ColorScheme ()))
-				.ToDictionary (t => t.Key, t => t.Value, new SchemeNameComparerIgnoreCase ());
-
-	/// <summary>
-	/// The application Toplevel color scheme, for the default Toplevel views.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["TopLevel"];</c>
-	/// </para>
-	/// </remarks>
-	public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); }
-
-	/// <summary>
-	/// The base color scheme, for the default Toplevel views.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Base"];</c>
-	/// </para>
-	/// </remarks>
-	public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); }
-
-	/// <summary>
-	/// The dialog color scheme, for standard popup dialog boxes
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Dialog"];</c>
-	/// </para>
-	/// </remarks>
-	public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); }
-
-	/// <summary>
-	/// The menu bar color
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Menu"];</c>
-	/// </para>
-	/// </remarks>
-	public static ColorScheme Menu { get => GetColorScheme (); set => SetColorScheme (value); }
-
-	/// <summary>
-	/// The color scheme for showing errors.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Error"];</c>
-	/// </para>
-	/// </remarks>
-	public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); }
-
-	static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) => ColorSchemes [schemeBeingSet];
-
-	static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null)
-	{
-		ColorSchemes [schemeBeingSet] = colorScheme;
-		colorScheme._schemeBeingSet = schemeBeingSet;
-	}
-
-	/// <summary>
-	/// Provides the defined <see cref="ColorScheme"/>s.
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
-	[JsonConverter (typeof (DictionaryJsonConverter<ColorScheme>))]
-	public static Dictionary<string, ColorScheme> ColorSchemes { get; private set; }
-}

+ 246 - 0
Terminal.Gui/Drawing/ColorScheme.cs

@@ -0,0 +1,246 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Defines a standard set of <see cref="Attribute"/>s for common visible elements in a <see cref="View"/>.
+/// </summary>
+/// <remarks>
+/// <para>
+/// ColorScheme objects are immutable. Once constructed, the properties cannot be changed.
+/// To change a ColorScheme, create a new one with the desired values,
+/// using the <see cref="ColorScheme(ColorScheme)"/> constructor.
+/// </para>
+/// <para>
+/// See also: <see cref="Colors.ColorSchemes"/>.
+/// </para>
+/// </remarks>
+[JsonConverter (typeof (ColorSchemeJsonConverter))]
+public class ColorScheme : IEquatable<ColorScheme> {
+	readonly Attribute _disabled = Attribute.Default;
+	readonly Attribute _focus = Attribute.Default;
+	readonly Attribute _hotFocus = Attribute.Default;
+	readonly Attribute _hotNormal = Attribute.Default;
+	readonly Attribute _normal = Attribute.Default;
+
+	/// <summary>
+	/// Creates a new instance set to the default colors (see <see cref="Attribute.Default"/>).
+	/// </summary>
+	public ColorScheme () : this (Attribute.Default) { }
+
+	/// <summary>
+	/// Creates a new instance, initialized with the values from <paramref name="scheme"/>.
+	/// </summary>
+	/// <param name="scheme">The scheme to initialize the new instance with.</param>
+	public ColorScheme (ColorScheme scheme)
+	{
+		if (scheme == null) {
+			throw new ArgumentNullException (nameof (scheme));
+		}
+		_normal = scheme.Normal;
+		_focus = scheme.Focus;
+		_hotNormal = scheme.HotNormal;
+		_disabled = scheme.Disabled;
+		_hotFocus = scheme.HotFocus;
+	}
+
+	/// <summary>
+	/// Creates a new instance, initialized with the values from <paramref name="attribute"/>.
+	/// </summary>
+	/// <param name="attribute">The attribute to initialize the new instance with.</param>
+	public ColorScheme (Attribute attribute)
+	{
+		_normal = attribute;
+		_focus = attribute;
+		_hotNormal = attribute;
+		_disabled = attribute;
+		_hotFocus = attribute;
+	}
+
+	/// <summary>
+	/// The foreground and background color for text when the view is not focused, hot, or disabled.
+	/// </summary>
+	public Attribute Normal {
+		get => _normal;
+		init => _normal = value;
+	}
+
+	/// <summary>
+	/// The foreground and background color for text when the view has the focus.
+	/// </summary>
+	public Attribute Focus {
+		get => _focus;
+		init => _focus = value;
+	}
+
+	/// <summary>
+	/// The foreground and background color for text in a non-focused view that indicates a <see cref="View.HotKey"/>.
+	/// </summary>
+	public Attribute HotNormal {
+		get => _hotNormal;
+		init => _hotNormal = value;
+	}
+
+	/// <summary>
+	/// The foreground and background color for for text in a focused view that indicates a <see cref="View.HotKey"/>.
+	/// </summary>
+	public Attribute HotFocus {
+		get => _hotFocus;
+		init => _hotFocus = value;
+	}
+
+	/// <summary>
+	/// The default foreground and background color for text when the view is disabled.
+	/// </summary>
+	public Attribute Disabled {
+		get => _disabled;
+		init => _disabled = value;
+	}
+
+	/// <summary>
+	/// Compares two <see cref="ColorScheme"/> objects for equality.
+	/// </summary>
+	/// <param name="other"></param>
+	/// <returns>true if the two objects are equal</returns>
+	public bool Equals (ColorScheme other) => other != null &&
+	                                          EqualityComparer<Attribute>.Default.Equals (_normal, other._normal) &&
+	                                          EqualityComparer<Attribute>.Default.Equals (_focus, other._focus) &&
+	                                          EqualityComparer<Attribute>.Default.Equals (_hotNormal, other._hotNormal) &&
+	                                          EqualityComparer<Attribute>.Default.Equals (_hotFocus, other._hotFocus) &&
+	                                          EqualityComparer<Attribute>.Default.Equals (_disabled, other._disabled);
+
+	/// <summary>
+	/// Compares two <see cref="ColorScheme"/> objects for equality.
+	/// </summary>
+	/// <param name="obj"></param>
+	/// <returns>true if the two objects are equal</returns>
+	public override bool Equals (object obj) => Equals (obj is ColorScheme ? (ColorScheme)obj : default);
+
+	/// <summary>
+	/// Returns a hashcode for this instance.
+	/// </summary>
+	/// <returns>hashcode for this instance</returns>
+	public override int GetHashCode ()
+	{
+		var hashCode = -1242460230;
+		hashCode = hashCode * -1521134295 + _normal.GetHashCode ();
+		hashCode = hashCode * -1521134295 + _focus.GetHashCode ();
+		hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode ();
+		hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode ();
+		hashCode = hashCode * -1521134295 + _disabled.GetHashCode ();
+		return hashCode;
+	}
+
+	/// <summary>
+	/// Compares two <see cref="ColorScheme"/> objects for equality.
+	/// </summary>
+	/// <param name="left"></param>
+	/// <param name="right"></param>
+	/// <returns><c>true</c> if the two objects are equivalent</returns>
+	public static bool operator == (ColorScheme left, ColorScheme right) => EqualityComparer<ColorScheme>.Default.Equals (left, right);
+
+	/// <summary>
+	/// Compares two <see cref="ColorScheme"/> objects for inequality.
+	/// </summary>
+	/// <param name="left"></param>
+	/// <param name="right"></param>
+	/// <returns><c>true</c> if the two objects are not equivalent</returns>
+	public static bool operator != (ColorScheme left, ColorScheme right) => !(left == right);
+}
+
+/// <summary>
+/// Holds the <see cref="ColorScheme"/>s that define the <see cref="Attribute"/>s that are used by views to render themselves.
+/// </summary>
+public static class Colors {
+	static Colors () => Reset ();
+	/// <summary>
+	/// Gets a dictionary of defined <see cref="ColorScheme"/> objects.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// The <see cref="ColorSchemes"/> dictionary includes the following keys, by default:
+	/// <list type="table">
+	/// <listheader>
+	///         <term>Built-in Color Scheme</term>
+	///         <description>Description</description>
+	/// </listheader>
+	/// <item>
+	///         <term>
+	///         Base
+	///         </term>
+	///         <description>
+	///         The base color scheme used for most Views.
+	///         </description>
+	/// </item>
+	/// <item>
+	///         <term>
+	///         TopLevel
+	///         </term>
+	///         <description>
+	///         The application Toplevel color scheme; used for the <see cref="Toplevel"/> View.
+	///         </description>
+	/// </item>
+	/// <item>
+	///         <term>
+	///         Dialog
+	///         </term>
+	///         <description>
+	///         The dialog color scheme; used for <see cref="Dialog"/>, <see cref="MessageBox"/>, and other views dialog-like views.
+	///         </description>
+	/// </item> 
+	/// <item>
+	///         <term>
+	///         Menu
+	///         </term>
+	///         <description>
+	///         The menu color scheme; used for <see cref="MenuBar"/>, <see cref="ContextMenu"/>, and <see cref="StatusBar"/>. 
+	///         </description>
+	/// </item>
+	/// <item>
+	///         <term>
+	///         Error
+	///         </term>
+	///         <description>
+	///         The color scheme for showing errors, such as in <see cref="MessageBox.ErrorQuery(string, string, string[])"/>. 
+	///         </description>
+	/// </item>
+	/// </list>
+	/// </para>
+	/// <para>
+	/// Changing the values of an entry in this dictionary will affect all views that use the scheme.
+	/// </para>
+	/// <para>
+	/// <see cref="ConfigurationManager"/> can be used to override the default values for these schemes and add additional schemes.
+	/// See <see cref="ConfigurationManager.Themes"/>.
+	/// </para>
+	/// </remarks>
+	[SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
+	[JsonConverter (typeof (DictionaryJsonConverter<ColorScheme>))]
+	public static Dictionary<string, ColorScheme> ColorSchemes { get; private set; } // Serialization requires this to have a setter (private set;)
+
+	/// <summary>
+	/// Resets the <see cref="ColorSchemes"/> dictionary to the default values.
+	/// </summary>
+	public static Dictionary<string, ColorScheme> Reset () =>
+		ColorSchemes = new Dictionary<string, ColorScheme> (comparer: new SchemeNameComparerIgnoreCase ()) {
+			{ "TopLevel", new ColorScheme () },
+			{ "Base", new ColorScheme () },
+			{ "Dialog", new ColorScheme () },
+			{ "Menu", new ColorScheme () },
+			{ "Error", new ColorScheme () },
+		};
+
+	class SchemeNameComparerIgnoreCase : IEqualityComparer<string> {
+		public bool Equals (string x, string y)
+		{
+			if (x != null && y != null) {
+				return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase);
+			}
+			return false;
+		}
+
+		public int GetHashCode (string obj) => obj.ToLowerInvariant ().GetHashCode ();
+	}
+}

+ 2 - 2
Terminal.Gui/Drawing/Thickness.cs

@@ -109,8 +109,8 @@ namespace Terminal.Gui {
 		/// the rectangle described by <see cref="GetInside(Rect)"/>.
 		/// </summary>
 		/// <param name="outside">Describes the location and size of the rectangle that contains the thickness.</param>
-		/// <param name="x"></param>
-		/// <param name="y"></param>
+		/// <param name="x">The x coord to check.</param>
+		/// <param name="y">The y coord to check.</param>
 		/// <returns><see langword="true"/> if the specified coordinate is within the thickness; <see langword="false"/> otherwise.</returns>
 		public bool Contains (Rect outside, int x, int y)
 		{

+ 16 - 7
Terminal.Gui/Resources/Strings.Designer.cs

@@ -61,7 +61,7 @@ namespace Terminal.Gui.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Cancel.
+        ///   Looks up a localized string similar to _Cancel.
         /// </summary>
         internal static string btnCancel {
             get {
@@ -70,7 +70,7 @@ namespace Terminal.Gui.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to No.
+        ///   Looks up a localized string similar to _No.
         /// </summary>
         internal static string btnNo {
             get {
@@ -79,7 +79,7 @@ namespace Terminal.Gui.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to OK.
+        ///   Looks up a localized string similar to _OK.
         /// </summary>
         internal static string btnOk {
             get {
@@ -88,7 +88,7 @@ namespace Terminal.Gui.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Open.
+        ///   Looks up a localized string similar to O_pen.
         /// </summary>
         internal static string btnOpen {
             get {
@@ -97,7 +97,7 @@ namespace Terminal.Gui.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Save.
+        ///   Looks up a localized string similar to _Save.
         /// </summary>
         internal static string btnSave {
             get {
@@ -106,7 +106,7 @@ namespace Terminal.Gui.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Save as.
+        ///   Looks up a localized string similar to Save _as.
         /// </summary>
         internal static string btnSaveAs {
             get {
@@ -115,7 +115,7 @@ namespace Terminal.Gui.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Yes.
+        ///   Looks up a localized string similar to _Yes.
         /// </summary>
         internal static string btnYes {
             get {
@@ -186,6 +186,15 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Date Picker.
+        /// </summary>
+        internal static string dpTitle {
+            get {
+                return ResourceManager.GetString("dpTitle", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Any Files.
         /// </summary>

+ 3 - 0
Terminal.Gui/Resources/Strings.fr-FR.resx

@@ -177,4 +177,7 @@
   <data name="btnOpen" xml:space="preserve">
     <value>Ouvrir</value>
   </data>
+  <data name="dpTitle" xml:space="preserve">
+    <value>Sélecteur de Date</value>
+  </data>
 </root>

+ 3 - 0
Terminal.Gui/Resources/Strings.ja-JP.resx

@@ -273,4 +273,7 @@
   <data name="fdCtxSortDesc" xml:space="preserve">
     <value>{0}で降順ソート (_S)</value>
   </data>
+  <data name="dpTitle" xml:space="preserve">
+    <value>日付ピッカー</value>
+  </data>
 </root>

+ 3 - 0
Terminal.Gui/Resources/Strings.pt-PT.resx

@@ -177,4 +177,7 @@
   <data name="btnOpen" xml:space="preserve">
     <value>Abrir</value>
   </data>
+  <data name="dpTitle" xml:space="preserve">
+    <value>Seletor de Data</value>
+  </data>
 </root>

+ 10 - 7
Terminal.Gui/Resources/Strings.resx

@@ -227,7 +227,7 @@
     <value>New Folder</value>
   </data>
   <data name="btnNo" xml:space="preserve">
-    <value>No</value>
+    <value>_No</value>
   </data>
   <data name="fdRenameFailedTitle" xml:space="preserve">
     <value>Rename Failed</value>
@@ -239,25 +239,25 @@
     <value>Rename</value>
   </data>
   <data name="btnYes" xml:space="preserve">
-    <value>Yes</value>
+    <value>_Yes</value>
   </data>
   <data name="fdExisting" xml:space="preserve">
     <value>Existing</value>
   </data>
   <data name="btnOpen" xml:space="preserve">
-    <value>Open</value>
+    <value>O_pen</value>
   </data>
   <data name="btnSave" xml:space="preserve">
-    <value>Save</value>
+    <value>_Save</value>
   </data>
   <data name="btnSaveAs" xml:space="preserve">
-    <value>Save as</value>
+    <value>Save _as</value>
   </data>
   <data name="btnOk" xml:space="preserve">
-    <value>OK</value>
+    <value>_OK</value>
   </data>
   <data name="btnCancel" xml:space="preserve">
-    <value>Cancel</value>
+    <value>_Cancel</value>
   </data>
   <data name="fdCtxDelete" xml:space="preserve">
     <value>_Delete</value>
@@ -277,4 +277,7 @@
   <data name="fdCtxSortDesc" xml:space="preserve">
     <value>_Sort {0} DESC</value>
   </data>
+  <data name="dpTitle" xml:space="preserve">
+    <value>Date Picker</value>
+  </data>
 </root>

+ 3 - 0
Terminal.Gui/Resources/Strings.zh-Hans.resx

@@ -273,4 +273,7 @@
   <data name="fdCtxSortDesc" xml:space="preserve">
     <value>{0}逆序排序 (_S)</value>
   </data>
+  <data name="dpTitle" xml:space="preserve">
+    <value>日期选择器</value>
+  </data>
 </root>

+ 1 - 1
Terminal.Gui/Terminal.Gui.csproj

@@ -40,7 +40,7 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
     <PackageReference Include="System.IO.Abstractions" Version="20.0.4" />
-    <PackageReference Include="System.Text.Json" Version="8.0.0" />
+    <PackageReference Include="System.Text.Json" Version="8.0.1" />
     <PackageReference Include="System.Management" Version="8.0.0" />
     <PackageReference Include="Wcwidth" Version="2.0.0" />
     <!-- Enable Nuget Source Link for github -->

+ 1 - 0
Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs

@@ -83,6 +83,7 @@ namespace Terminal.Gui {
 		public override void GenerateSuggestions (AutocompleteContext context)
 		{
 			if (_suspendSuggestions) {
+				_suspendSuggestions = false;
 				return;
 			}
 			base.GenerateSuggestions (context);

+ 1 - 12
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs

@@ -23,17 +23,6 @@ namespace Terminal.Gui {
 				WantMousePositionReports = true;
 			}
 
-			public override Rect Frame {
-				get => base.Frame;
-				set {
-					base.Frame = value;
-					X = value.X;
-					Y = value.Y;
-					Width = value.Width;
-					Height = value.Height;
-				}
-			}
-
 			public override void OnDrawContent (Rect contentArea)
 			{
 				if (autocomplete.LastPopupPos == null) {
@@ -135,7 +124,7 @@ namespace Terminal.Gui {
 		public override ColorScheme ColorScheme {
 			get {
 				if (colorScheme == null) {
-					colorScheme = Colors.Menu;
+					colorScheme = Colors.ColorSchemes ["Menu"];
 				}
 				return colorScheme;
 			}

+ 81 - 40
Terminal.Gui/Text/TextFormatter.cs

@@ -50,23 +50,50 @@ namespace Terminal.Gui {
 		Justified
 	}
 
-	/// TextDirection  [H] = Horizontal  [V] = Vertical
-	/// =============
-	/// LeftRight_TopBottom [H] Normal
-	/// TopBottom_LeftRight [V] Normal
-	/// 
-	/// RightLeft_TopBottom [H] Invert Text
-	/// TopBottom_RightLeft [V] Invert Lines
-	/// 
-	/// LeftRight_BottomTop [H] Invert Lines
-	/// BottomTop_LeftRight [V] Invert Text
-	/// 
-	/// RightLeft_BottomTop [H] Invert Text + Invert Lines
-	/// BottomTop_RightLeft [V] Invert Text + Invert Lines
-	///
 	/// <summary>
 	/// Text direction enumeration, controls how text is displayed.
 	/// </summary>
+	/// <remarks>
+	/// <para>TextDirection  [H] = Horizontal  [V] = Vertical</para>
+	/// <table>
+	///   <tr>
+	///     <th>TextDirection</th>
+	///     <th>Description</th>
+	///   </tr>
+	///   <tr>
+	///     <td>LeftRight_TopBottom [H]</td>
+	///     <td>Normal</td>
+	///   </tr>
+	///   <tr>
+	///     <td>TopBottom_LeftRight [V]</td>
+	///     <td>Normal</td>
+	///   </tr>
+	///   <tr>
+	///     <td>RightLeft_TopBottom [H]</td>
+	///     <td>Invert Text</td>
+	///   </tr>
+	///   <tr>
+	///     <td>TopBottom_RightLeft [V]</td>
+	///     <td>Invert Lines</td>
+	///   </tr>
+	///   <tr>
+	///     <td>LeftRight_BottomTop [H]</td>
+	///     <td>Invert Lines</td>
+	///   </tr>
+	///   <tr>
+	///     <td>BottomTop_LeftRight [V]</td>
+	///     <td>Invert Text</td>
+	///   </tr>
+	///   <tr>
+	///     <td>RightLeft_BottomTop [H]</td>
+	///     <td>Invert Text + Invert Lines</td>
+	///   </tr>
+	///   <tr>
+	///     <td>BottomTop_RightLeft [V]</td>
+	///     <td>Invert Text + Invert Lines</td>
+	///   </tr>
+	/// </table>
+	/// </remarks>
 	public enum TextDirection {
 		/// <summary>
 		/// Normal horizontal direction.
@@ -933,12 +960,14 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="text">The text to look in.</param>
 		/// <param name="hotKeySpecifier">The HotKey specifier (e.g. '_') to look for.</param>
-		/// <param name="firstUpperCase">If <c>true</c> the legacy behavior of identifying the first upper case character as the HotKey will be enabled.
-		/// Regardless of the value of this parameter, <c>hotKeySpecifier</c> takes precedence.</param>
 		/// <param name="hotPos">Outputs the Rune index into <c>text</c>.</param>
 		/// <param name="hotKey">Outputs the hotKey. <see cref="Key.Empty"/> if not found.</param>
+		/// <param name="firstUpperCase">If <c>true</c> the legacy behavior of identifying the
+		/// first upper case character as the HotKey will be enabled.
+		/// Regardless of the value of this parameter, <c>hotKeySpecifier</c> takes precedence.
+		/// Defaults to <see langword="false"/>.</param>
 		/// <returns><c>true</c> if a HotKey was found; <c>false</c> otherwise.</returns>
-		public static bool FindHotKey (string text, Rune hotKeySpecifier, bool firstUpperCase, out int hotPos, out Key hotKey)
+		public static bool FindHotKey (string text, Rune hotKeySpecifier, out int hotPos, out Key hotKey, bool firstUpperCase = false)
 		{
 			if (string.IsNullOrEmpty (text) || hotKeySpecifier == (Rune)0xFFFF) {
 				hotPos = -1;
@@ -1075,7 +1104,7 @@ namespace Terminal.Gui {
 				_text = EnableNeedsFormat (value);
 
 				if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty)) {
-					Size = CalcRect (0, 0, _text, _textDirection, TabWidth).Size;
+					Size = CalcRect (0, 0, _text, Direction, TabWidth).Size;
 				}
 
 				//if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) {
@@ -1087,20 +1116,22 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Used by <see cref="Text"/> to resize the view's <see cref="View.Bounds"/> with the <see cref="Size"/>.
-		/// Setting <see cref="AutoSize"/> to true only work if the <see cref="View.Width"/> and <see cref="View.Height"/> are null or
-		///   <see cref="LayoutStyle.Absolute"/> values and doesn't work with <see cref="LayoutStyle.Computed"/> layout,
-		///   to avoid breaking the <see cref="Pos"/> and <see cref="Dim"/> settings.
+		/// Gets or sets whether the <see cref="Size"/> should be automatically changed to fit the <see cref="Text"/>.
 		/// </summary>
 		/// <remarks>
-		///   Auto size is ignored if the <see cref="TextAlignment.Justified"/> and <see cref="VerticalTextAlignment.Justified"/> are used.
+		/// <para>
+		/// Used by <see cref="View.AutoSize"/> to resize the view's <see cref="View.Bounds"/> to fit <see cref="Size"/>.
+		/// </para>
+		/// <para>
+		/// AutoSize is ignored if <see cref="TextAlignment.Justified"/> and <see cref="VerticalTextAlignment.Justified"/> are used.
+		/// </para>
 		/// </remarks>
 		public bool AutoSize {
 			get => _autoSize;
 			set {
 				_autoSize = EnableNeedsFormat (value);
 				if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
-					Size = CalcRect (0, 0, Text, _textDirection, TabWidth).Size;
+					Size = CalcRect (0, 0, _text, Direction, TabWidth).Size;
 				}
 			}
 		}
@@ -1140,7 +1171,12 @@ namespace Terminal.Gui {
 		/// <value>The text vertical alignment.</value>
 		public TextDirection Direction {
 			get => _textDirection;
-			set => _textDirection = EnableNeedsFormat (value);
+			set {
+				_textDirection = EnableNeedsFormat (value);
+				if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
+					Size = CalcRect (0, 0, Text, Direction, TabWidth).Size;
+				}
+			}
 		}
 
 		/// <summary>
@@ -1204,7 +1240,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Allows word wrap the to fit the available container width.
+		/// Gets or sets whether word wrap will be used to fit <see cref="Text"/> to <see cref="Size"/>.
 		/// </summary>
 		public bool WordWrap {
 			get => _wordWrap;
@@ -1212,16 +1248,16 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets or sets the size of the area the text will be constrained to when formatted.
+		/// Gets or sets the size <see cref="Text"/> will be constrained to when formatted.
 		/// </summary>
 		/// <remarks>
-		/// Does not return the size the formatted text; just the value that was set.
+		/// Does not return the size of the formatted text but the size that will be used to constrain the text when formatted.
 		/// </remarks>
 		public Size Size {
 			get => _size;
 			set {
 				if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
-					_size = EnableNeedsFormat (CalcRect (0, 0, Text, _textDirection, TabWidth).Size);
+					_size = EnableNeedsFormat (CalcRect (0, 0, Text, Direction, TabWidth).Size);
 				} else {
 					_size = EnableNeedsFormat (value);
 				}
@@ -1291,16 +1327,16 @@ namespace Terminal.Gui {
 					NeedsFormat = false;
 					return _lines;
 				}
-				
+
 				if (NeedsFormat) {
 					var shown_text = _text;
-					if (FindHotKey (_text, HotKeySpecifier, true, out _hotKeyPos, out var newHotKey)) {
+					if (FindHotKey (_text, HotKeySpecifier, out _hotKeyPos, out var newHotKey)) {
 						HotKey = newHotKey;
 						shown_text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier);
 						shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos);
 					}
 
-					if (IsVerticalDirection (_textDirection)) {
+					if (IsVerticalDirection (Direction)) {
 						var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth);
 						_lines = Format (shown_text, Size.Height, VerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth && WordWrap,
 							PreserveTrailingSpaces, TabWidth, Direction, MultiLine);
@@ -1325,11 +1361,16 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute, Rect, bool, ConsoleDriver)"/> is called.
-		/// If it is <c>false</c> when Draw is called, the Draw call will be faster.
+		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text. 
 		/// </summary>
 		/// <remarks>
 		/// <para>
+		/// If <c>false</c> when Draw is called, the Draw call will be faster.
+		/// </para>
+		/// <para>
+		/// Used by <see cref="Draw(Rect, Attribute, Attribute, Rect, bool, ConsoleDriver)"/>
+		/// </para>
+		/// <para>
 		/// This is set to true when the properties of <see cref="TextFormatter"/> are set.
 		/// </para>
 		/// </remarks>
@@ -1373,7 +1414,7 @@ namespace Terminal.Gui {
 			foreach (var line in Lines) {
 				sb.AppendLine (line);
 			}
-			return sb.ToString ();
+			return sb.ToString ().TrimEnd (Environment.NewLine.ToCharArray ());
 		}
 
 		/// <summary>
@@ -1400,7 +1441,7 @@ namespace Terminal.Gui {
 			// Use "Lines" to ensure a Format (don't use "lines"))
 
 			var linesFormated = Lines;
-			switch (_textDirection) {
+			switch (Direction) {
 			case TextDirection.TopBottom_RightLeft:
 			case TextDirection.LeftRight_BottomTop:
 			case TextDirection.RightLeft_BottomTop:
@@ -1409,7 +1450,7 @@ namespace Terminal.Gui {
 				break;
 			}
 
-			var isVertical = IsVerticalDirection (_textDirection);
+			var isVertical = IsVerticalDirection (Direction);
 			var maxBounds = bounds;
 			if (driver != null) {
 				maxBounds = containerBounds == default
@@ -1441,7 +1482,7 @@ namespace Terminal.Gui {
 
 				var runes = _lines [line].ToRunes ();
 
-				switch (_textDirection) {
+				switch (Direction) {
 				case TextDirection.RightLeft_BottomTop:
 				case TextDirection.RightLeft_TopBottom:
 				case TextDirection.BottomTop_LeftRight:
@@ -1454,7 +1495,7 @@ namespace Terminal.Gui {
 
 				int x, y;
 				// Horizontal Alignment
-				if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (_textDirection))) {
+				if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (Direction))) {
 					if (isVertical) {
 						var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
 						x = bounds.Right - runesWidth;
@@ -1487,7 +1528,7 @@ namespace Terminal.Gui {
 				}
 
 				// Vertical Alignment
-				if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (_textDirection))) {
+				if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (Direction))) {
 					if (isVertical) {
 						y = bounds.Bottom - runes.Length;
 					} else {

+ 177 - 0
Terminal.Gui/View/Adornment/Adornment.cs

@@ -0,0 +1,177 @@
+using System;
+
+namespace Terminal.Gui;
+
+// TODO: v2 - Missing 3D effect - 3D effects will be drawn by a mechanism separate from Adornments
+// TODO: v2 - If a Adornment has focus, navigation keys (e.g Command.NextView) should cycle through SubViews of the Adornments
+// QUESTION: How does a user navigate out of an Adornment to another Adornment, or back into the Parent's SubViews?
+
+/// <summary>
+/// Adornments are a special form of <see cref="View"/> that appear outside of the <see cref="View.Bounds"/>:
+/// <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>. They are defined using the <see cref="Thickness"/>
+/// class, which specifies the thickness of the sides of a rectangle. 
+/// </summary>
+/// <remarsk>
+/// <para>
+/// There is no prevision for creating additional subclasses of Adornment. It is not abstract to enable unit testing.
+/// </para>
+/// <para>
+/// Each of <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/> can be customized.
+/// </para>
+/// </remarsk>
+public class Adornment : View {
+	/// <inheritdoc />
+	public Adornment () { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */ }
+
+	/// <summary>
+	/// Constructs a new adornment for the view specified by <paramref name="parent"/>.
+	/// </summary>
+	/// <param name="parent"></param>
+	public Adornment (View parent) => Parent = parent;
+
+	Thickness _thickness = Thickness.Empty;
+
+	/// <summary>
+	/// The Parent of this Adornment (the View this Adornment surrounds).
+	/// </summary>
+	/// <remarks>
+	/// Adornments are distinguished from typical View classes in that they are not sub-views,
+	/// but have a parent/child relationship with their containing View.
+	/// </remarks>
+	public View Parent { get; set; }
+
+	/// <summary>
+	/// Adornments cannot be used as sub-views (see <see cref="Parent"/>); this method always throws an <see cref="InvalidOperationException"/>.
+	/// TODO: Are we sure?
+	/// </summary>
+	public override View SuperView {
+		get => null;
+		set => throw new NotImplementedException ();
+	}
+
+	/// <summary>
+	/// Adornments only render to their <see cref="Parent"/>'s or Parent's SuperView's LineCanvas,
+	/// so setting this property throws an <see cref="InvalidOperationException"/>.
+	/// </summary>
+	public override bool SuperViewRendersLineCanvas {
+		get => false; // throw new NotImplementedException ();
+		set => throw new NotImplementedException ();
+	}
+
+	/// <summary>
+	/// Defines the rectangle that the <see cref="Adornment"/> will use to draw its content.
+	/// </summary>
+	public Thickness Thickness {
+		get => _thickness;
+		set {
+			var prev = _thickness;
+			_thickness = value;
+			if (prev != _thickness) {
+
+				Parent?.LayoutAdornments ();
+				OnThicknessChanged (prev);
+			}
+
+		}
+	}
+
+	/// <summary>
+	/// Gets the rectangle that describes the inner area of the Adornment. The Location is always (0,0).
+	/// </summary>
+	public override Rect Bounds {
+		get => Thickness?.GetInside (new Rect (Point.Empty, Frame.Size)) ?? new Rect (Point.Empty, Frame.Size);
+		set => throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
+	}
+
+	internal override Adornment CreateAdornment (Type adornmentType)
+	{
+		/* Do nothing - Adornments do not have Adornments */
+		return null;
+	}
+
+	internal override void LayoutAdornments ()
+	{
+		/* Do nothing - Adornments do not have Adornments */
+	}
+
+	/// <inheritdoc/>
+	public override void BoundsToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
+	{
+		// Adornments are *Children* of a View, not SubViews. Thus View.BoundsToScreen will not work.
+		// To get the screen-relative coordinates of a Adornment, we need to know who
+		// the Parent is
+		var parentFrame = Parent?.Frame ?? Frame;
+		rrow = row + parentFrame.Y;
+		rcol = col + parentFrame.X;
+
+		// We now have rcol/rrow in coordinates relative to our View's SuperView. If our View's SuperView has
+		// a SuperView, keep going...
+		Parent?.SuperView?.BoundsToScreen (rcol, rrow, out rcol, out rrow, clipped);
+	}
+
+	/// <inheritdoc/>
+	public override Rect FrameToScreen ()
+	{
+		// Adornments are *Children* of a View, not SubViews. Thus View.FrameToScreen will not work.
+		// To get the screen-relative coordinates of a Adornment, we need to know who
+		// the Parent is
+		var ret = Parent?.Frame ?? Frame;
+		ret.Size = Frame.Size;
+
+		ret.Location = Parent?.FrameToScreen ().Location ?? ret.Location;
+
+		// We now have coordinates relative to our View. If our View's SuperView has
+		// a SuperView, keep going...
+		return ret;
+	}
+
+	/// <summary>
+	/// Does nothing for Adornment
+	/// </summary>
+	/// <returns></returns>
+	public override bool OnDrawAdornments () => false;
+
+	/// <summary>
+	/// Does nothing for Adornment
+	/// </summary>
+	/// <returns></returns>
+	public override bool OnRenderLineCanvas () => false;
+	
+	/// <summary>
+	/// Redraws the Adornments that comprise the <see cref="Adornment"/>.
+	/// </summary>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		if (Thickness == Thickness.Empty) {
+			return;
+		}
+
+		var screenBounds = BoundsToScreen (Frame);
+
+		Attribute normalAttr = GetNormalColor ();
+
+		// This just draws/clears the thickness, not the insides.
+		Driver.SetAttribute (normalAttr);
+		Thickness.Draw (screenBounds, (string)(Data ?? string.Empty));
+
+		if (!string.IsNullOrEmpty (TextFormatter.Text)) {
+			if (TextFormatter != null) {
+				TextFormatter.Size = Frame.Size;
+				TextFormatter.NeedsFormat = true;
+			}
+		}
+
+		TextFormatter?.Draw (screenBounds, normalAttr, normalAttr, Rect.Empty, false);
+		//base.OnDrawContent (contentArea);
+	}
+
+	/// <summary>
+	/// Called whenever the <see cref="Thickness"/> property changes.
+	/// </summary>
+	public virtual void OnThicknessChanged (Thickness previousThickness) => ThicknessChanged?.Invoke (this, new ThicknessEventArgs { Thickness = Thickness, PreviousThickness = previousThickness });
+
+	/// <summary>
+	/// Fired whenever the <see cref="Thickness"/> property changes.
+	/// </summary>
+	public event EventHandler<ThicknessEventArgs> ThicknessChanged;
+}

+ 345 - 0
Terminal.Gui/View/Adornment/Border.cs

@@ -0,0 +1,345 @@
+using System;
+using System.Linq;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// The Border for a <see cref="View"/>. 
+/// </summary>
+/// <remarks>
+/// <para>
+/// Renders a border around the view with the <see cref="View.Title"/>. A border using <see cref="LineStyle"/>
+/// will be drawn on the sides of <see cref="Thickness"/> that are greater than zero. 
+/// </para>
+/// <para>
+/// The <see cref="View.Title"/> of <see cref="Adornment.Parent"/> will be drawn based on the value of <see cref="Thickness.Top"/>:
+/// </para>
+/// <para>
+/// If <c>1</c>:
+/// <code>
+/// ┌┤1234├──┐
+/// │        │
+/// └────────┘
+/// </code>
+/// </para>
+/// <para>
+/// If <c>2</c>:
+/// <code>
+///  ┌────┐
+/// ┌┤1234├──┐
+/// │        │
+/// └────────┘
+/// </code>
+/// </para>
+/// <para>
+/// If <c>3</c>:
+/// <code>
+///  ┌────┐
+/// ┌┤1234├──┐
+/// │└────┘  │
+/// │        │
+/// └────────┘
+/// </code>
+/// </para>
+/// <para/>
+/// <para>
+/// See the <see cref="Adornment"/> class. 
+/// </para>
+/// </remarks>
+public class Border : Adornment {
+	LineStyle? _lineStyle = null;
+
+	/// <inheritdoc />
+	public Border () { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */ }
+
+	/// <inheritdoc />
+	public Border (View parent) : base (parent) { /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */ }
+
+	/// <summary>
+	/// The color scheme for the Border. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/> scheme.
+	/// color scheme.
+	/// </summary>
+	public override ColorScheme ColorScheme {
+		get {
+			if (base.ColorScheme != null) {
+				return base.ColorScheme;
+			}
+			return Parent?.ColorScheme;
+		}
+		set {
+			base.ColorScheme = value;
+			Parent?.SetNeedsDisplay ();
+		}
+	}
+
+	/// <summary>
+	/// Sets the style of the border by changing the <see cref="Thickness"/>. This is a helper API for
+	/// setting the <see cref="Thickness"/> to <c>(1,1,1,1)</c> and setting the line style of the
+	/// views that comprise the border. If set to <see cref="LineStyle.None"/> no border will be drawn.
+	/// </summary>
+	public LineStyle LineStyle {
+		get {
+			if (_lineStyle.HasValue) {
+				return _lineStyle.Value;
+			}
+			// TODO: Make Border.LineStyle inherit from the SuperView hierarchy
+			// TODO: Right now, Window and FrameView use CM to set BorderStyle, which negates
+			// TODO: all this.
+			return Parent.SuperView?.BorderStyle ?? LineStyle.None;
+		}
+		set => _lineStyle = value;
+	}
+
+	/// <inheritdoc />
+	public override void OnDrawContent (Rect contentArea)
+	{
+		base.OnDrawContent (contentArea);
+
+		if (Thickness == Thickness.Empty) {
+			return;
+		}
+
+		//Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal);
+		var screenBounds = BoundsToScreen (Frame);
+
+		//OnDrawSubviews (bounds); 
+
+		// TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title)
+
+		// The border adornment (and title) are drawn at the outermost edge of border; 
+		// For Border
+		// ...thickness extends outward (border/title is always as far in as possible)
+		var borderBounds = new Rect (
+			screenBounds.X + Math.Max (0, Thickness.Left - 1),
+			screenBounds.Y + Math.Max (0, Thickness.Top - 1),
+			Math.Max (0, screenBounds.Width - Math.Max (0, Math.Max (0, Thickness.Left - 1) + Math.Max (0, Thickness.Right - 1))),
+			Math.Max (0, screenBounds.Height - Math.Max (0, Math.Max (0, Thickness.Top - 1) + Math.Max (0, Thickness.Bottom - 1))));
+
+		var topTitleLineY = borderBounds.Y;
+		var titleY = borderBounds.Y;
+		var titleBarsLength = 0; // the little vertical thingies
+		var maxTitleWidth = Math.Min (Parent.Title.GetColumns (), Math.Min (screenBounds.Width - 4, borderBounds.Width - 4));
+		var sideLineLength = borderBounds.Height;
+		var canDrawBorder = borderBounds.Width > 0 && borderBounds.Height > 0;
+
+		if (!string.IsNullOrEmpty (Parent?.Title)) {
+			if (Thickness.Top == 2) {
+				topTitleLineY = borderBounds.Y - 1;
+				titleY = topTitleLineY + 1;
+				titleBarsLength = 2;
+			}
+
+			// ┌────┐
+			//┌┘View└
+			//│
+			if (Thickness.Top == 3) {
+				topTitleLineY = borderBounds.Y - (Thickness.Top - 1);
+				titleY = topTitleLineY + 1;
+				titleBarsLength = 3;
+				sideLineLength++;
+			}
+
+			// ┌────┐
+			//┌┘View└
+			//│
+			if (Thickness.Top > 3) {
+				topTitleLineY = borderBounds.Y - 2;
+				titleY = topTitleLineY + 1;
+				titleBarsLength = 3;
+				sideLineLength++;
+			}
+
+		}
+
+		if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) {
+			var prevAttr = Driver.GetAttribute ();
+			Driver.SetAttribute (Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
+			DrawTitle (new Rect (borderBounds.X, titleY, maxTitleWidth, 1), Parent?.Title);
+			Driver.SetAttribute (prevAttr);
+		}
+
+		if (canDrawBorder && LineStyle != LineStyle.None) {
+			var lc = Parent?.LineCanvas;
+
+			var drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height > 1;
+			var drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
+			var drawBottom = Thickness.Bottom > 0 && Frame.Width > 1;
+			var drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0);
+
+			var prevAttr = Driver.GetAttribute ();
+			if (ColorScheme != null) {
+				Driver.SetAttribute (GetNormalColor ());
+			} else {
+				Driver.SetAttribute (Parent.GetNormalColor ());
+			}
+
+			if (drawTop) {
+				// ╔╡Title╞═════╗
+				// ╔╡╞═════╗
+				if (borderBounds.Width < 4 || string.IsNullOrEmpty (Parent?.Title)) {
+					// ╔╡╞╗ should be ╔══╗
+					lc.AddLine (new Point (borderBounds.Location.X, titleY), borderBounds.Width, Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+				} else {
+
+					// ┌────┐
+					//┌┘View└
+					//│
+					if (Thickness.Top == 2) {
+						lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+					}
+					// ┌────┐
+					//┌┘View└
+					//│
+					if (borderBounds.Width >= 4 && Thickness.Top > 2) {
+						lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+						lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY + 2), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+					}
+
+					// ╔╡Title╞═════╗
+					// Add a short horiz line for ╔╡
+					lc.AddLine (new Point (borderBounds.Location.X, titleY), 2, Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+					// Add a vert line for ╔╡
+					lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), titleBarsLength, Orientation.Vertical, LineStyle.Single, Driver.GetAttribute ());
+					// Add a vert line for ╞
+					lc.AddLine (new Point (borderBounds.X + 1 + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2) - 1, topTitleLineY), titleBarsLength, Orientation.Vertical, LineStyle.Single, Driver.GetAttribute ());
+					// Add the right hand line for ╞═════╗
+					lc.AddLine (new Point (borderBounds.X + 1 + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2) - 1, titleY), borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+				}
+			}
+			if (drawLeft) {
+				lc.AddLine (new Point (borderBounds.Location.X, titleY), sideLineLength, Orientation.Vertical, LineStyle, Driver.GetAttribute ());
+			}
+			if (drawBottom) {
+				lc.AddLine (new Point (borderBounds.X, borderBounds.Y + borderBounds.Height - 1), borderBounds.Width, Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+			}
+			if (drawRight) {
+				lc.AddLine (new Point (borderBounds.X + borderBounds.Width - 1, titleY), sideLineLength, Orientation.Vertical, LineStyle, Driver.GetAttribute ());
+			}
+			Driver.SetAttribute (prevAttr);
+
+			// TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
+			if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
+				// Top
+				var hruler = new Ruler { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
+				if (drawTop) {
+					hruler.Draw (new Point (screenBounds.X, screenBounds.Y));
+				}
+
+				// Redraw title 
+				if (drawTop && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) {
+					prevAttr = Driver.GetAttribute ();
+					if (ColorScheme != null) {
+						Driver.SetAttribute (HasFocus ? GetHotNormalColor () : GetNormalColor ());
+					} else {
+						Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ());
+					}
+					DrawTitle (new Rect (borderBounds.X, titleY, Parent.Title.GetColumns (), 1), Parent?.Title);
+					Driver.SetAttribute (prevAttr);
+				}
+
+				//Left
+				var vruler = new Ruler { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
+				if (drawLeft) {
+					vruler.Draw (new Point (screenBounds.X, screenBounds.Y + 1), 1);
+				}
+
+				// Bottom
+				if (drawBottom) {
+					hruler.Draw (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
+				}
+
+				// Right
+				if (drawRight) {
+					vruler.Draw (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
+				}
+
+			}
+		}
+
+		//base.OnDrawContent (contentArea);
+	}
+
+	/// <summary>
+	/// Draws the title for a Window-style view.
+	/// </summary>
+	/// <param name="region">Screen relative region where the title will be drawn.</param>
+	/// <param name="title">The title.</param>
+	public void DrawTitle (Rect region, string title)
+	{
+		var width = region.Width;
+		if (!string.IsNullOrEmpty (title)) {
+			Driver.Move (region.X + 2, region.Y);
+			//Driver.AddRune (' ');
+			var str = title.EnumerateRunes ().Sum (r => Math.Max (r.GetColumns (), 1)) >= width
+				? TextFormatter.Format (title, width, false, false) [0] : title;
+			Driver.AddStr (str);
+		}
+	}
+
+	/// <summary>
+	/// Draws a frame in the current view, clipped by the boundary of this view
+	/// </summary>
+	/// <param name="region">View-relative region for the frame to be drawn.</param>
+	/// <param name="clear">If set to <see langword="true"/> it clear the region.</param>
+	[Obsolete ("This method is obsolete in v2. Use use LineCanvas or Frame instead.", false)]
+	public void DrawFrame (Rect region, bool clear)
+	{
+		var savedClip = ClipToBounds ();
+		var screenBounds = BoundsToScreen (region);
+
+		if (clear) {
+			Driver.FillRect (region);
+		}
+
+		var lc = new LineCanvas ();
+		var drawTop = region.Width > 1 && region.Height > 1;
+		var drawLeft = region.Width > 1 && region.Height > 1;
+		var drawBottom = region.Width > 1 && region.Height > 1;
+		var drawRight = region.Width > 1 && region.Height > 1;
+
+		if (drawTop) {
+			lc.AddLine (screenBounds.Location, screenBounds.Width, Orientation.Horizontal, LineStyle);
+		}
+		if (drawLeft) {
+			lc.AddLine (screenBounds.Location, screenBounds.Height, Orientation.Vertical, LineStyle);
+		}
+		if (drawBottom) {
+			lc.AddLine (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1), screenBounds.Width, Orientation.Horizontal, LineStyle);
+		}
+		if (drawRight) {
+			lc.AddLine (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y), screenBounds.Height, Orientation.Vertical, LineStyle);
+		}
+		foreach (var p in lc.GetMap ()) {
+			Driver.Move (p.Key.X, p.Key.Y);
+			Driver.AddRune (p.Value);
+		}
+		lc.Clear ();
+
+		// TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
+		if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
+			// Top
+			var hruler = new Ruler { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
+			if (drawTop) {
+				hruler.Draw (new Point (screenBounds.X, screenBounds.Y));
+			}
+
+			//Left
+			var vruler = new Ruler { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
+			if (drawLeft) {
+				vruler.Draw (new Point (screenBounds.X, screenBounds.Y + 1), 1);
+			}
+
+			// Bottom
+			if (drawBottom) {
+				hruler.Draw (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
+			}
+
+			// Right
+			if (drawRight) {
+				vruler.Draw (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
+			}
+		}
+
+		Driver.Clip = savedClip;
+	}
+}

+ 37 - 0
Terminal.Gui/View/Adornment/Margin.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Linq;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// The Margin for a <see cref="View"/>. 
+/// </summary>
+/// <remarks>
+/// <para>
+/// See the <see cref="Adornment"/> class. 
+/// </para>
+/// </remarks>
+public class Margin : Adornment {
+	/// <inheritdoc />
+	public Margin () { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */ }
+
+	/// <inheritdoc />
+	public Margin (View parent) : base (parent) { /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */ }
+
+	/// <summary>
+	/// The color scheme for the Margin. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/>'s <see cref="View.SuperView"/> scheme.
+	/// color scheme.
+	/// </summary>
+	public override ColorScheme ColorScheme {
+		get {
+			if (base.ColorScheme != null) {
+				return base.ColorScheme;
+			}
+			return Parent?.SuperView?.ColorScheme ?? Colors.ColorSchemes ["TopLevel"];
+		}
+		set {
+			base.ColorScheme = value;
+			Parent?.SetNeedsDisplay ();
+		}
+	}
+}

+ 37 - 0
Terminal.Gui/View/Adornment/Padding.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Linq;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// The Padding for a <see cref="View"/>. 
+/// </summary>
+/// <remarks>
+/// <para>
+/// See the <see cref="Adornment"/> class. 
+/// </para>
+/// </remarks>
+public class Padding : Adornment {
+	/// <inheritdoc />
+	public Padding () { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */ }
+
+	/// <inheritdoc />
+	public Padding (View parent) : base (parent) { /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */ }
+	
+	/// <summary>
+	/// The color scheme for the Padding. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/> scheme.
+	/// color scheme.
+	/// </summary>
+	public override ColorScheme ColorScheme {
+		get {
+			if (base.ColorScheme != null) {
+				return base.ColorScheme;
+			}
+			return Parent?.ColorScheme;
+		}
+		set {
+			base.ColorScheme = value;
+			Parent?.SetNeedsDisplay ();
+		}
+	}
+}

+ 0 - 446
Terminal.Gui/View/Frame.cs

@@ -1,446 +0,0 @@
-using System.Text;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Xml.Linq;
-using static Terminal.Gui.TileView;
-
-namespace Terminal.Gui {
-
-	// TODO: v2 - Missing 3D effect - 3D effects will be drawn by a mechanism separate from Frames
-	// TODO: v2 - If a Frame has focus, navigation keys (e.g Command.NextView) should cycle through SubViews of the Frame
-	// QUESTION: How does a user navigate out of a Frame to another Frame, or back into the Parent's SubViews?
-
-	/// <summary>
-	/// Frames are a special form of <see cref="View"/> that act as adornments; they appear outside of the <see cref="View.Bounds"/>
-	/// enabling borders, menus, etc... 
-	/// </summary>
-	public class Frame : View {
-		private Thickness _thickness = Thickness.Empty;
-
-		internal override void CreateFrames () { /* Do nothing - Frames do not have Frames */ }
-		internal override void LayoutFrames () { /* Do nothing - Frames do not have Frames */ }
-
-		/// <summary>
-		/// The Parent of this Frame (the View this Frame surrounds).
-		/// </summary>
-		public View Parent { get; set; }
-
-		/// <summary>
-		/// Frames cannot be used as sub-views, so this method always throws an <see cref="InvalidOperationException"/>.
-		/// TODO: Are we sure?
-		/// </summary>
-		public override View SuperView {
-			get {
-				return null;
-			}
-			set {
-				throw new NotImplementedException ();
-			}
-		}
-
-		/// <inheritdoc/>
-		public override void BoundsToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
-		{
-			// Frames are *Children* of a View, not SubViews. Thus View.BoundsToScreen will not work.
-			// To get the screen-relative coordinates of a Frame, we need to know who
-			// the Parent is
-			var parentFrame = Parent?.Frame ?? Frame;
-			rrow = row + parentFrame.Y;
-			rcol = col + parentFrame.X;
-
-			// We now have rcol/rrow in coordinates relative to our View's SuperView. If our View's SuperView has
-			// a SuperView, keep going...
-			Parent?.SuperView?.BoundsToScreen (rcol, rrow, out rcol, out rrow, clipped);
-		}
-
-		/// <inheritdoc/>
-		public override Rect FrameToScreen ()
-		{
-			// Frames are *Children* of a View, not SubViews. Thus View.FramToScreen will not work.
-			// To get the screen-relative coordinates of a Frame, we need to know who
-			// the Parent is
-			var ret = Parent?.Frame ?? Frame;
-			ret.Size = Frame.Size;
-
-			ret.Location = Parent?.FrameToScreen ().Location ?? ret.Location;
-
-			// We now have coordinates relative to our View. If our View's SuperView has
-			// a SuperView, keep going...
-			return ret;
-		}
-
-		/// <summary>
-		/// Does nothing for Frame
-		/// </summary>
-		/// <returns></returns>
-		public override bool OnDrawFrames () => false;
-
-		/// <summary>
-		/// Does nothing for Frame
-		/// </summary>
-		/// <returns></returns>
-		public override bool OnRenderLineCanvas () => false;
-
-		/// <summary>
-		/// Frames only render to their Parent or Parent's SuperView's LineCanvas,
-		/// so this always throws an <see cref="InvalidOperationException"/>.
-		/// </summary>
-		public override bool SuperViewRendersLineCanvas {
-			get {
-				return false;// throw new NotImplementedException ();
-			}
-			set {
-				throw new NotImplementedException ();
-			}
-		}
-
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="clipRect"></param>
-		public virtual void OnDrawSubViews (Rect clipRect)
-		{
-			// TODO: Enable subviews of Frames (adornments).
-			//	if (Subviews == null) {
-			//		return;
-			//	}
-
-			//	foreach (var view in Subviews) {
-			//		// BUGBUG: v2 - shouldn't this be !view.LayoutNeeded? Why draw if layout is going to happen and we'll just draw again?
-			//		if (view.LayoutNeeded) {
-			//			view.LayoutSubviews ();
-			//		}
-			//		if ((view.Visible && !view.NeedDisplay.IsEmpty && view.Frame.Width > 0 && view.Frame.Height > 0) || view.ChildNeedsDisplay) {
-			//			view.Redraw (view.Bounds);
-
-			//			view.NeedDisplay = Rect.Empty;
-			//			// BUGBUG - v2 why does this need to be set to false?
-			//			// Shouldn't it be set when the subviews draw?
-			//			view.ChildNeedsDisplay = false;
-			//		}
-			//	}
-
-		}
-
-		/// <summary>
-		/// Redraws the Frames that comprise the <see cref="Frame"/>.
-		/// </summary>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			if (Thickness == Thickness.Empty) {
-				return;
-			}
-
-			if (ColorScheme != null) {
-				Driver.SetAttribute (GetNormalColor ());
-			} else {
-				if (Id == "Padding") {
-					Driver.SetAttribute (new Attribute (Parent.ColorScheme.HotNormal.Background, Parent.ColorScheme.HotNormal.Foreground));
-				} else {
-					Driver.SetAttribute (Parent.GetNormalColor ());
-				}
-			}
-
-			//Driver.SetAttribute (Colors.Error.Normal);
-			var screenBounds = BoundsToScreen (Frame);
-
-			// This just draws/clears the thickness, not the insides.
-			Thickness.Draw (screenBounds, (string)(Data != null ? Data : string.Empty));
-
-			//OnDrawSubviews (bounds); 
-
-			// TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title)
-
-			// The border frame (and title) are drawn at the outermost edge of border; 
-			// For Border
-			// ...thickness extends outward (border/title is always as far in as possible)
-			var borderBounds = new Rect (
-				screenBounds.X + Math.Max (0, Thickness.Left - 1),
-				screenBounds.Y + Math.Max (0, Thickness.Top - 1),
-				Math.Max (0, screenBounds.Width - Math.Max (0, Math.Max (0, Thickness.Left - 1) + Math.Max (0, Thickness.Right - 1))),
-				Math.Max (0, screenBounds.Height - Math.Max (0, Math.Max (0, Thickness.Top - 1) + Math.Max (0, Thickness.Bottom - 1))));
-
-			var topTitleLineY = borderBounds.Y;
-			var titleY = borderBounds.Y;
-			var titleBarsLength = 0; // the little vertical thingies
-			var maxTitleWidth = Math.Min (Parent.Title.GetColumns (), Math.Min (screenBounds.Width - 4, borderBounds.Width - 4));
-			var sideLineLength = borderBounds.Height;
-			var canDrawBorder = borderBounds.Width > 0 && borderBounds.Height > 0;
-
-			if (!string.IsNullOrEmpty (Parent?.Title)) {
-				if (Thickness.Top == 2) {
-					topTitleLineY = borderBounds.Y - 1;
-					titleY = topTitleLineY + 1;
-					titleBarsLength = 2;
-				}
-
-				// ┌────┐
-				//┌┘View└
-				//│
-				if (Thickness.Top == 3) {
-					topTitleLineY = borderBounds.Y - (Thickness.Top - 1);
-					titleY = topTitleLineY + 1;
-					titleBarsLength = 3;
-					sideLineLength++;
-				}
-
-				// ┌────┐
-				//┌┘View└
-				//│
-				if (Thickness.Top > 3) {
-					topTitleLineY = borderBounds.Y - 2;
-					titleY = topTitleLineY + 1;
-					titleBarsLength = 3;
-					sideLineLength++;
-				}
-
-			}
-
-			if (Id == "Border" && canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) {
-				var prevAttr = Driver.GetAttribute ();
-				if (ColorScheme != null) {
-					Driver.SetAttribute (HasFocus ? GetHotNormalColor () : GetNormalColor ());
-				} else {
-					Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ());
-				}
-				DrawTitle (new Rect (borderBounds.X, titleY, maxTitleWidth, 1), Parent?.Title);
-				Driver.SetAttribute (prevAttr);
-			}
-
-			if (Id == "Border" && canDrawBorder && BorderStyle != LineStyle.None) {
-				LineCanvas lc = Parent?.LineCanvas;
-
-				var drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height > 1;
-				var drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
-				var drawBottom = Thickness.Bottom > 0 && Frame.Width > 1;
-				var drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0);
-
-				var prevAttr = Driver.GetAttribute ();
-				if (ColorScheme != null) {
-					Driver.SetAttribute (GetNormalColor ());
-				} else {
-					Driver.SetAttribute (Parent.GetNormalColor ());
-				}
-
-				if (drawTop) {
-					// ╔╡Title╞═════╗
-					// ╔╡╞═════╗
-					if (borderBounds.Width < 4 || string.IsNullOrEmpty (Parent?.Title)) {
-						// ╔╡╞╗ should be ╔══╗
-						lc.AddLine (new Point (borderBounds.Location.X, titleY), borderBounds.Width, Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
-					} else {
-
-						// ┌────┐
-						//┌┘View└
-						//│
-						if (Thickness.Top == 2) {
-							lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
-						}
-						// ┌────┐
-						//┌┘View└
-						//│
-						if (borderBounds.Width >= 4 && Thickness.Top > 2) {
-							lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
-							lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY + 2), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
-						}
-
-						// ╔╡Title╞═════╗
-						// Add a short horiz line for ╔╡
-						lc.AddLine (new Point (borderBounds.Location.X, titleY), 2, Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
-						// Add a vert line for ╔╡
-						lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), titleBarsLength, Orientation.Vertical, LineStyle.Single, Driver.GetAttribute ());
-						// Add a vert line for ╞
-						lc.AddLine (new Point (borderBounds.X + 1 + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2) - 1, topTitleLineY), titleBarsLength, Orientation.Vertical, LineStyle.Single, Driver.GetAttribute ());
-						// Add the right hand line for ╞═════╗
-						lc.AddLine (new Point (borderBounds.X + 1 + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2) - 1, titleY), borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
-					}
-				}
-				if (drawLeft) {
-					lc.AddLine (new Point (borderBounds.Location.X, titleY), sideLineLength, Orientation.Vertical, BorderStyle, Driver.GetAttribute ());
-				}
-				if (drawBottom) {
-					lc.AddLine (new Point (borderBounds.X, borderBounds.Y + borderBounds.Height - 1), borderBounds.Width, Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
-				}
-				if (drawRight) {
-					lc.AddLine (new Point (borderBounds.X + borderBounds.Width - 1, titleY), sideLineLength, Orientation.Vertical, BorderStyle, Driver.GetAttribute ());
-				}
-				Driver.SetAttribute (prevAttr);
-
-				// TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
-				if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
-					// Top
-					var hruler = new Ruler () { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
-					if (drawTop) {
-						hruler.Draw (new Point (screenBounds.X, screenBounds.Y));
-					}
-
-					// Redraw title 
-					if (drawTop && Id == "Border" && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) {
-						prevAttr = Driver.GetAttribute ();
-						if (ColorScheme != null) {
-							Driver.SetAttribute (HasFocus ? GetHotNormalColor () : GetNormalColor ());
-						} else {
-							Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ());
-						}
-						DrawTitle (new Rect (borderBounds.X, titleY, Parent.Title.GetColumns (), 1), Parent?.Title);
-						Driver.SetAttribute (prevAttr);
-					}
-
-					//Left
-					var vruler = new Ruler () { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
-					if (drawLeft) {
-						vruler.Draw (new Point (screenBounds.X, screenBounds.Y + 1), 1);
-					}
-
-					// Bottom
-					if (drawBottom) {
-						hruler.Draw (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
-					}
-
-					// Right
-					if (drawRight) {
-						vruler.Draw (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
-					}
-
-				}
-			}
-
-			ClearNeedsDisplay ();
-		}
-
-		// TODO: v2 - Frame.BorderStyle is temporary - Eventually the border will be drawn by a "BorderView" that is a subview of the Frame.
-		/// <summary>
-		/// 
-		/// </summary>
-		public new LineStyle BorderStyle { get; set; } = LineStyle.None;
-
-		/// <summary>
-		/// Defines the rectangle that the <see cref="Frame"/> will use to draw its content. 
-		/// </summary>
-		public Thickness Thickness {
-			get { return _thickness; }
-			set {
-				var prev = _thickness;
-				_thickness = value;
-				if (prev != _thickness) {
-
-					Parent?.LayoutFrames ();
-					OnThicknessChanged (prev);
-				}
-
-			}
-		}
-
-		/// <summary>
-		/// Called whenever the <see cref="Thickness"/> property changes.
-		/// </summary>
-		public virtual void OnThicknessChanged (Thickness previousThickness)
-		{
-			ThicknessChanged?.Invoke (this, new ThicknessEventArgs () { Thickness = Thickness, PreviousThickness = previousThickness });
-		}
-
-		/// <summary>
-		/// Fired whenever the <see cref="Thickness"/> property changes.
-		/// </summary>
-		public event EventHandler<ThicknessEventArgs> ThicknessChanged;
-
-		/// <summary>
-		/// Gets the rectangle that describes the inner area of the frame. The Location is always (0,0).
-		/// </summary>
-		public override Rect Bounds {
-			get {
-				return Thickness?.GetInside (new Rect (Point.Empty, Frame.Size)) ?? new Rect (Point.Empty, Frame.Size);
-			}
-			set {
-				throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
-			}
-		}
-
-		/// <summary>
-		/// Draws the title for a Window-style view.
-		/// </summary>
-		/// <param name="region">Screen relative region where the title will be drawn.</param>
-		/// <param name="title">The title.</param>
-		public void DrawTitle (Rect region, string title)
-		{
-			var width = region.Width;
-			if (!string.IsNullOrEmpty (title)) {
-				Driver.Move (region.X + 2, region.Y);
-				//Driver.AddRune (' ');
-				var str = title.EnumerateRunes ().Sum (r => Math.Max (r.GetColumns (), 1)) >= width
-					? TextFormatter.Format (title, width, false, false) [0] : title;
-				Driver.AddStr (str);
-			}
-		}
-
-		/// <summary>
-		/// Draws a frame in the current view, clipped by the boundary of this view
-		/// </summary>
-		/// <param name="region">View-relative region for the frame to be drawn.</param>
-		/// <param name="clear">If set to <see langword="true"/> it clear the region.</param>
-		[ObsoleteAttribute ("This method is obsolete in v2. Use use LineCanvas or Frame instead.", false)]
-		public void DrawFrame (Rect region, bool clear)
-		{
-			var savedClip = ClipToBounds ();
-			var screenBounds = BoundsToScreen (region);
-
-			if (clear) {
-				Driver.FillRect (region);
-			}
-
-			var lc = new LineCanvas ();
-			var drawTop = region.Width > 1 && region.Height > 1;
-			var drawLeft = region.Width > 1 && region.Height > 1;
-			var drawBottom = region.Width > 1 && region.Height > 1;
-			var drawRight = region.Width > 1 && region.Height > 1;
-
-			if (drawTop) {
-				lc.AddLine (screenBounds.Location, screenBounds.Width, Orientation.Horizontal, BorderStyle);
-			}
-			if (drawLeft) {
-				lc.AddLine (screenBounds.Location, screenBounds.Height, Orientation.Vertical, BorderStyle);
-			}
-			if (drawBottom) {
-				lc.AddLine (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1), screenBounds.Width, Orientation.Horizontal, BorderStyle);
-			}
-			if (drawRight) {
-				lc.AddLine (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y), screenBounds.Height, Orientation.Vertical, BorderStyle);
-			}
-			foreach (var p in lc.GetMap ()) {
-				Driver.Move (p.Key.X, p.Key.Y);
-				Driver.AddRune (p.Value);
-			}
-			lc.Clear ();
-
-			// TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
-			if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
-				// Top
-				var hruler = new Ruler () { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
-				if (drawTop) {
-					hruler.Draw (new Point (screenBounds.X, screenBounds.Y));
-				}
-
-				//Left
-				var vruler = new Ruler () { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
-				if (drawLeft) {
-					vruler.Draw (new Point (screenBounds.X, screenBounds.Y + 1), 1);
-				}
-
-				// Bottom
-				if (drawBottom) {
-					hruler.Draw (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
-				}
-
-				// Right
-				if (drawRight) {
-					vruler.Draw (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
-				}
-			}
-
-			Driver.Clip = savedClip;
-		}
-
-	}
-}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 488 - 400
Terminal.Gui/View/Layout/PosDim.cs


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 381 - 354
Terminal.Gui/View/Layout/ViewLayout.cs


+ 27 - 26
Terminal.Gui/View/SuperViewChangedEventArgs.cs

@@ -1,33 +1,34 @@
 using System;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+
+/// <summary>
+/// Args for events where the <see cref="View.SuperView"/> of a <see cref="View"/> is changed
+/// (e.g. <see cref="View.Removed"/> / <see cref="View.Added"/> events).
+/// </summary>
+public class SuperViewChangedEventArgs : EventArgs {
 	/// <summary>
-	/// Args for events where the <see cref="View.SuperView"/> of a <see cref="View"/> is changed
-	/// (e.g. <see cref="View.Removed"/> / <see cref="View.Added"/> events).
+	/// Creates a new instance of the <see cref="SuperViewChangedEventArgs"/> class.
 	/// </summary>
-	public class SuperViewChangedEventArgs : EventArgs
+	/// <param name="parent"></param>
+	/// <param name="child"></param>
+	public SuperViewChangedEventArgs (View parent, View child)
 	{
-		/// <summary>
-		/// Creates a new instance of the <see cref="SuperViewChangedEventArgs"/> class.
-		/// </summary>
-		/// <param name="parent"></param>
-		/// <param name="child"></param>
-		public SuperViewChangedEventArgs (View parent, View child)
-		{
-			Parent = parent;
-			Child = child;
-		}
+		Parent = parent;
+		Child = child;
+	}
 
-		/// <summary>
-		/// The parent.  For <see cref="View.Removed"/> this is the old
-		/// parent (new parent now being null).  For <see cref="View.Added"/>
-		/// it is the new parent to whom view now belongs.
-		/// </summary>
-		public View Parent { get; }
+	// TODO: Parent is the wrong name. It should be SuperView.
+	/// <summary>
+	/// The parent.  For <see cref="View.Removed"/> this is the old
+	/// parent (new parent now being null).  For <see cref="View.Added"/>
+	/// it is the new parent to whom view now belongs.
+	/// </summary>
+	public View Parent { get; }
 
-		/// <summary>
-		/// The view that is having it's <see cref="View.SuperView"/> changed
-		/// </summary>
-		public View Child { get; }
-	}
-}
+	// TODO: Child is the wrong name. It should be View.
+	/// <summary>
+	/// The view that is having it's <see cref="View.SuperView"/> changed
+	/// </summary>
+	public View Child { get; }
+}

+ 388 - 329
Terminal.Gui/View/View.cs

@@ -1,315 +1,137 @@
 using System;
 using System.ComponentModel;
+using System.Diagnostics;
 
 namespace Terminal.Gui;
 
 #region API Docs
 /// <summary>
-/// View is the base class for all views on the screen and represents a visible element that can render itself and 
+/// View is the base class for all views on the screen and represents a visible element that can render itself and
 /// contains zero or more nested views, called SubViews. View provides basic functionality for layout, positioning,
 /// and drawing. In addition, View provides keyboard and mouse event handling.
 /// </summary>
 /// <remarks>
 /// <list type="table">
-///	<listheader>
-///	<term>Term</term><description>Definition</description>
-///	</listheader>
-///	<item>
-///	<term>SubView</term><description>A View that is contained in another view and will be rendered as part of the containing view's ContentArea. 
-/// SubViews are added to another view via the <see cref="View.Add(View)"/>` method. A View may only be a SubView of a single View. </description>
-///	</item>
-///	<item>
-///		<term>SuperView</term><description>The View that is a container for SubViews.</description>
-///	</item>
+///         <listheader>
+///                 <term>Term</term><description>Definition</description>
+///         </listheader>
+///         <item>
+///                 <term>SubView</term>
+///                 <description>
+///                 A View that is contained in another view and will be rendered as part of the containing view's
+///                 ContentArea.
+///                 SubViews are added to another view via the <see cref="View.Add(View)"/>` method. A View may only be a
+///                 SubView of a single View.
+///                 </description>
+///         </item>
+///         <item>
+///                 <term>SuperView</term><description>The View that is a container for SubViews.</description>
+///         </item>
 /// </list>
 /// <para>
 /// Focus is a concept that is used to describe which View is currently receiving user input. Only Views that are
 /// <see cref="Enabled"/>, <see cref="Visible"/>, and <see cref="CanFocus"/> will receive focus.
 /// </para>
 /// <para>
-///    Views that are focusable should implement the <see cref="PositionCursor"/> to make sure that
-///    the cursor is placed in a location that makes sense. Unix terminals do not have
-///    a way of hiding the cursor, so it can be distracting to have the cursor left at
-///    the last focused view. So views should make sure that they place the cursor
-///    in a visually sensible place.
+/// Views that are focusable should implement the <see cref="PositionCursor"/> to make sure that
+/// the cursor is placed in a location that makes sense. Unix terminals do not have
+/// a way of hiding the cursor, so it can be distracting to have the cursor left at
+/// the last focused view. So views should make sure that they place the cursor
+/// in a visually sensible place.
 /// </para>
 /// <para>
-///    The View defines the base functionality for user interface elements in Terminal.Gui. Views
-///    can contain one or more subviews, can respond to user input and render themselves on the screen.
+/// The View defines the base functionality for user interface elements in Terminal.Gui. Views
+/// can contain one or more subviews, can respond to user input and render themselves on the screen.
 /// </para>
 /// <para>
-///    Views supports two layout styles: <see cref="LayoutStyle.Absolute"/> or <see cref="LayoutStyle.Computed"/>. 
-///    The choice as to which layout style is used by the View 
-///    is determined when the View is initialized. To create a View using Absolute layout, call a constructor that takes a
-///    Rect parameter to specify the absolute position and size (the View.<see cref="View.Frame "/>). To create a View 
-///    using Computed layout use a constructor that does not take a Rect parameter and set the X, Y, Width and Height 
-///    properties on the view. Both approaches use coordinates that are relative to the container they are being added to. 
+/// View supports two layout styles: <see cref="LayoutStyle.Absolute"/> or <see cref="LayoutStyle.Computed"/>.
+/// The style is determined by the values of <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and
+/// <see cref="Height"/>.
+/// If any of these is set to non-absolute <see cref="Pos"/> or <see cref="Dim"/> object,
+/// then the layout style is <see cref="LayoutStyle.Computed"/>. Otherwise it is <see cref="LayoutStyle.Absolute"/>.
 /// </para>
 /// <para>
-///    To switch between Absolute and Computed layout, use the <see cref="LayoutStyle"/> property. 
+/// To create a View using Absolute layout, call a constructor that takes a
+/// Rect parameter to specify the absolute position and size or simply set <see cref="View.Frame "/>). To create a View
+/// using Computed layout use a constructor that does not take a Rect parameter and set the X, Y, Width and Height
+/// properties on the view to non-absolute values. Both approaches use coordinates that are relative to the
+/// <see cref="Bounds"/> of the <see cref="SuperView"/> the View is added to.
 /// </para>
 /// <para>
-///    Computed layout is more flexible and supports dynamic console apps where controls adjust layout
-///    as the terminal resizes or other Views change size or position. The X, Y, Width and Height 
-///    properties are Dim and Pos objects that dynamically update the position of a view.
-///    The X and Y properties are of type <see cref="Pos"/>
-///    and you can use either absolute positions, percentages or anchor
-///    points. The Width and Height properties are of type
-///    <see cref="Dim"/> and can use absolute position,
-///    percentages and anchors. These are useful as they will take
-///    care of repositioning views when view's frames are resized or
-///    if the terminal size changes.
+/// Computed layout is more flexible and supports dynamic console apps where controls adjust layout
+/// as the terminal resizes or other Views change size or position. The
+/// <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and
+/// <see cref="Height"/> properties are <see cref="Dim"/> and <see cref="Pos"/> objects that dynamically update the
+/// position of a view.
+/// The X and Y properties are of type <see cref="Pos"/>
+/// and you can use either absolute positions, percentages, or anchor
+/// points. The Width and Height properties are of type
+/// <see cref="Dim"/> and can use absolute position,
+/// percentages, and anchors. These are useful as they will take
+/// care of repositioning views when view's adornments are resized or
+/// if the terminal size changes.
 /// </para>
 /// <para>
-///    Absolute layout requires specifying coordinates and sizes of Views explicitly, and the
-///    View will typically stay in a fixed position and size. To change the position and size use the
-///    <see cref="Frame"/> property.
+/// Absolute layout requires specifying coordinates and sizes of Views explicitly, and the
+/// View will typically stay in a fixed position and size. To change the position and size use the
+/// <see cref="Frame"/> property.
 /// </para>
 /// <para>
-///    Subviews (child views) can be added to a View by calling the <see cref="Add(View)"/> method. 
-///    The container of a View can be accessed with the <see cref="SuperView"/> property.
+/// Subviews (child views) can be added to a View by calling the <see cref="Add(View)"/> method.
+/// The container of a View can be accessed with the <see cref="SuperView"/> property.
 /// </para>
 /// <para>
-///    To flag a region of the View's <see cref="Bounds"/> to be redrawn call <see cref="SetNeedsDisplay(Rect)"/>. 
-///    To flag the entire view for redraw call <see cref="SetNeedsDisplay()"/>.
+/// To flag a region of the View's <see cref="Bounds"/> to be redrawn call <see cref="SetNeedsDisplay(Rect)"/>.
+/// To flag the entire view for redraw call <see cref="SetNeedsDisplay()"/>.
 /// </para>
 /// <para>
-///    The <see cref="LayoutSubviews"/> method is invoked when the size or layout of a view has
-///    changed. The default processing system will keep the size and dimensions
-///    for views that use the <see cref="LayoutStyle.Absolute"/>, and will recompute the
-///    frames for the vies that use <see cref="LayoutStyle.Computed"/>.
+/// The <see cref="LayoutSubviews"/> method is invoked when the size or layout of a view has
+/// changed. The default processing system will keep the size and dimensions
+/// for views that use the <see cref="LayoutStyle.Absolute"/>, and will recompute the
+/// Adornments for the views that use <see cref="LayoutStyle.Computed"/>.
 /// </para>
 /// <para>
-///    Views have a <see cref="ColorScheme"/> property that defines the default colors that subviews
-///    should use for rendering. This ensures that the views fit in the context where
-///    they are being used, and allows for themes to be plugged in. For example, the
-///    default colors for windows and Toplevels uses a blue background, while it uses
-///    a white background for dialog boxes and a red background for errors.
+/// Views have a <see cref="ColorScheme"/> property that defines the default colors that subviews
+/// should use for rendering. This ensures that the views fit in the context where
+/// they are being used, and allows for themes to be plugged in. For example, the
+/// default colors for windows and Toplevels uses a blue background, while it uses
+/// a white background for dialog boxes and a red background for errors.
 /// </para>
 /// <para>
-///    Subclasses should not rely on <see cref="ColorScheme"/> being
-///    set at construction time. If a <see cref="ColorScheme"/> is not set on a view, the view will inherit the
-///    value from its <see cref="SuperView"/> and the value might only be valid once a view has been
-///    added to a SuperView. 
+/// Subclasses should not rely on <see cref="ColorScheme"/> being
+/// set at construction time. If a <see cref="ColorScheme"/> is not set on a view, the view will inherit the
+/// value from its <see cref="SuperView"/> and the value might only be valid once a view has been
+/// added to a SuperView.
 /// </para>
 /// <para>
-///    By using  <see cref="ColorScheme"/> applications will work both
-///    in color as well as black and white displays.
+/// By using  <see cref="ColorScheme"/> applications will work both
+/// in color as well as black and white displays.
 /// </para>
 /// <para>
-///     Views can also opt-in to more sophisticated initialization
-///     by implementing overrides to <see cref="ISupportInitialize.BeginInit"/> and
-///     <see cref="ISupportInitialize.EndInit"/> which will be called
-///     when the view is added to a <see cref="SuperView"/>. 
+/// Views can also opt-in to more sophisticated initialization
+/// by implementing overrides to <see cref="ISupportInitialize.BeginInit"/> and
+/// <see cref="ISupportInitialize.EndInit"/> which will be called
+/// when the view is added to a <see cref="SuperView"/>.
 /// </para>
 /// <para>
-///     If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification"/>
-///     can be implemented, in which case the <see cref="ISupportInitialize"/>
-///     methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
-///     is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
-///     to override base class layout code optimally by doing so only on first run,
-///     instead of on every run.
-///   </para>
+/// If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification"/>
+/// can be implemented, in which case the <see cref="ISupportInitialize"/>
+/// methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
+/// is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
+/// to override base class layout code optimally by doing so only on first run,
+/// instead of on every run.
+/// </para>
 /// <para>
-///	See <see href="../docs/keyboard.md">for an overview of View keyboard handling.</see>
-/// </para>	/// </remarks>
+/// See <see href="../docs/keyboard.md">for an overview of View keyboard handling.</see>
+/// </para>
+/// ///
+/// </remarks>
 #endregion API Docs
 
 public partial class View : Responder, ISupportInitializeNotification {
-	#region Constructors and Initialization
-	/// <summary>
-	/// Initializes a new instance of a <see cref="Terminal.Gui.LayoutStyle.Absolute"/> <see cref="View"/> class with the absolute
-	/// dimensions specified in the <paramref name="frame"/> parameter. 
-	/// </summary>
-	/// <param name="frame">The region covered by this view.</param>
-	/// <remarks>
-	/// This constructor initialize a View with a <see cref="LayoutStyle"/> of <see cref="Terminal.Gui.LayoutStyle.Absolute"/>.
-	/// Use <see cref="View"/> to initialize a View with  <see cref="LayoutStyle"/> of <see cref="Terminal.Gui.LayoutStyle.Computed"/> 
-	/// </remarks>
-	public View (Rect frame) : this (frame, null) { }
-
-	/// <summary>
-	///   Initializes a new instance of <see cref="View"/> using <see cref="Terminal.Gui.LayoutStyle.Computed"/> layout.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically control the size and location of the view.
-	///   The <see cref="View"/> will be created using <see cref="Terminal.Gui.LayoutStyle.Computed"/>
-	///   coordinates. The initial size (<see cref="View.Frame"/>) will be 
-	///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-	/// </para>
-	/// <para>
-	///   If <see cref="Height"/> is greater than one, word wrapping is provided.
-	/// </para>
-	/// <para>
-	///   This constructor initialize a View with a <see cref="LayoutStyle"/> of <see cref="Terminal.Gui.LayoutStyle.Computed"/>. 
-	///   Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically control the size and location of the view.
-	/// </para>
-	/// </remarks>
-	public View () : this (string.Empty, TextDirection.LeftRight_TopBottom) { }
-
-	/// <summary>
-	///   Initializes a new instance of <see cref="View"/> using <see cref="Terminal.Gui.LayoutStyle.Absolute"/> layout.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   The <see cref="View"/> will be created at the given
-	///   coordinates with the given string. The size (<see cref="View.Frame"/>) will be 
-	///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-	/// </para>
-	/// <para>
-	///   No line wrapping is provided.
-	/// </para>
-	/// </remarks>
-	/// <param name="x">column to locate the View.</param>
-	/// <param name="y">row to locate the View.</param>
-	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
-	public View (int x, int y, string text) : this (TextFormatter.CalcRect (x, y, text), text) { }
-
-	/// <summary>
-	///   Initializes a new instance of <see cref="View"/> using <see cref="Terminal.Gui.LayoutStyle.Absolute"/> layout.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   The <see cref="View"/> will be created at the given
-	///   coordinates with the given string. The initial size (<see cref="View.Frame"/>) will be 
-	///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-	/// </para>
-	/// <para>
-	///   If <c>rect.Height</c> is greater than one, word wrapping is provided.
-	/// </para>
-	/// </remarks>
-	/// <param name="rect">Location.</param>
-	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
-	public View (Rect rect, string text) => SetInitialProperties (text, rect, LayoutStyle.Absolute, TextDirection.LeftRight_TopBottom);
-
-	/// <summary>
-	///   Initializes a new instance of <see cref="View"/> using <see cref="Terminal.Gui.LayoutStyle.Computed"/> layout.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   The <see cref="View"/> will be created using <see cref="Terminal.Gui.LayoutStyle.Computed"/>
-	///   coordinates with the given string. The initial size (<see cref="View.Frame"/>) will be 
-	///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-	/// </para>
-	/// <para>
-	///   If <see cref="Height"/> is greater than one, word wrapping is provided.
-	/// </para>
-	/// </remarks>
-	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
-	/// <param name="direction">The text direction.</param>
-	public View (string text, TextDirection direction = TextDirection.LeftRight_TopBottom) => SetInitialProperties (text, Rect.Empty, LayoutStyle.Computed, direction);
-
-	// TODO: v2 - Remove constructors with parameters
-	/// <summary>
-	/// Private helper to set the initial properties of the View that were provided via constructors.
-	/// </summary>
-	/// <param name="text"></param>
-	/// <param name="rect"></param>
-	/// <param name="layoutStyle"></param>
-	/// <param name="direction"></param>
-	void SetInitialProperties (string text, Rect rect, LayoutStyle layoutStyle = LayoutStyle.Computed,
-				TextDirection direction = TextDirection.LeftRight_TopBottom)
-	{
-		TextFormatter = new TextFormatter ();
-		TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
-		TextDirection = direction;
-
-		CanFocus = false;
-		TabIndex = -1;
-		TabStop = false;
-		LayoutStyle = layoutStyle;
-
-		Text = text == null ? string.Empty : text;
-		LayoutStyle = layoutStyle;
-		Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect;
-		OnResizeNeeded ();
-
-		AddCommands ();
-
-		CreateFrames ();
-	}
-
-	/// <summary>
-	/// Get or sets if  the <see cref="View"/> has been initialized (via <see cref="ISupportInitialize.BeginInit"/> 
-	/// and <see cref="ISupportInitialize.EndInit"/>).
-	/// </summary>
-	/// <para>
-	///     If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification.IsInitialized"/>
-	///     can be implemented, in which case the <see cref="ISupportInitialize"/>
-	///     methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
-	///     is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
-	///     to override base class layout code optimally by doing so only on first run,
-	///     instead of on every run.
-	///   </para>
-	public virtual bool IsInitialized { get; set; }
-
-	/// <summary>
-	///  Signals the View that initialization is starting. See <see cref="ISupportInitialize"/>.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///     Views can opt-in to more sophisticated initialization
-	///     by implementing overrides to <see cref="ISupportInitialize.BeginInit"/> and
-	///     <see cref="ISupportInitialize.EndInit"/> which will be called
-	///     when the view is added to a <see cref="SuperView"/>. 
-	/// </para>
-	/// <para>
-	///     If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification"/>
-	///     can be implemented too, in which case the <see cref="ISupportInitialize"/>
-	///     methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
-	///     is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
-	///     to override base class layout code optimally by doing so only on first run,
-	///     instead of on every run.
-	///   </para>
-	/// </remarks>
-	public virtual void BeginInit ()
-	{
-		if (!IsInitialized) {
-			_oldCanFocus = CanFocus;
-			_oldTabIndex = _tabIndex;
-
-
-			// TODO: Figure out why ScrollView and other tests fail if this call is put here 
-			// instead of the constructor.
-			//InitializeFrames ();
-
-		} else {
-			//throw new InvalidOperationException ("The view is already initialized.");
-
-		}
-
-		if (_subviews?.Count > 0) {
-			foreach (var view in _subviews) {
-				if (!view.IsInitialized) {
-					view.BeginInit ();
-				}
-			}
-		}
-	}
-
-	/// <summary>
-	///  Signals the View that initialization is ending. See <see cref="ISupportInitialize"/>.
-	/// </summary>
-	public void EndInit ()
-	{
-		IsInitialized = true;
-		// These calls were moved from BeginInit as they access Bounds which is indeterminate until EndInit is called.
-		UpdateTextDirection (TextDirection);
-		UpdateTextFormatterText ();
-		SetHotKey ();
+	bool _oldEnabled;
 
-		OnResizeNeeded ();
-		if (_subviews != null) {
-			foreach (var view in _subviews) {
-				if (!view.IsInitialized) {
-					view.EndInit ();
-				}
-			}
-		}
-		Initialized?.Invoke (this, EventArgs.Empty);
-	}
-	#endregion Constructors and Initialization
+	string _title = string.Empty;
 
 	/// <summary>
 	/// Points to the current driver in use by the view, it is a convenience property
@@ -330,10 +152,9 @@ public partial class View : Responder, ISupportInitializeNotification {
 	/// <remarks>The id should be unique across all Views that share a SuperView.</remarks>
 	public string Id { get; set; } = "";
 
-	string _title = string.Empty;
-
 	/// <summary>
-	/// The title to be displayed for this <see cref="View"/>. The title will be displayed if <see cref="Border"/>.<see cref="Thickness.Top"/>
+	/// The title to be displayed for this <see cref="View"/>. The title will be displayed if <see cref="Border"/>.
+	/// <see cref="Thickness.Top"/>
 	/// is greater than 0.
 	/// </summary>
 	/// <value>The title.</value>
@@ -341,12 +162,12 @@ public partial class View : Responder, ISupportInitializeNotification {
 		get => _title;
 		set {
 			if (!OnTitleChanging (_title, value)) {
-				string old = _title;
+				var old = _title;
 				_title = value;
 				SetNeedsDisplay ();
 #if DEBUG
 				if (_title != null && string.IsNullOrEmpty (Id)) {
-					Id = _title.ToString ();
+					Id = _title;
 				}
 #endif // DEBUG
 				OnTitleChanged (old, _title);
@@ -354,51 +175,6 @@ public partial class View : Responder, ISupportInitializeNotification {
 		}
 	}
 
-	/// <summary>
-	/// Called before the <see cref="View.Title"/> changes. Invokes the <see cref="TitleChanging"/> event, which can be cancelled.
-	/// </summary>
-	/// <param name="oldTitle">The <see cref="View.Title"/> that is/has been replaced.</param>
-	/// <param name="newTitle">The new <see cref="View.Title"/> to be replaced.</param>
-	/// <returns>`true` if an event handler canceled the Title change.</returns>
-	public virtual bool OnTitleChanging (string oldTitle, string newTitle)
-	{
-		var args = new TitleEventArgs (oldTitle, newTitle);
-		TitleChanging?.Invoke (this, args);
-		return args.Cancel;
-	}
-
-	/// <summary>
-	/// Event fired when the <see cref="View.Title"/> is changing. Set <see cref="TitleEventArgs.Cancel"/> to 
-	/// `true` to cancel the Title change.
-	/// </summary>
-	public event EventHandler<TitleEventArgs> TitleChanging;
-
-	/// <summary>
-	/// Called when the <see cref="View.Title"/> has been changed. Invokes the <see cref="TitleChanged"/> event.
-	/// </summary>
-	/// <param name="oldTitle">The <see cref="View.Title"/> that is/has been replaced.</param>
-	/// <param name="newTitle">The new <see cref="View.Title"/> to be replaced.</param>
-	public virtual void OnTitleChanged (string oldTitle, string newTitle)
-	{
-		var args = new TitleEventArgs (oldTitle, newTitle);
-		TitleChanged?.Invoke (this, args);
-	}
-
-	/// <summary>
-	/// Event fired after the <see cref="View.Title"/> has been changed. 
-	/// </summary>
-	public event EventHandler<TitleEventArgs> TitleChanged;
-
-	/// <summary>
-	/// Event fired when the <see cref="Enabled"/> value is being changed.
-	/// </summary>
-	public event EventHandler EnabledChanged;
-
-	/// <inheritdoc/>
-	public override void OnEnabledChanged () => EnabledChanged?.Invoke (this, EventArgs.Empty);
-
-	bool _oldEnabled;
-
 	/// <inheritdoc/>
 	public override bool Enabled {
 		get => base.Enabled;
@@ -432,20 +208,13 @@ public partial class View : Responder, ISupportInitializeNotification {
 		}
 	}
 
-	/// <summary>
-	/// Event fired when the <see cref="Visible"/> value is being changed.
-	/// </summary>
-	public event EventHandler VisibleChanged;
-
-	/// <inheritdoc/>
-	public override void OnVisibleChanged () => VisibleChanged?.Invoke (this, EventArgs.Empty);
-
 	/// <summary>
 	/// Gets or sets whether a view is cleared if the <see cref="Visible"/> property is <see langword="false"/>.
 	/// </summary>
 	public bool ClearOnVisibleFalse { get; set; } = true;
 
-	/// <inheritdoc/>>
+	/// <inheritdoc/>
+	/// >
 	public override bool Visible {
 		get => base.Visible;
 		set {
@@ -465,6 +234,58 @@ public partial class View : Responder, ISupportInitializeNotification {
 		}
 	}
 
+	/// <summary>
+	/// Called before the <see cref="View.Title"/> changes. Invokes the <see cref="TitleChanging"/> event, which can be
+	/// cancelled.
+	/// </summary>
+	/// <param name="oldTitle">The <see cref="View.Title"/> that is/has been replaced.</param>
+	/// <param name="newTitle">The new <see cref="View.Title"/> to be replaced.</param>
+	/// <returns>`true` if an event handler canceled the Title change.</returns>
+	public virtual bool OnTitleChanging (string oldTitle, string newTitle)
+	{
+		var args = new TitleEventArgs (oldTitle, newTitle);
+		TitleChanging?.Invoke (this, args);
+		return args.Cancel;
+	}
+
+	/// <summary>
+	/// Event fired when the <see cref="View.Title"/> is changing. Set <see cref="TitleEventArgs.Cancel"/> to
+	/// `true` to cancel the Title change.
+	/// </summary>
+	public event EventHandler<TitleEventArgs> TitleChanging;
+
+	/// <summary>
+	/// Called when the <see cref="View.Title"/> has been changed. Invokes the <see cref="TitleChanged"/> event.
+	/// </summary>
+	/// <param name="oldTitle">The <see cref="View.Title"/> that is/has been replaced.</param>
+	/// <param name="newTitle">The new <see cref="View.Title"/> to be replaced.</param>
+	public virtual void OnTitleChanged (string oldTitle, string newTitle)
+	{
+		var args = new TitleEventArgs (oldTitle, newTitle);
+		TitleChanged?.Invoke (this, args);
+	}
+
+	/// <summary>
+	/// Event fired after the <see cref="View.Title"/> has been changed.
+	/// </summary>
+	public event EventHandler<TitleEventArgs> TitleChanged;
+
+	/// <summary>
+	/// Event fired when the <see cref="Enabled"/> value is being changed.
+	/// </summary>
+	public event EventHandler EnabledChanged;
+
+	/// <inheritdoc/>
+	public override void OnEnabledChanged () => EnabledChanged?.Invoke (this, EventArgs.Empty);
+
+	/// <summary>
+	/// Event fired when the <see cref="Visible"/> value is being changed.
+	/// </summary>
+	public event EventHandler VisibleChanged;
+
+	/// <inheritdoc/>
+	public override void OnVisibleChanged () => VisibleChanged?.Invoke (this, EventArgs.Empty);
+
 	bool CanBeVisible (View view)
 	{
 		if (!view.Visible) {
@@ -497,18 +318,256 @@ public partial class View : Responder, ISupportInitializeNotification {
 		Padding?.Dispose ();
 		Padding = null;
 
-		_height = null;
-		_width = null;
-		_x = null;
-		_y = null;
-
-		for (int i = InternalSubviews.Count - 1; i >= 0; i--) {
+		for (var i = InternalSubviews.Count - 1; i >= 0; i--) {
 			var subview = InternalSubviews [i];
 			Remove (subview);
 			subview.Dispose ();
 		}
 
 		base.Dispose (disposing);
-		System.Diagnostics.Debug.Assert (InternalSubviews.Count == 0);
+		Debug.Assert (InternalSubviews.Count == 0);
 	}
+
+	#region Constructors and Initialization
+	/// <summary>
+	/// Initializes a new instance of a <see cref="View"/> class with the absolute
+	/// dimensions specified in the <paramref name="frame"/> parameter.
+	/// </summary>
+	/// <param name="frame">The region covered by this view.</param>
+	/// <remarks>
+	///         <para>
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view.
+	///         The <see cref="View"/> will be created using <see cref="LayoutStyle.Absolute"/>
+	///         coordinates. The initial size (<see cref="View.Frame"/>) will be
+	///         adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
+	///         </para>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         This constructor initialize a View with a <see cref="LayoutStyle"/> of
+	///         <see cref="LayoutStyle.Absolute"/>.
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
+	///         </para>
+	/// </remarks>
+	public View (Rect frame) : this (frame, null) { }
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="View"/>.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view.
+	///         The <see cref="View"/> will be created using <see cref="LayoutStyle.Absolute"/>
+	///         coordinates. The initial size (<see cref="View.Frame"/>) will be
+	///         adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
+	///         </para>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         This constructor initialize a View with a <see cref="LayoutStyle"/> of
+	///         <see cref="LayoutStyle.Absolute"/>.
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
+	///         </para>
+	/// </remarks>
+	public View () : this (string.Empty) { }
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="View"/> in at the position specified with the
+	/// dimensions specified in the <paramref name="x"/> and <paramref name="y"/> parameters.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         The <see cref="View"/> will be created at the given
+	///         coordinates with the given string. The size (<see cref="View.Frame"/>) will be
+	///         adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
+	///         </para>
+	///         <para>
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view.
+	///         </para>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         This constructor initialize a View with a <see cref="LayoutStyle"/> of
+	///         <see cref="LayoutStyle.Absolute"/>.
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
+	///         </para>
+	/// </remarks>
+	/// <param name="x">column to locate the View.</param>
+	/// <param name="y">row to locate the View.</param>
+	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
+	public View (int x, int y, string text) : this (TextFormatter.CalcRect (x, y, text), text) { }
+
+	/// <summary>
+	/// Initializes a new instance of a <see cref="View"/> class with the absolute
+	/// dimensions specified in the <paramref name="frame"/> parameter.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         The <see cref="View"/> will be created at the given
+	///         coordinates with the given string. The size (<see cref="View.Frame"/>) will be
+	///         adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
+	///         </para>
+	///         <para>
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view.
+	///         </para>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         This constructor initialize a View with a <see cref="LayoutStyle"/> of
+	///         <see cref="LayoutStyle.Absolute"/>.
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
+	///         </para>
+	/// </remarks>
+	/// <param name="frame">Location.</param>
+	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
+	public View (Rect frame, string text) => SetInitialProperties (text, frame, LayoutStyle.Absolute);
+
+
+	/// <summary>
+	/// Initializes a new instance of a <see cref="View"/> class with using the given text and text styling information.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         The <see cref="View"/> will be created at the given
+	///         coordinates with the given string. The size (<see cref="View.Frame"/>) will be
+	///         adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
+	///         </para>
+	///         <para>
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view.
+	///         </para>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         This constructor initialize a View with a <see cref="LayoutStyle"/> of
+	///         <see cref="LayoutStyle.Absolute"/>.
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
+	///         </para>
+	/// </remarks>
+	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
+	/// <param name="direction">The text direction.</param>
+	public View (string text, TextDirection direction = TextDirection.LeftRight_TopBottom) => SetInitialProperties (text, Rect.Empty, LayoutStyle.Computed, direction);
+
+	// TODO: v2 - Remove constructors with parameters
+
+
+	/// <summary>
+	/// Private helper to set the initial properties of the View that were provided via constructors.
+	/// </summary>
+	/// <param name="text"></param>
+	/// <param name="rect"></param>
+	/// <param name="layoutStyle"></param>
+	/// <param name="direction"></param>
+	void SetInitialProperties (string text,
+				   Rect rect,
+				   LayoutStyle layoutStyle = LayoutStyle.Computed,
+				   TextDirection direction = TextDirection.LeftRight_TopBottom)
+	{
+		TextFormatter = new TextFormatter ();
+		TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
+		TextDirection = direction;
+
+		CanFocus = false;
+		TabIndex = -1;
+		TabStop = false;
+
+		Text = text == null ? string.Empty : text;
+		Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect;
+
+		AddCommands ();
+
+		Margin = CreateAdornment (typeof (Margin)) as Margin;
+		Border = CreateAdornment (typeof (Border)) as Border;
+		Padding = CreateAdornment (typeof (Padding)) as Padding;
+	}
+
+	/// <summary>
+	/// Get or sets if  the <see cref="View"/> has been initialized (via <see cref="ISupportInitialize.BeginInit"/>
+	/// and <see cref="ISupportInitialize.EndInit"/>).
+	/// </summary>
+	/// <para>
+	/// If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification.IsInitialized"/>
+	/// can be implemented, in which case the <see cref="ISupportInitialize"/>
+	/// methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
+	/// is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
+	/// to override base class layout code optimally by doing so only on first run,
+	/// instead of on every run.
+	/// </para>
+	public virtual bool IsInitialized { get; set; }
+
+	/// <summary>
+	/// Signals the View that initialization is starting. See <see cref="ISupportInitialize"/>.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         Views can opt-in to more sophisticated initialization
+	///         by implementing overrides to <see cref="ISupportInitialize.BeginInit"/> and
+	///         <see cref="ISupportInitialize.EndInit"/> which will be called
+	///         when the view is added to a <see cref="SuperView"/>.
+	///         </para>
+	///         <para>
+	///         If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification"/>
+	///         can be implemented too, in which case the <see cref="ISupportInitialize"/>
+	///         methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
+	///         is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
+	///         to override base class layout code optimally by doing so only on first run,
+	///         instead of on every run.
+	///         </para>
+	/// </remarks>
+	public virtual void BeginInit ()
+	{
+		if (!IsInitialized) {
+			_oldCanFocus = CanFocus;
+			_oldTabIndex = _tabIndex;
+		}
+
+		//throw new InvalidOperationException ("The view is already initialized.");
+		if (_subviews?.Count > 0) {
+			foreach (var view in _subviews) {
+				if (!view.IsInitialized) {
+					view.BeginInit ();
+				}
+			}
+		}
+	}
+
+	/// <summary>
+	/// Signals the View that initialization is ending. See <see cref="ISupportInitialize"/>.
+	/// </summary>
+	public void EndInit ()
+	{
+		IsInitialized = true;
+		// These calls were moved from BeginInit as they access Bounds which is indeterminate until EndInit is called.
+		UpdateTextDirection (TextDirection);
+		UpdateTextFormatterText ();
+		SetHotKey ();
+
+		OnResizeNeeded ();
+		if (_subviews != null) {
+			foreach (var view in _subviews) {
+				if (!view.IsInitialized) {
+					view.EndInit ();
+				}
+			}
+		}
+		Initialized?.Invoke (this, EventArgs.Empty);
+	}
+	#endregion Constructors and Initialization
 }

+ 161 - 118
Terminal.Gui/View/ViewDrawing.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 
@@ -8,9 +7,12 @@ namespace Terminal.Gui;
 public partial class View {
 	ColorScheme _colorScheme;
 
+	// The view-relative region that needs to be redrawn. Marked internal for unit tests.
+	internal Rect _needsDisplayRect = Rect.Empty;
+
 	/// <summary>
 	/// The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s
-	/// color scheme. 
+	/// color scheme.
 	/// </summary>
 	public virtual ColorScheme ColorScheme {
 		get {
@@ -27,12 +29,47 @@ public partial class View {
 		}
 	}
 
+	/// <summary>
+	/// Gets or sets whether the view needs to be redrawn.
+	/// </summary>
+	public bool NeedsDisplay {
+		get => _needsDisplayRect != Rect.Empty;
+		set {
+			if (value) {
+				SetNeedsDisplay ();
+			} else {
+				ClearNeedsDisplay ();
+			}
+		}
+	}
+
+	/// <summary>
+	/// Gets whether any Subviews need to be redrawn.
+	/// </summary>
+	public bool SubViewNeedsDisplay { get; private set; }
+
+	/// <summary>
+	/// The canvas that any line drawing that is to be shared by subviews of this view should add lines to.
+	/// </summary>
+	/// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
+	public LineCanvas LineCanvas { get; } = new ();
+
+	/// <summary>
+	/// Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for
+	/// rendering any border lines. If <see langword="true"/> the rendering of any borders drawn
+	/// by this Frame will be done by it's parent's SuperView. If <see langword="false"/> (the default)
+	/// this View's <see cref="OnDrawAdornments"/> method will be called to render the borders.
+	/// </summary>
+	public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
+
 	/// <summary>
 	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 	/// </summary>
-	/// <returns><see cref="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// <returns>
+	/// <see cref="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/>
 	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
-	/// If it's overridden can return other values.</returns>
+	/// If it's overridden can return other values.
+	/// </returns>
 	public virtual Attribute GetNormalColor ()
 	{
 		var cs = ColorScheme;
@@ -45,18 +82,36 @@ public partial class View {
 	/// <summary>
 	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 	/// </summary>
-	/// <returns><see cref="Terminal.Gui.ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// <returns>
+	/// <see cref="Terminal.Gui.ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/>
 	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
-	/// If it's overridden can return other values.</returns>
-	public virtual Attribute GetFocusColor () => Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+	/// If it's overridden can return other values.
+	/// </returns>
+	public virtual Attribute GetFocusColor ()
+	{
+		var cs = ColorScheme;
+		if (ColorScheme == null) {
+			cs = new ColorScheme ();
+		}
+		return Enabled ? cs.Focus : cs.Disabled;
+	}
 
 	/// <summary>
 	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 	/// </summary>
-	/// <returns><see cref="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// <returns>
+	/// <see cref="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/>
 	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
-	/// If it's overridden can return other values.</returns>
-	public virtual Attribute GetHotNormalColor () => Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled;
+	/// If it's overridden can return other values.
+	/// </returns>
+	public virtual Attribute GetHotNormalColor ()
+	{
+		var cs = ColorScheme;
+		if (ColorScheme == null) {
+			cs = new ColorScheme ();
+		}
+		return Enabled ? cs.HotNormal : cs.Disabled;
+	}
 
 	/// <summary>
 	/// Displays the specified character in the specified column and row of the View.
@@ -82,24 +137,7 @@ public partial class View {
 	protected void ClearNeedsDisplay ()
 	{
 		_needsDisplayRect = Rect.Empty;
-		_subViewNeedsDisplay = false;
-	}
-
-	// The view-relative region that needs to be redrawn. Marked internal for unit tests.
-	internal Rect _needsDisplayRect = Rect.Empty;
-
-	/// <summary>
-	/// Gets or sets whether the view needs to be redrawn.
-	/// </summary>
-	public bool NeedsDisplay {
-		get => _needsDisplayRect != Rect.Empty;
-		set {
-			if (value) {
-				SetNeedsDisplay ();
-			} else {
-				ClearNeedsDisplay ();
-			}
-		}
+		SubViewNeedsDisplay = false;
 	}
 
 	/// <summary>
@@ -133,18 +171,18 @@ public partial class View {
 		if (_needsDisplayRect.IsEmpty) {
 			_needsDisplayRect = region;
 		} else {
-			int x = Math.Min (_needsDisplayRect.X, region.X);
-			int y = Math.Min (_needsDisplayRect.Y, region.Y);
-			int w = Math.Max (_needsDisplayRect.Width, region.Width);
-			int h = Math.Max (_needsDisplayRect.Height, region.Height);
+			var x = Math.Min (_needsDisplayRect.X, region.X);
+			var y = Math.Min (_needsDisplayRect.Y, region.Y);
+			var w = Math.Max (_needsDisplayRect.Width, region.Width);
+			var h = Math.Max (_needsDisplayRect.Height, region.Height);
 			_needsDisplayRect = new Rect (x, y, w, h);
 		}
 		_superView?.SetSubViewNeedsDisplay ();
 
 		if (_needsDisplayRect.X < Bounds.X ||
-		_needsDisplayRect.Y < Bounds.Y ||
-		_needsDisplayRect.Width > Bounds.Width ||
-		_needsDisplayRect.Height > Bounds.Height) {
+		    _needsDisplayRect.Y < Bounds.Y ||
+		    _needsDisplayRect.Width > Bounds.Width ||
+		    _needsDisplayRect.Height > Bounds.Height) {
 			Margin?.SetNeedsDisplay (Margin.Bounds);
 			Border?.SetNeedsDisplay (Border.Bounds);
 			Padding?.SetNeedsDisplay (Padding.Bounds);
@@ -164,31 +202,24 @@ public partial class View {
 		}
 	}
 
-	/// <summary>
-	/// Gets whether any Subviews need to be redrawn.
-	/// </summary>
-	public bool SubViewNeedsDisplay => _subViewNeedsDisplay;
-
-	bool _subViewNeedsDisplay;
-
 	/// <summary>
 	/// Indicates that any Subviews (in the <see cref="Subviews"/> list) need to be repainted.
 	/// </summary>
 	public void SetSubViewNeedsDisplay ()
 	{
-		_subViewNeedsDisplay = true;
-		if (_superView != null && !_superView._subViewNeedsDisplay) {
+		SubViewNeedsDisplay = true;
+		if (_superView != null && !_superView.SubViewNeedsDisplay) {
 			_superView.SetSubViewNeedsDisplay ();
 		}
 	}
 
 	/// <summary>
-	///   Clears the <see cref="Bounds"/> with the normal background color.
+	/// Clears the <see cref="Bounds"/> with the normal background color.
 	/// </summary>
 	/// <remarks>
-	///   <para>
-	///     This clears the Bounds used by this view.
-	///   </para>
+	///         <para>
+	///         This clears the Bounds used by this view.
+	///         </para>
 	/// </remarks>
 	public void Clear ()
 	{
@@ -202,7 +233,7 @@ public partial class View {
 	// "View APIs only deal with View-relative coords". This is only used by ComboBox which can
 	// be refactored to use the View-relative version.
 	/// <summary>
-	///   Clears the specified screen-relative rectangle with the normal background. 
+	/// Clears the specified screen-relative rectangle with the normal background.
 	/// </summary>
 	/// <remarks>
 	/// </remarks>
@@ -220,10 +251,10 @@ public partial class View {
 	// Clips a rectangle in screen coordinates to the dimensions currently available on the screen
 	internal Rect ScreenClip (Rect regionScreen)
 	{
-		int x = regionScreen.X < 0 ? 0 : regionScreen.X;
-		int y = regionScreen.Y < 0 ? 0 : regionScreen.Y;
-		int w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width;
-		int h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height;
+		var x = regionScreen.X < 0 ? 0 : regionScreen.X;
+		var y = regionScreen.Y < 0 ? 0 : regionScreen.Y;
+		var w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width;
+		var h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height;
 
 		return new Rect (x, y, w, h);
 	}
@@ -231,14 +262,21 @@ public partial class View {
 	/// <summary>
 	/// Expands the <see cref="ConsoleDriver"/>'s clip region to include <see cref="Bounds"/>.
 	/// </summary>
-	/// <returns>The current screen-relative clip region, which can be then re-applied by setting <see cref="ConsoleDriver.Clip"/>.</returns>
+	/// <returns>
+	/// The current screen-relative clip region, which can be then re-applied by setting
+	/// <see cref="ConsoleDriver.Clip"/>.
+	/// </returns>
 	/// <remarks>
-	/// <para>
-	/// If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to <see cref="Rect.Empty"/>.
-	/// </para>
+	///         <para>
+	///         If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to
+	///         <see cref="Rect.Empty"/>.
+	///         </para>
 	/// </remarks>
 	public Rect ClipToBounds ()
 	{
+		if (Driver == null) {
+			return Rect.Empty;
+		}
 		var previous = Driver.Clip;
 		Driver.Clip = Rect.Intersect (previous, BoundsToScreen (Bounds));
 		return previous;
@@ -251,15 +289,18 @@ public partial class View {
 	/// <param name="hotColor">Hot color.</param>
 	/// <param name="normalColor">Normal color.</param>
 	/// <remarks>
-	/// <para>The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by default.</para>
-	/// <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
+	///         <para>
+	///         The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by
+	///         default.
+	///         </para>
+	///         <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
 	/// </remarks>
 	public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
 	{
 		var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
 		Application.Driver.SetAttribute (normalColor);
-		foreach (char rune in text) {
-			if (rune == hotkeySpec.Value) {
+		foreach (var rune in text.EnumerateRunes ()) {
+			if (rune == new Rune(hotkeySpec.Value)) {
 				Application.Driver.SetAttribute (hotColor);
 				continue;
 			}
@@ -272,7 +313,10 @@ public partial class View {
 	/// Utility function to draw strings that contains a hotkey using a <see cref="ColorScheme"/> and the "focused" state.
 	/// </summary>
 	/// <param name="text">String to display, the underscore before a letter flags the next letter as the hotkey.</param>
-	/// <param name="focused">If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise the regular ones.</param>
+	/// <param name="focused">
+	/// If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise
+	/// the regular ones.
+	/// </param>
 	/// <param name="scheme">The color scheme to use.</param>
 	public void DrawHotString (string text, bool focused, ColorScheme scheme)
 	{
@@ -295,32 +339,19 @@ public partial class View {
 			return;
 		}
 
-		BoundsToScreen (col, row, out int rCol, out int rRow, false);
+		BoundsToScreen (col, row, out var rCol, out var rRow, false);
 		Driver?.Move (rCol, rRow);
 	}
 
-	/// <summary>
-	/// The canvas that any line drawing that is to be shared by subviews of this view should add lines to.
-	/// </summary>
-	/// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
-	public LineCanvas LineCanvas { get; } = new ();
-
-	/// <summary>
-	/// Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for
-	/// rendering any border lines. If <see langword="true"/> the rendering of any borders drawn
-	/// by this Frame will be done by it's parent's SuperView. If <see langword="false"/> (the default)
-	/// this View's <see cref="OnDrawFrames()"/> method will be called to render the borders.
-	/// </summary>
-	public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
-
 	// TODO: Make this cancelable
 	/// <summary>
-	/// Prepares <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the <see cref="LineCanvas"/> of 
-	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this 
+	/// Prepares <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the
+	/// <see cref="LineCanvas"/> of
+	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this
 	/// method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
 	/// </summary>
 	/// <returns></returns>
-	public virtual bool OnDrawFrames ()
+	public virtual bool OnDrawAdornments ()
 	{
 		if (!IsInitialized) {
 			return false;
@@ -336,34 +367,35 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Draws the view. Causes the following virtual methods to be called (along with their related events): 
+	/// Draws the view. Causes the following virtual methods to be called (along with their related events):
 	/// <see cref="OnDrawContent"/>, <see cref="OnDrawContentComplete"/>.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	///    Always use <see cref="Bounds"/> (view-relative) when calling <see cref="OnDrawContent(Rect)"/>, NOT <see cref="Frame"/> (superview-relative).
-	/// </para>
-	/// <para>
-	///    Views should set the color that they want to use on entry, as otherwise this will inherit
-	///    the last color that was set globally on the driver.
-	/// </para>
-	/// <para>
-	///    Overrides of <see cref="OnDrawContent(Rect)"/> must ensure they do not set <c>Driver.Clip</c> to a clip region
-	///    larger than the <ref name="Bounds"/> property, as this will cause the driver to clip the entire region.
-	/// </para>
+	///         <para>
+	///         Always use <see cref="Bounds"/> (view-relative) when calling <see cref="OnDrawContent(Rect)"/>, NOT
+	///         <see cref="Frame"/> (superview-relative).
+	///         </para>
+	///         <para>
+	///         Views should set the color that they want to use on entry, as otherwise this will inherit
+	///         the last color that was set globally on the driver.
+	///         </para>
+	///         <para>
+	///         Overrides of <see cref="OnDrawContent(Rect)"/> must ensure they do not set <c>Driver.Clip</c> to a clip region
+	///         larger than the <ref name="Bounds"/> property, as this will cause the driver to clip the entire region.
+	///         </para>
 	/// </remarks>
 	public void Draw ()
 	{
 		if (!CanBeVisible (this)) {
 			return;
 		}
-		OnDrawFrames ();
+		OnDrawAdornments ();
 
 		var prevClip = ClipToBounds ();
 
 		if (ColorScheme != null) {
 			//Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
-			Driver.SetAttribute (GetNormalColor ());
+			Driver?.SetAttribute (GetNormalColor ());
 		}
 
 		// Invoke DrawContentEvent
@@ -374,7 +406,9 @@ public partial class View {
 			OnDrawContent (Bounds);
 		}
 
-		Driver.Clip = prevClip;
+		if (Driver != null) {
+			Driver.Clip = prevClip;
+		}
 
 		OnRenderLineCanvas ();
 		// Invoke DrawContentCompleteEvent
@@ -387,8 +421,9 @@ public partial class View {
 
 	// TODO: Make this cancelable
 	/// <summary>
-	/// Renders <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the <see cref="LineCanvas"/> of 
-	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this 
+	/// Renders <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the
+	/// <see cref="LineCanvas"/> of
+	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this
 	/// method will cause the <see cref="LineCanvas"/> to be rendered.
 	/// </summary>
 	/// <returns></returns>
@@ -411,7 +446,7 @@ public partial class View {
 		}
 
 		if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) {
-			foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) {
+			foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas)) {
 				// Combine the LineCanvas'
 				LineCanvas.Merge (subview.LineCanvas);
 				subview.LineCanvas.Clear ();
@@ -434,27 +469,31 @@ public partial class View {
 	/// Event invoked when the content area of the View is to be drawn.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	/// Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.
-	/// </para>
-	/// <para>
-	/// Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.
-	/// </para>
+	///         <para>
+	///         Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.
+	///         </para>
+	///         <para>
+	///         Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>
+	///         .
+	///         </para>
 	/// </remarks>
 	public event EventHandler<DrawEventArgs> DrawContent;
 
 	/// <summary>
-	/// Enables overrides to draw infinitely scrolled content and/or a background behind added controls. 
+	/// Enables overrides to draw infinitely scrolled content and/or a background behind added controls.
 	/// </summary>
-	/// <param name="contentArea">The view-relative rectangle describing the currently visible viewport into the <see cref="View"/></param>
+	/// <param name="contentArea">
+	/// The view-relative rectangle describing the currently visible viewport into the
+	/// <see cref="View"/>
+	/// </param>
 	/// <remarks>
-	/// This method will be called before any subviews added with <see cref="Add(View)"/> have been drawn. 
+	/// This method will be called before any subviews added with <see cref="Add(View)"/> have been drawn.
 	/// </remarks>
 	public virtual void OnDrawContent (Rect contentArea)
 	{
 		if (NeedsDisplay) {
 			if (SuperView != null) {
-				Clear (BoundsToScreen (Bounds));
+				Clear (BoundsToScreen (contentArea));
 			}
 
 			if (!string.IsNullOrEmpty (TextFormatter.Text)) {
@@ -463,7 +502,7 @@ public partial class View {
 				}
 			}
 			// This should NOT clear 
-			TextFormatter?.Draw (BoundsToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
+			TextFormatter?.Draw (BoundsToScreen (contentArea), HasFocus ? GetFocusColor () : GetNormalColor (),
 				HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
 				Rect.Empty, false);
 			SetSubViewNeedsDisplay ();
@@ -475,8 +514,8 @@ public partial class View {
 			var subviewsNeedingDraw = _subviews.Where (
 				view => view.Visible &&
 					(view.NeedsDisplay ||
-					view.SubViewNeedsDisplay ||
-					view.LayoutNeeded)
+					 view.SubViewNeedsDisplay ||
+					 view.LayoutNeeded)
 			);
 
 			foreach (var view in subviewsNeedingDraw) {
@@ -499,19 +538,23 @@ public partial class View {
 	/// Event invoked when the content area of the View is completed drawing.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	/// Will be invoked after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
-	/// </para>
-	/// <para>
-	/// Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.
-	/// </para>
+	///         <para>
+	///         Will be invoked after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
+	///         </para>
+	///         <para>
+	///         Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>
+	///         .
+	///         </para>
 	/// </remarks>
 	public event EventHandler<DrawEventArgs> DrawContentComplete;
 
 	/// <summary>
 	/// Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed controls.
 	/// </summary>
-	/// <param name="contentArea">The view-relative rectangle describing the currently visible viewport into the <see cref="View"/></param>
+	/// <param name="contentArea">
+	/// The view-relative rectangle describing the currently visible viewport into the
+	/// <see cref="View"/>
+	/// </param>
 	/// <remarks>
 	/// This method will be called after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
 	/// </remarks>

+ 1 - 1
Terminal.Gui/View/ViewKeyboard.cs

@@ -194,7 +194,7 @@ public partial class View {
 		if (TextFormatter == null || HotKeySpecifier == new Rune ('\xFFFF')) {
 			return; // throw new InvalidOperationException ("Can't set HotKey unless a TextFormatter has been created");
 		}
-		if (TextFormatter.FindHotKey (_text, HotKeySpecifier, true, out _, out var hk)) {
+		if (TextFormatter.FindHotKey (_text, HotKeySpecifier, out _, out var hk)) {
 			if (_hotKey.KeyCode != hk) {
 				HotKey = hk;
 			}

+ 98 - 92
Terminal.Gui/View/ViewMouse.cs

@@ -1,114 +1,120 @@
 using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-
-namespace Terminal.Gui {
-	public partial class View  {
-		/// <summary>
-		/// Event fired when the view receives the mouse event for the first time.
-		/// </summary>
-		public event EventHandler<MouseEventEventArgs> MouseEnter;
-
-		/// <summary>
-		/// Event fired when the view receives a mouse event for the last time.
-		/// </summary>
-		public event EventHandler<MouseEventEventArgs> MouseLeave;
-
-		/// <summary>
-		/// Event fired when a mouse event is generated.
-		/// </summary>
-		public event EventHandler<MouseEventEventArgs> MouseClick;
-
-		/// <inheritdoc/>
-		public override bool OnMouseEnter (MouseEvent mouseEvent)
-		{
-			if (!Enabled) {
-				return true;
-			}
-
-			if (!CanBeVisible (this)) {
-				return false;
-			}
 
-			var args = new MouseEventEventArgs (mouseEvent);
-			MouseEnter?.Invoke (this, args);
-
-			return args.Handled || base.OnMouseEnter (mouseEvent);
+namespace Terminal.Gui; 
+
+public partial class View {
+
+	/// <summary>
+	/// Gets or sets a value indicating whether this <see cref="View"/> wants mouse position reports.
+	/// </summary>
+	/// <value><see langword="true"/> if want mouse position reports; otherwise, <see langword="false"/>.</value>
+	public virtual bool WantMousePositionReports { get; set; }
+
+	/// <summary>
+	/// Gets or sets a value indicating whether this <see cref="View"/> want continuous button pressed event.
+	/// </summary>
+	public virtual bool WantContinuousButtonPressed { get; set; }
+
+	/// <summary>
+	/// Event fired when the view receives the mouse event for the first time.
+	/// </summary>
+	public event EventHandler<MouseEventEventArgs> MouseEnter;
+
+	/// <summary>
+	/// Event fired when the view receives a mouse event for the last time.
+	/// </summary>
+	public event EventHandler<MouseEventEventArgs> MouseLeave;
+
+	/// <summary>
+	/// Event fired when a mouse event is generated.
+	/// </summary>
+	public event EventHandler<MouseEventEventArgs> MouseClick;
+
+	/// <inheritdoc/>
+	public override bool OnMouseEnter (MouseEvent mouseEvent)
+	{
+		if (!Enabled) {
+			return true;
 		}
 
-		/// <inheritdoc/>
-		public override bool OnMouseLeave (MouseEvent mouseEvent)
-		{
-			if (!Enabled) {
-				return true;
-			}
+		if (!CanBeVisible (this)) {
+			return false;
+		}
 
-			if (!CanBeVisible (this)) {
-				return false;
-			}
+		var args = new MouseEventEventArgs (mouseEvent);
+		MouseEnter?.Invoke (this, args);
 
-			var args = new MouseEventEventArgs (mouseEvent);
-			MouseLeave?.Invoke (this, args);
+		return args.Handled || base.OnMouseEnter (mouseEvent);
+	}
 
-			return args.Handled || base.OnMouseLeave (mouseEvent);
+	/// <inheritdoc/>
+	public override bool OnMouseLeave (MouseEvent mouseEvent)
+	{
+		if (!Enabled) {
+			return true;
 		}
 
-		/// <summary>
-		/// Method invoked when a mouse event is generated
-		/// </summary>
-		/// <param name="mouseEvent"></param>
-		/// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
-		public virtual bool OnMouseEvent (MouseEvent mouseEvent)
-		{
-			if (!Enabled) {
-				return true;
-			}
+		if (!CanBeVisible (this)) {
+			return false;
+		}
 
-			if (!CanBeVisible (this)) {
-				return false;
-			}
+		var args = new MouseEventEventArgs (mouseEvent);
+		MouseLeave?.Invoke (this, args);
 
-			var args = new MouseEventEventArgs (mouseEvent);
-			if (OnMouseClick (args))
-				return true;
-			if (MouseEvent (mouseEvent))
-				return true;
+		return args.Handled || base.OnMouseLeave (mouseEvent);
+	}
 
-			if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
-				if (CanFocus && !HasFocus && SuperView != null) {
-					SuperView.SetFocus (this);
-					SetNeedsDisplay ();
-				}
+	/// <summary>
+	/// Method invoked when a mouse event is generated
+	/// </summary>
+	/// <param name="mouseEvent"></param>
+	/// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
+	public virtual bool OnMouseEvent (MouseEvent mouseEvent)
+	{
+		if (!Enabled) {
+			return true;
+		}
 
-				return true;
-			}
+		if (!CanBeVisible (this)) {
 			return false;
 		}
 
-		/// <summary>
-		/// Invokes the MouseClick event.
-		/// </summary>
-		protected bool OnMouseClick (MouseEventEventArgs args)
-		{
-			if (!Enabled) {
-				return true;
+		var args = new MouseEventEventArgs (mouseEvent);
+		if (MouseEvent (mouseEvent)) {
+			return true;
+		}
+
+		if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
+			if (CanFocus && !HasFocus && SuperView != null) {
+				SuperView.SetFocus (this);
+				SetNeedsDisplay ();
 			}
 
-			MouseClick?.Invoke (this, args);
-			return args.Handled;
+			return OnMouseClick (args);
+		}
+		if (mouseEvent.Flags == MouseFlags.Button2Clicked) {
+			return OnMouseClick (args);
+		}
+		if (mouseEvent.Flags == MouseFlags.Button3Clicked) {
+			return OnMouseClick (args);
+		}
+		if (mouseEvent.Flags == MouseFlags.Button4Clicked) {
+			return OnMouseClick (args);
 		}
 
-		/// <summary>
-		/// Gets or sets a value indicating whether this <see cref="View"/> wants mouse position reports.
-		/// </summary>
-		/// <value><see langword="true"/> if want mouse position reports; otherwise, <see langword="false"/>.</value>
-		public virtual bool WantMousePositionReports { get; set; }
+		return false;
+	}
+
+	/// <summary>
+	/// Invokes the MouseClick event.
+	/// </summary>
+	protected bool OnMouseClick (MouseEventEventArgs args)
+	{
+		if (!Enabled) {
+			return true;
+		}
 
-		/// <summary>
-		/// Gets or sets a value indicating whether this <see cref="View"/> want continuous button pressed event.
-		/// </summary>
-		public virtual bool WantContinuousButtonPressed { get; set; }
+		MouseClick?.Invoke (this, args);
+		return args.Handled;
 	}
-}
+}

+ 608 - 612
Terminal.Gui/View/ViewSubViews.cs

@@ -1,729 +1,725 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-
-namespace Terminal.Gui {
-	public partial class View {
-		static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
-
-		View _superView = null;
-
-		/// <summary>
-		/// Returns the container for this view, or null if this view has not been added to a container.
-		/// </summary>
-		/// <value>The super view.</value>
-		public virtual View SuperView {
-			get {
-				return _superView;
-			}
-			set {
-				throw new NotImplementedException ();
-			}
-		}
 
-		List<View> _subviews; // This is null, and allocated on demand.
-		/// <summary>
-		/// This returns a list of the subviews contained by this view.
-		/// </summary>
-		/// <value>The subviews.</value>
-		public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
-
-		// Internally, we use InternalSubviews rather than subviews, as we do not expect us
-		// to make the same mistakes our users make when they poke at the Subviews.
-		internal IList<View> InternalSubviews => _subviews ?? _empty;
-
-		/// <summary>
-		/// Returns a value indicating if this View is currently on Top (Active)
-		/// </summary>
-		public bool IsCurrentTop => Application.Current == this;
-
-		/// <summary>
-		/// Event fired when this view is added to another.
-		/// </summary>
-		public event EventHandler<SuperViewChangedEventArgs> Added;
-
-		internal bool _addingView;
-
-		/// <summary>
-		///   Adds a subview (child) to this view.
-		/// </summary>
-		/// <remarks>
-		/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. 
-		/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/> 
-		/// </remarks>
-		public virtual void Add (View view)
-		{
-			if (view == null) {
-				return;
-			}
-			if (_subviews == null) {
-				_subviews = new List<View> ();
-			}
-			if (_tabIndexes == null) {
-				_tabIndexes = new List<View> ();
-			}
-			_subviews.Add (view);
-			_tabIndexes.Add (view);
-			view._superView = this;
-			if (view.CanFocus) {
-				_addingView = true;
-				if (SuperView?.CanFocus == false) {
-					SuperView._addingView = true;
-					SuperView.CanFocus = true;
-					SuperView._addingView = false;
-				}
-				CanFocus = true;
-				view._tabIndex = _tabIndexes.IndexOf (view);
-				_addingView = false;
-			}
-			if (view.Enabled && !Enabled) {
-				view._oldEnabled = true;
-				view.Enabled = false;
-			}
+namespace Terminal.Gui;
 
-			OnAdded (new SuperViewChangedEventArgs (this, view));
-			if (IsInitialized && !view.IsInitialized) {
-				view.BeginInit ();
-				view.EndInit ();
-			}
+public partial class View {
+	static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
 
-			SetNeedsLayout ();
-			SetNeedsDisplay ();
+	internal bool _addingView;
+
+	List<View> _subviews; // This is null, and allocated on demand.
+
+	View _superView;
+
+	/// <summary>
+	/// Returns the container for this view, or null if this view has not been added to a container.
+	/// </summary>
+	/// <value>The super view.</value>
+	public virtual View SuperView {
+		get => _superView;
+		set => throw new NotImplementedException ();
+	}
+
+	/// <summary>
+	/// This returns a list of the subviews contained by this view.
+	/// </summary>
+	/// <value>The subviews.</value>
+	public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
+
+	// Internally, we use InternalSubviews rather than subviews, as we do not expect us
+	// to make the same mistakes our users make when they poke at the Subviews.
+	internal IList<View> InternalSubviews => _subviews ?? _empty;
+
+	/// <summary>
+	/// Returns a value indicating if this View is currently on Top (Active)
+	/// </summary>
+	public bool IsCurrentTop => Application.Current == this;
+
+	/// <summary>
+	/// Indicates whether the view was added to <see cref="SuperView"/>.
+	/// </summary>
+	public bool IsAdded { get; private set; }
+
+	/// <summary>
+	/// Event fired when this view is added to another.
+	/// </summary>
+	public event EventHandler<SuperViewChangedEventArgs> Added;
+
+	/// <summary>
+	/// Adds a subview (child) to this view.
+	/// </summary>
+	/// <remarks>
+	/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property.
+	/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+	/// </remarks>
+	public virtual void Add (View view)
+	{
+		if (view == null) {
+			return;
+		}
+		if (_subviews == null) {
+			_subviews = new List<View> ();
+		}
+		if (_tabIndexes == null) {
+			_tabIndexes = new List<View> ();
+		}
+		_subviews.Add (view);
+		_tabIndexes.Add (view);
+		view._superView = this;
+		if (view.CanFocus) {
+			_addingView = true;
+			if (SuperView?.CanFocus == false) {
+				SuperView._addingView = true;
+				SuperView.CanFocus = true;
+				SuperView._addingView = false;
+			}
+			CanFocus = true;
+			view._tabIndex = _tabIndexes.IndexOf (view);
+			_addingView = false;
+		}
+		if (view.Enabled && !Enabled) {
+			view._oldEnabled = true;
+			view.Enabled = false;
 		}
 
-		/// <summary>
-		/// Adds the specified views (children) to the view.
-		/// </summary>
-		/// <param name="views">Array of one or more views (can be optional parameter).</param>
-		/// <remarks>
-		/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. 
-		/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/> 
-		/// </remarks>
-		public void Add (params View [] views)
-		{
-			if (views == null) {
-				return;
-			}
-			foreach (var view in views) {
-				Add (view);
-			}
+		OnAdded (new SuperViewChangedEventArgs (this, view));
+		if (IsInitialized && !view.IsInitialized) {
+			view.BeginInit ();
+			view.EndInit ();
 		}
 
-		/// <summary>
-		/// Method invoked when a subview is being added to this view.
-		/// </summary>
-		/// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
-		public virtual void OnAdded (SuperViewChangedEventArgs e)
-		{
-			var view = e.Child;
-			view.IsAdded = true;
-			view.OnResizeNeeded ();
-			view._x ??= view._frame.X;
-			view._y ??= view._frame.Y;
-			view._width ??= view._frame.Width;
-			view._height ??= view._frame.Height;
-
-			view.Added?.Invoke (this, e);
-		}
-
-		/// <summary>
-		/// Indicates whether the view was added to <see cref="SuperView"/>.
-		/// </summary>
-		public bool IsAdded { get; private set; }
-
-		/// <summary>
-		/// Event fired when this view is removed from another.
-		/// </summary>
-		public event EventHandler<SuperViewChangedEventArgs> Removed;
-
-		/// <summary>
-		///   Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
-		/// </summary>
-		public virtual void RemoveAll ()
-		{
-			if (_subviews == null) {
-				return;
-			}
+		SetNeedsLayout ();
+		SetNeedsDisplay ();
+	}
 
-			while (_subviews.Count > 0) {
-				Remove (_subviews [0]);
-			}
+	/// <summary>
+	/// Adds the specified views (children) to the view.
+	/// </summary>
+	/// <param name="views">Array of one or more views (can be optional parameter).</param>
+	/// <remarks>
+	/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property.
+	/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+	/// </remarks>
+	public void Add (params View [] views)
+	{
+		if (views == null) {
+			return;
+		}
+		foreach (var view in views) {
+			Add (view);
 		}
+	}
 
-		/// <summary>
-		///   Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
-		/// </summary>
-		/// <remarks>
-		/// </remarks>
-		public virtual void Remove (View view)
-		{
-			if (view == null || _subviews == null) return;
-
-			var touched = view.Frame;
-			_subviews.Remove (view);
-			_tabIndexes.Remove (view);
-			view._superView = null;
-			view._tabIndex = -1;
-			SetNeedsLayout ();
-			SetNeedsDisplay ();
+	/// <summary>
+	/// Method invoked when a subview is being added to this view.
+	/// </summary>
+	/// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
+	public virtual void OnAdded (SuperViewChangedEventArgs e)
+	{
+		var view = e.Child;
+		view.IsAdded = true;
+		view.OnResizeNeeded ();
+		view.Added?.Invoke (this, e);
+	}
 
-			foreach (var v in _subviews) {
-				if (v.Frame.IntersectsWith (touched))
-					view.SetNeedsDisplay ();
-			}
-			OnRemoved (new SuperViewChangedEventArgs (this, view));
-			if (_focused == view) {
-				_focused = null;
-			}
+	/// <summary>
+	/// Event fired when this view is removed from another.
+	/// </summary>
+	public event EventHandler<SuperViewChangedEventArgs> Removed;
+
+	/// <summary>
+	/// Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+	/// </summary>
+	public virtual void RemoveAll ()
+	{
+		if (_subviews == null) {
+			return;
 		}
 
-		/// <summary>
-		/// Method invoked when a subview is being removed from this view.
-		/// </summary>
-		/// <param name="e">Event args describing the subview being removed.</param>
-		public virtual void OnRemoved (SuperViewChangedEventArgs e)
-		{
-			var view = e.Child;
-			view.IsAdded = false;
-			view.Removed?.Invoke (this, e);
+		while (_subviews.Count > 0) {
+			Remove (_subviews [0]);
 		}
+	}
 
+	/// <summary>
+	/// Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	public virtual void Remove (View view)
+	{
+		if (view == null || _subviews == null) {
+			return;
+		}
 
-		void PerformActionForSubview (View subview, Action<View> action)
-		{
-			if (_subviews.Contains (subview)) {
-				action (subview);
-			}
+		var touched = view.Frame;
+		_subviews.Remove (view);
+		_tabIndexes.Remove (view);
+		view._superView = null;
+		view._tabIndex = -1;
+		SetNeedsLayout ();
+		SetNeedsDisplay ();
 
-			SetNeedsDisplay ();
-			subview.SetNeedsDisplay ();
-		}
-
-		/// <summary>
-		/// Brings the specified subview to the front so it is drawn on top of any other views.
-		/// </summary>
-		/// <param name="subview">The subview to send to the front</param>
-		/// <remarks>
-		///   <seealso cref="SendSubviewToBack"/>.
-		/// </remarks>
-		public void BringSubviewToFront (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				_subviews.Remove (x);
-				_subviews.Add (x);
-			});
-		}
-
-		/// <summary>
-		/// Sends the specified subview to the front so it is the first view drawn
-		/// </summary>
-		/// <param name="subview">The subview to send to the front</param>
-		/// <remarks>
-		///   <seealso cref="BringSubviewToFront(View)"/>.
-		/// </remarks>
-		public void SendSubviewToBack (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				_subviews.Remove (x);
-				_subviews.Insert (0, subview);
-			});
-		}
-
-		/// <summary>
-		/// Moves the subview backwards in the hierarchy, only one step
-		/// </summary>
-		/// <param name="subview">The subview to send backwards</param>
-		/// <remarks>
-		/// If you want to send the view all the way to the back use SendSubviewToBack.
-		/// </remarks>
-		public void SendSubviewBackwards (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				var idx = _subviews.IndexOf (x);
-				if (idx > 0) {
-					_subviews.Remove (x);
-					_subviews.Insert (idx - 1, x);
-				}
-			});
-		}
-
-		/// <summary>
-		/// Moves the subview backwards in the hierarchy, only one step
-		/// </summary>
-		/// <param name="subview">The subview to send backwards</param>
-		/// <remarks>
-		/// If you want to send the view all the way to the back use SendSubviewToBack.
-		/// </remarks>
-		public void BringSubviewForward (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				var idx = _subviews.IndexOf (x);
-				if (idx + 1 < _subviews.Count) {
-					_subviews.Remove (x);
-					_subviews.Insert (idx + 1, x);
-				}
-			});
-		}
-
-		/// <summary>
-		/// Get the top superview of a given <see cref="View"/>.
-		/// </summary>
-		/// <returns>The superview view.</returns>
-		public View GetTopSuperView (View view = null, View superview = null)
-		{
-			View top = superview ?? Application.Top;
-			for (var v = view?.SuperView ?? (this?.SuperView); v != null; v = v.SuperView) {
-				top = v;
-				if (top == superview) {
-					break;
-				}
+		foreach (var v in _subviews) {
+			if (v.Frame.IntersectsWith (touched)) {
+				view.SetNeedsDisplay ();
 			}
-
-			return top;
 		}
+		OnRemoved (new SuperViewChangedEventArgs (this, view));
+		if (Focused == view) {
+			Focused = null;
+		}
+	}
 
+	/// <summary>
+	/// Method invoked when a subview is being removed from this view.
+	/// </summary>
+	/// <param name="e">Event args describing the subview being removed.</param>
+	public virtual void OnRemoved (SuperViewChangedEventArgs e)
+	{
+		var view = e.Child;
+		view.IsAdded = false;
+		view.Removed?.Invoke (this, e);
+	}
 
 
-		#region Focus
-		View _focused = null;
+	void PerformActionForSubview (View subview, Action<View> action)
+	{
+		if (_subviews.Contains (subview)) {
+			action (subview);
+		}
 
-		internal enum Direction {
-			Forward,
-			Backward
+		SetNeedsDisplay ();
+		subview.SetNeedsDisplay ();
+	}
+
+	/// <summary>
+	/// Brings the specified subview to the front so it is drawn on top of any other views.
+	/// </summary>
+	/// <param name="subview">The subview to send to the front</param>
+	/// <remarks>
+	/// <seealso cref="SendSubviewToBack"/>.
+	/// </remarks>
+	public void BringSubviewToFront (View subview) => PerformActionForSubview (subview, x => {
+		_subviews.Remove (x);
+		_subviews.Add (x);
+	});
+
+	/// <summary>
+	/// Sends the specified subview to the front so it is the first view drawn
+	/// </summary>
+	/// <param name="subview">The subview to send to the front</param>
+	/// <remarks>
+	/// <seealso cref="BringSubviewToFront(View)"/>.
+	/// </remarks>
+	public void SendSubviewToBack (View subview) => PerformActionForSubview (subview, x => {
+		_subviews.Remove (x);
+		_subviews.Insert (0, subview);
+	});
+
+	/// <summary>
+	/// Moves the subview backwards in the hierarchy, only one step
+	/// </summary>
+	/// <param name="subview">The subview to send backwards</param>
+	/// <remarks>
+	/// If you want to send the view all the way to the back use SendSubviewToBack.
+	/// </remarks>
+	public void SendSubviewBackwards (View subview) => PerformActionForSubview (subview, x => {
+		var idx = _subviews.IndexOf (x);
+		if (idx > 0) {
+			_subviews.Remove (x);
+			_subviews.Insert (idx - 1, x);
+		}
+	});
+
+	/// <summary>
+	/// Moves the subview backwards in the hierarchy, only one step
+	/// </summary>
+	/// <param name="subview">The subview to send backwards</param>
+	/// <remarks>
+	/// If you want to send the view all the way to the back use SendSubviewToBack.
+	/// </remarks>
+	public void BringSubviewForward (View subview) => PerformActionForSubview (subview, x => {
+		var idx = _subviews.IndexOf (x);
+		if (idx + 1 < _subviews.Count) {
+			_subviews.Remove (x);
+			_subviews.Insert (idx + 1, x);
+		}
+	});
+
+	/// <summary>
+	/// Get the top superview of a given <see cref="View"/>.
+	/// </summary>
+	/// <returns>The superview view.</returns>
+	public View GetTopSuperView (View view = null, View superview = null)
+	{
+		var top = superview ?? Application.Top;
+		for (var v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) {
+			top = v;
+			if (top == superview) {
+				break;
+			}
 		}
 
-		/// <summary>
-		/// Event fired when the view gets focus.
-		/// </summary>
-		public event EventHandler<FocusEventArgs> Enter;
+		return top;
+	}
+
 
-		/// <summary>
-		/// Event fired when the view looses focus.
-		/// </summary>
-		public event EventHandler<FocusEventArgs> Leave;
 
-		Direction _focusDirection;
-		internal Direction FocusDirection {
-			get => SuperView?.FocusDirection ?? _focusDirection;
-			set {
-				if (SuperView != null)
-					SuperView.FocusDirection = value;
-				else
-					_focusDirection = value;
+	#region Focus
+	internal enum Direction {
+		Forward,
+		Backward
+	}
+
+	/// <summary>
+	/// Event fired when the view gets focus.
+	/// </summary>
+	public event EventHandler<FocusEventArgs> Enter;
+
+	/// <summary>
+	/// Event fired when the view looses focus.
+	/// </summary>
+	public event EventHandler<FocusEventArgs> Leave;
+
+	Direction _focusDirection;
+
+	internal Direction FocusDirection {
+		get => SuperView?.FocusDirection ?? _focusDirection;
+		set {
+			if (SuperView != null) {
+				SuperView.FocusDirection = value;
+			} else {
+				_focusDirection = value;
 			}
 		}
+	}
 
 
-		// BUGBUG: v2 - Seems weird that this is in View and not Responder.
-		bool _hasFocus;
+	// BUGBUG: v2 - Seems weird that this is in View and not Responder.
+	bool _hasFocus;
 
-		/// <inheritdoc/>
-		public override bool HasFocus => _hasFocus;
+	/// <inheritdoc/>
+	public override bool HasFocus => _hasFocus;
 
-		void SetHasFocus (bool value, View view, bool force = false)
-		{
-			if (_hasFocus != value || force) {
-				_hasFocus = value;
-				if (value) {
-					OnEnter (view);
-				} else {
-					OnLeave (view);
-				}
-				SetNeedsDisplay ();
+	void SetHasFocus (bool value, View view, bool force = false)
+	{
+		if (_hasFocus != value || force) {
+			_hasFocus = value;
+			if (value) {
+				OnEnter (view);
+			} else {
+				OnLeave (view);
 			}
+			SetNeedsDisplay ();
+		}
 
-			// Remove focus down the chain of subviews if focus is removed
-			if (!value && _focused != null) {
-				var f = _focused;
-				f.OnLeave (view);
-				f.SetHasFocus (false, view);
-				_focused = null;
-			}
+		// Remove focus down the chain of subviews if focus is removed
+		if (!value && Focused != null) {
+			var f = Focused;
+			f.OnLeave (view);
+			f.SetHasFocus (false, view);
+			Focused = null;
 		}
+	}
 
-		/// <summary>
-		/// Event fired when the <see cref="CanFocus"/> value is being changed.
-		/// </summary>
-		public event EventHandler CanFocusChanged;
+	/// <summary>
+	/// Event fired when the <see cref="CanFocus"/> value is being changed.
+	/// </summary>
+	public event EventHandler CanFocusChanged;
 
-		/// <inheritdoc/>
-		public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty);
+	/// <inheritdoc/>
+	public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty);
+
+	bool _oldCanFocus;
+
+	/// <inheritdoc/>
+	public override bool CanFocus {
+		get => base.CanFocus;
+		set {
+			if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) {
+				throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
+			}
+			if (base.CanFocus != value) {
+				base.CanFocus = value;
 
-		bool _oldCanFocus;
-		/// <inheritdoc/>
-		public override bool CanFocus {
-			get => base.CanFocus;
-			set {
-				if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) {
-					throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
+				switch (value) {
+				case false when _tabIndex > -1:
+					TabIndex = -1;
+					break;
+				case true when SuperView?.CanFocus == false && _addingView:
+					SuperView.CanFocus = true;
+					break;
 				}
-				if (base.CanFocus != value) {
-					base.CanFocus = value;
-
-					switch (value) {
-					case false when _tabIndex > -1:
-						TabIndex = -1;
-						break;
-					case true when SuperView?.CanFocus == false && _addingView:
-						SuperView.CanFocus = true;
-						break;
-					}
 
-					if (value && _tabIndex == -1) {
-						TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1;
-					}
-					TabStop = value;
+				if (value && _tabIndex == -1) {
+					TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1;
+				}
+				TabStop = value;
 
-					if (!value && SuperView?.Focused == this) {
-						SuperView._focused = null;
-					}
-					if (!value && HasFocus) {
-						SetHasFocus (false, this);
-						SuperView?.EnsureFocus ();
-						if (SuperView != null && SuperView.Focused == null) {
-							SuperView.FocusNext ();
-							if (SuperView.Focused == null && Application.Current != null) {
-								Application.Current.FocusNext ();
-							}
-							Application.BringOverlappedTopToFront ();
+				if (!value && SuperView?.Focused == this) {
+					SuperView.Focused = null;
+				}
+				if (!value && HasFocus) {
+					SetHasFocus (false, this);
+					SuperView?.EnsureFocus ();
+					if (SuperView != null && SuperView.Focused == null) {
+						SuperView.FocusNext ();
+						if (SuperView.Focused == null && Application.Current != null) {
+							Application.Current.FocusNext ();
 						}
+						Application.BringOverlappedTopToFront ();
 					}
-					if (_subviews != null && IsInitialized) {
-						foreach (var view in _subviews) {
-							if (view.CanFocus != value) {
-								if (!value) {
-									view._oldCanFocus = view.CanFocus;
-									view._oldTabIndex = view._tabIndex;
-									view.CanFocus = false;
-									view._tabIndex = -1;
-								} else {
-									if (_addingView) {
-										view._addingView = true;
-									}
-									view.CanFocus = view._oldCanFocus;
-									view._tabIndex = view._oldTabIndex;
-									view._addingView = false;
+				}
+				if (_subviews != null && IsInitialized) {
+					foreach (var view in _subviews) {
+						if (view.CanFocus != value) {
+							if (!value) {
+								view._oldCanFocus = view.CanFocus;
+								view._oldTabIndex = view._tabIndex;
+								view.CanFocus = false;
+								view._tabIndex = -1;
+							} else {
+								if (_addingView) {
+									view._addingView = true;
 								}
+								view.CanFocus = view._oldCanFocus;
+								view._tabIndex = view._oldTabIndex;
+								view._addingView = false;
 							}
 						}
 					}
-					OnCanFocusChanged ();
-					SetNeedsDisplay ();
 				}
+				OnCanFocusChanged ();
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
 
-		/// <inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			var args = new FocusEventArgs (view);
-			Enter?.Invoke (this, args);
-			if (args.Handled) {
-				return true;
-			}
-			if (base.OnEnter (view)) {
-				return true;
-			}
+	/// <inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		var args = new FocusEventArgs (view);
+		Enter?.Invoke (this, args);
+		if (args.Handled) {
+			return true;
+		}
+		if (base.OnEnter (view)) {
+			return true;
+		}
 
-			return false;
+		return false;
+	}
+
+	/// <inheritdoc/>
+	public override bool OnLeave (View view)
+	{
+		var args = new FocusEventArgs (view);
+		Leave?.Invoke (this, args);
+		if (args.Handled) {
+			return true;
+		}
+		if (base.OnLeave (view)) {
+			return true;
 		}
 
-		/// <inheritdoc/>
-		public override bool OnLeave (View view)
-		{
-			var args = new FocusEventArgs (view);
-			Leave?.Invoke (this, args);
-			if (args.Handled) {
-				return true;
+		Driver?.SetCursorVisibility (CursorVisibility.Invisible);
+		return false;
+	}
+
+	/// <summary>
+	/// Returns the currently focused view inside this view, or null if nothing is focused.
+	/// </summary>
+	/// <value>The focused.</value>
+	public View Focused { get; private set; }
+
+	/// <summary>
+	/// Returns the most focused view in the chain of subviews (the leaf view that has the focus).
+	/// </summary>
+	/// <value>The most focused View.</value>
+	public View MostFocused {
+		get {
+			if (Focused == null) {
+				return null;
 			}
-			if (base.OnLeave (view)) {
-				return true;
+			var most = Focused.MostFocused;
+			if (most != null) {
+				return most;
 			}
-
-			Driver?.SetCursorVisibility (CursorVisibility.Invisible);
-			return false;
+			return Focused;
 		}
+	}
 
-		/// <summary>
-		/// Returns the currently focused view inside this view, or null if nothing is focused.
-		/// </summary>
-		/// <value>The focused.</value>
-		public View Focused => _focused;
-
-		/// <summary>
-		/// Returns the most focused view in the chain of subviews (the leaf view that has the focus).
-		/// </summary>
-		/// <value>The most focused View.</value>
-		public View MostFocused {
-			get {
-				if (Focused == null)
-					return null;
-				var most = Focused.MostFocused;
-				if (most != null)
-					return most;
-				return Focused;
-			}
+	/// <summary>
+	/// Causes the specified subview to have focus.
+	/// </summary>
+	/// <param name="view">View.</param>
+	void SetFocus (View view)
+	{
+		if (view == null) {
+			return;
+		}
+		//Console.WriteLine ($"Request to focus {view}");
+		if (!view.CanFocus || !view.Visible || !view.Enabled) {
+			return;
 		}
+		if (Focused?._hasFocus == true && Focused == view) {
+			return;
+		}
+		if (Focused?._hasFocus == true && Focused?.SuperView == view || view == this) {
 
-		/// <summary>
-		/// Causes the specified subview to have focus.
-		/// </summary>
-		/// <param name="view">View.</param>
-		void SetFocus (View view)
-		{
-			if (view == null) {
-				return;
-			}
-			//Console.WriteLine ($"Request to focus {view}");
-			if (!view.CanFocus || !view.Visible || !view.Enabled) {
-				return;
+			if (!view._hasFocus) {
+				view._hasFocus = true;
 			}
-			if (_focused?._hasFocus == true && _focused == view) {
-				return;
+			return;
+		}
+		// Make sure that this view is a subview
+		View c;
+		for (c = view._superView; c != null; c = c._superView) {
+			if (c == this) {
+				break;
 			}
-			if ((_focused?._hasFocus == true && _focused?.SuperView == view) || view == this) {
+		}
+		if (c == null) {
+			throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+		}
 
-				if (!view._hasFocus) {
-					view._hasFocus = true;
-				}
-				return;
-			}
-			// Make sure that this view is a subview
-			View c;
-			for (c = view._superView; c != null; c = c._superView)
-				if (c == this)
-					break;
-			if (c == null)
-				throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+		if (Focused != null) {
+			Focused.SetHasFocus (false, view);
+		}
 
-			if (_focused != null)
-				_focused.SetHasFocus (false, view);
+		var f = Focused;
+		Focused = view;
+		Focused.SetHasFocus (true, f);
+		Focused.EnsureFocus ();
 
-			var f = _focused;
-			_focused = view;
-			_focused.SetHasFocus (true, f);
-			_focused.EnsureFocus ();
+		// Send focus upwards
+		if (SuperView != null) {
+			SuperView.SetFocus (this);
+		} else {
+			SetFocus (this);
+		}
+	}
 
-			// Send focus upwards
-			if (SuperView != null) {
-				SuperView.SetFocus (this);
-			} else {
-				SetFocus (this);
+	/// <summary>
+	/// Causes the specified view and the entire parent hierarchy to have the focused order updated.
+	/// </summary>
+	public void SetFocus ()
+	{
+		if (!CanBeVisible (this) || !Enabled) {
+			if (HasFocus) {
+				SetHasFocus (false, this);
 			}
+			return;
 		}
 
-		/// <summary>
-		/// Causes the specified view and the entire parent hierarchy to have the focused order updated.
-		/// </summary>
-		public void SetFocus ()
-		{
-			if (!CanBeVisible (this) || !Enabled) {
-				if (HasFocus) {
-					SetHasFocus (false, this);
-				}
-				return;
-			}
+		if (SuperView != null) {
+			SuperView.SetFocus (this);
+		} else {
+			SetFocus (this);
+		}
+	}
 
-			if (SuperView != null) {
-				SuperView.SetFocus (this);
+	/// <summary>
+	/// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does
+	/// nothing.
+	/// </summary>
+	public void EnsureFocus ()
+	{
+		if (Focused == null && _subviews?.Count > 0) {
+			if (FocusDirection == Direction.Forward) {
+				FocusFirst ();
 			} else {
-				SetFocus (this);
+				FocusLast ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does nothing.
-		/// </summary>
-		public void EnsureFocus ()
-		{
-			if (_focused == null && _subviews?.Count > 0) {
-				if (FocusDirection == Direction.Forward) {
-					FocusFirst ();
-				} else {
-					FocusLast ();
-				}
-			}
+	/// <summary>
+	/// Focuses the first focusable subview if one exists.
+	/// </summary>
+	public void FocusFirst ()
+	{
+		if (!CanBeVisible (this)) {
+			return;
 		}
 
-		/// <summary>
-		/// Focuses the first focusable subview if one exists.
-		/// </summary>
-		public void FocusFirst ()
-		{
-			if (!CanBeVisible (this)) {
-				return;
-			}
+		if (_tabIndexes == null) {
+			SuperView?.SetFocus (this);
+			return;
+		}
 
-			if (_tabIndexes == null) {
-				SuperView?.SetFocus (this);
+		foreach (var view in _tabIndexes) {
+			if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) {
+				SetFocus (view);
 				return;
 			}
+		}
+	}
 
-			foreach (var view in _tabIndexes) {
-				if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) {
-					SetFocus (view);
-					return;
-				}
-			}
+	/// <summary>
+	/// Focuses the last focusable subview if one exists.
+	/// </summary>
+	public void FocusLast ()
+	{
+		if (!CanBeVisible (this)) {
+			return;
 		}
 
-		/// <summary>
-		/// Focuses the last focusable subview if one exists.
-		/// </summary>
-		public void FocusLast ()
-		{
-			if (!CanBeVisible (this)) {
-				return;
-			}
+		if (_tabIndexes == null) {
+			SuperView?.SetFocus (this);
+			return;
+		}
 
-			if (_tabIndexes == null) {
-				SuperView?.SetFocus (this);
+		for (var i = _tabIndexes.Count; i > 0;) {
+			i--;
+
+			var v = _tabIndexes [i];
+			if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) {
+				SetFocus (v);
 				return;
 			}
+		}
+	}
 
-			for (var i = _tabIndexes.Count; i > 0;) {
-				i--;
+	/// <summary>
+	/// Focuses the previous view.
+	/// </summary>
+	/// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
+	public bool FocusPrev ()
+	{
+		if (!CanBeVisible (this)) {
+			return false;
+		}
 
-				var v = _tabIndexes [i];
-				if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) {
-					SetFocus (v);
-					return;
-				}
-			}
+		FocusDirection = Direction.Backward;
+		if (_tabIndexes == null || _tabIndexes.Count == 0) {
+			return false;
 		}
 
-		/// <summary>
-		/// Focuses the previous view.
-		/// </summary>
-		/// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
-		public bool FocusPrev ()
-		{
-			if (!CanBeVisible (this)) {
-				return false;
-			}
+		if (Focused == null) {
+			FocusLast ();
+			return Focused != null;
+		}
 
-			FocusDirection = Direction.Backward;
-			if (_tabIndexes == null || _tabIndexes.Count == 0)
-				return false;
+		var focusedIdx = -1;
+		for (var i = _tabIndexes.Count; i > 0;) {
+			i--;
+			var w = _tabIndexes [i];
 
-			if (_focused == null) {
-				FocusLast ();
-				return _focused != null;
+			if (w.HasFocus) {
+				if (w.FocusPrev ()) {
+					return true;
+				}
+				focusedIdx = i;
+				continue;
 			}
+			if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
+				Focused.SetHasFocus (false, w);
 
-			var focusedIdx = -1;
-			for (var i = _tabIndexes.Count; i > 0;) {
-				i--;
-				var w = _tabIndexes [i];
-
-				if (w.HasFocus) {
-					if (w.FocusPrev ())
-						return true;
-					focusedIdx = i;
-					continue;
+				if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) {
+					w.FocusLast ();
 				}
-				if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
-					_focused.SetHasFocus (false, w);
-
-					if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
-						w.FocusLast ();
 
-					SetFocus (w);
-					return true;
-				}
-			}
-			if (_focused != null) {
-				_focused.SetHasFocus (false, this);
-				_focused = null;
+				SetFocus (w);
+				return true;
 			}
-			return false;
 		}
+		if (Focused != null) {
+			Focused.SetHasFocus (false, this);
+			Focused = null;
+		}
+		return false;
+	}
 
-		/// <summary>
-		/// Focuses the next view.
-		/// </summary>
-		/// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
-		public bool FocusNext ()
-		{
-			if (!CanBeVisible (this)) {
-				return false;
-			}
-
-			FocusDirection = Direction.Forward;
-			if (_tabIndexes == null || _tabIndexes.Count == 0)
-				return false;
+	/// <summary>
+	/// Focuses the next view.
+	/// </summary>
+	/// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
+	public bool FocusNext ()
+	{
+		if (!CanBeVisible (this)) {
+			return false;
+		}
 
-			if (_focused == null) {
-				FocusFirst ();
-				return _focused != null;
-			}
-			var focusedIdx = -1;
-			for (var i = 0; i < _tabIndexes.Count; i++) {
-				var w = _tabIndexes [i];
-
-				if (w.HasFocus) {
-					if (w.FocusNext ())
-						return true;
-					focusedIdx = i;
-					continue;
-				}
-				if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
-					_focused.SetHasFocus (false, w);
+		FocusDirection = Direction.Forward;
+		if (_tabIndexes == null || _tabIndexes.Count == 0) {
+			return false;
+		}
 
-					if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
-						w.FocusFirst ();
+		if (Focused == null) {
+			FocusFirst ();
+			return Focused != null;
+		}
+		var focusedIdx = -1;
+		for (var i = 0; i < _tabIndexes.Count; i++) {
+			var w = _tabIndexes [i];
 
-					SetFocus (w);
+			if (w.HasFocus) {
+				if (w.FocusNext ()) {
 					return true;
 				}
+				focusedIdx = i;
+				continue;
 			}
-			if (_focused != null) {
-				_focused.SetHasFocus (false, this);
-				_focused = null;
-			}
-			return false;
-		}
+			if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
+				Focused.SetHasFocus (false, w);
 
-		View GetMostFocused (View view)
-		{
-			if (view == null) {
-				return null;
+				if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) {
+					w.FocusFirst ();
+				}
+
+				SetFocus (w);
+				return true;
 			}
+		}
+		if (Focused != null) {
+			Focused.SetHasFocus (false, this);
+			Focused = null;
+		}
+		return false;
+	}
 
-			return view._focused != null ? GetMostFocused (view._focused) : view;
+	View GetMostFocused (View view)
+	{
+		if (view == null) {
+			return null;
 		}
 
-		/// <summary>
-		///   Positions the cursor in the right position based on the currently focused view in the chain.
-		/// </summary>
-		///    Views that are focusable should override <see cref="PositionCursor"/> to ensure
-		///    the cursor is placed in a location that makes sense. Unix terminals do not have
-		///    a way of hiding the cursor, so it can be distracting to have the cursor left at
-		///    the last focused view. Views should make sure that they place the cursor
-		///    in a visually sensible place.
-		public virtual void PositionCursor ()
-		{
-			if (!CanBeVisible (this) || !Enabled) {
-				return;
-			}
+		return view.Focused != null ? GetMostFocused (view.Focused) : view;
+	}
 
-			// BUGBUG: v2 - This needs to support children of Frames too
+	/// <summary>
+	/// Positions the cursor in the right position based on the currently focused view in the chain.
+	/// </summary>
+	/// Views that are focusable should override
+	/// <see cref="PositionCursor"/>
+	/// to ensure
+	/// the cursor is placed in a location that makes sense. Unix terminals do not have
+	/// a way of hiding the cursor, so it can be distracting to have the cursor left at
+	/// the last focused view. Views should make sure that they place the cursor
+	/// in a visually sensible place.
+	public virtual void PositionCursor ()
+	{
+		if (!CanBeVisible (this) || !Enabled) {
+			return;
+		}
 
-			if (_focused == null && SuperView != null) {
-				SuperView.EnsureFocus ();
-			} else if (_focused?.Visible == true && _focused?.Enabled == true && _focused?.Frame.Width > 0 && _focused.Frame.Height > 0) {
-				_focused.PositionCursor ();
-			} else if (_focused?.Visible == true && _focused?.Enabled == false) {
-				_focused = null;
-			} else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
-				Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
-			} else {
-				Move (_frame.X, _frame.Y);
-			}
+		// BUGBUG: v2 - This needs to support children of Frames too
+
+		if (Focused == null && SuperView != null) {
+			SuperView.EnsureFocus ();
+		} else if (Focused?.Visible == true && Focused?.Enabled == true && Focused?.Frame.Width > 0 && Focused.Frame.Height > 0) {
+			Focused.PositionCursor ();
+		} else if (Focused?.Visible == true && Focused?.Enabled == false) {
+			Focused = null;
+		} else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
+			Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
+		} else {
+			Move (_frame.X, _frame.Y);
 		}
-		#endregion Focus
 	}
-}
+	#endregion Focus
+}

+ 168 - 85
Terminal.Gui/View/ViewText.cs

@@ -7,24 +7,28 @@ public partial class View {
 	string _text;
 
 	/// <summary>
-	///   The text displayed by the <see cref="View"/>.
+	/// The text displayed by the <see cref="View"/>.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	///  The text will be drawn before any subviews are drawn.
-	/// </para>
-	/// <para>
-	///  The text will be drawn starting at the view origin (0, 0) and will be formatted according
-	///  to <see cref="TextAlignment"/> and <see cref="TextDirection"/>. 
-	/// </para>
-	/// <para>
-	///  The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Bounds"/>'s height
-	///  is 1, the text will be clipped.	
-	///  </para>
-	/// <para>
-	///  Set the <see cref="HotKeySpecifier"/> to enable hotkey support. To disable hotkey support set <see cref="HotKeySpecifier"/> to
-	///  <c>(Rune)0xffff</c>.
-	/// </para>
+	///         <para>
+	///         The text will be drawn before any subviews are drawn.
+	///         </para>
+	///         <para>
+	///         The text will be drawn starting at the view origin (0, 0) and will be formatted according
+	///         to <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
+	///         </para>
+	///         <para>
+	///         The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Bounds"/>'s height
+	///         is 1, the text will be clipped.
+	///         </para>
+	///         <para>
+	///         Set the <see cref="HotKeySpecifier"/> to enable hotkey support. To disable hotkey support set
+	///         <see cref="HotKeySpecifier"/> to
+	///         <c>(Rune)0xffff</c>.
+	///         </para>
+	///         <para>
+	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+	///         </para>
 	/// </remarks>
 	public virtual string Text {
 		get => _text;
@@ -32,7 +36,6 @@ public partial class View {
 			_text = value;
 			SetHotKey ();
 			UpdateTextFormatterText ();
-			//TextFormatter.Format ();
 			OnResizeNeeded ();
 
 #if DEBUG
@@ -48,21 +51,10 @@ public partial class View {
 	/// </summary>
 	public TextFormatter TextFormatter { get; set; }
 
-	/// <summary>
-	/// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
-	///  different format than the default.
-	/// </summary>
-	protected virtual void UpdateTextFormatterText ()
-	{
-		if (TextFormatter != null) {
-			TextFormatter.Text = _text;
-		}
-	}
-
 	/// <summary>
 	/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
-	/// or not when <see cref="TextFormatter.WordWrap"/> is enabled. 
-	/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when 
+	/// or not when <see cref="TextFormatter.WordWrap"/> is enabled.
+	/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when
 	/// <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
 	/// </summary>
 	public virtual bool PreserveTrailingSpaces {
@@ -76,8 +68,14 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will redisplay the <see cref="View"/>.
+	/// Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will
+	/// redisplay the <see cref="View"/>.
 	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+	///         </para>
+	/// </remarks>
 	/// <value>The text alignment.</value>
 	public virtual TextAlignment TextAlignment {
 		get => TextFormatter.Alignment;
@@ -89,8 +87,14 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will redisplay the <see cref="View"/>.
+	/// Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will redisplay
+	/// the <see cref="View"/>.
 	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+	///         </para>
+	/// </remarks>
 	/// <value>The text alignment.</value>
 	public virtual VerticalTextAlignment VerticalTextAlignment {
 		get => TextFormatter.VerticalAlignment;
@@ -101,8 +105,14 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the <see cref="View"/>.
+	/// Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the
+	/// <see cref="View"/>.
 	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+	///         </para>
+	/// </remarks>
 	/// <value>The text alignment.</value>
 	public virtual TextDirection TextDirection {
 		get => TextFormatter.Direction;
@@ -112,18 +122,27 @@ public partial class View {
 		}
 	}
 
+	/// <summary>
+	/// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
+	/// different format than the default.
+	/// </summary>
+	protected virtual void UpdateTextFormatterText ()
+	{
+		if (TextFormatter != null) {
+			TextFormatter.Text = _text;
+		}
+	}
+
 	void UpdateTextDirection (TextDirection newDirection)
 	{
-		bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction)
-					!= TextFormatter.IsHorizontalDirection (newDirection);
+		var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection);
 		TextFormatter.Direction = newDirection;
 
-		bool isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _);
+		var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _);
 
 		UpdateTextFormatterText ();
 
-		if (!ValidatePosDim && directionChanged && AutoSize
-		|| ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) {
+		if (!ValidatePosDim && directionChanged && AutoSize || ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) {
 			OnResizeNeeded ();
 		} else if (directionChanged && IsAdded) {
 			ResizeBoundsToFit (Bounds.Size);
@@ -132,16 +151,19 @@ public partial class View {
 		} else {
 			SetFrameToFitText ();
 		}
-		TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+		SetTextFormatterSize ();
 		SetNeedsDisplay ();
 	}
 
 
 	/// <summary>
-	/// Sets the size of the View to the minimum width or height required to fit <see cref="Text"/>. 
+	/// Sets the size of the View to the minimum width or height required to fit <see cref="Text"/>.
 	/// </summary>
-	/// <returns><see langword="true"/> if the size was changed; <see langword="false"/> if <see cref="AutoSize"/> == <see langword="true"/> or
-	/// <see cref="Text"/> will not fit.</returns>
+	/// <returns>
+	/// <see langword="true"/> if the size was changed; <see langword="false"/> if <see cref="AutoSize"/> ==
+	/// <see langword="true"/> or
+	/// <see cref="Text"/> will not fit.
+	/// </returns>
 	/// <remarks>
 	/// Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
 	/// if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
@@ -168,89 +190,150 @@ public partial class View {
 			}
 			sizeRequired = Bounds.Size;
 
-			if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) {
-				switch (TextFormatter.IsVerticalDirection (TextDirection)) {
-				case true:
-					int colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
-					// TODO: v2 - This uses frame.Width; it should only use Bounds
-					if (_frame.Width < colWidth &&
-					(Width == null ||
-					Bounds.Width >= 0 &&
-					Width is Dim.DimAbsolute &&
-					Width.Anchor (0) >= 0 &&
-					Width.Anchor (0) < colWidth)) {
-						sizeRequired = new Size (colWidth, Bounds.Height);
-						return true;
-					}
-					break;
-				default:
-					if (_frame.Height < 1 &&
-					(Height == null ||
-					Height is Dim.DimAbsolute &&
-					Height.Anchor (0) == 0)) {
-						sizeRequired = new Size (Bounds.Width, 1);
-						return true;
-					}
-					break;
+			if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) {
+				return false;
+			}
+
+			switch (TextFormatter.IsVerticalDirection (TextDirection)) {
+			case true:
+				var colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
+				// TODO: v2 - This uses frame.Width; it should only use Bounds
+				if (_frame.Width < colWidth &&
+				    (Width == null ||
+				     Bounds.Width >= 0 &&
+				     Width is Dim.DimAbsolute &&
+				     Width.Anchor (0) >= 0 &&
+				     Width.Anchor (0) < colWidth)) {
+					sizeRequired = new Size (colWidth, Bounds.Height);
+					return true;
+				}
+				break;
+			default:
+				if (_frame.Height < 1 &&
+				    (Height == null ||
+				     Height is Dim.DimAbsolute &&
+				     Height.Anchor (0) == 0)) {
+					sizeRequired = new Size (Bounds.Width, 1);
+					return true;
 				}
+				break;
 			}
 			return false;
 		}
 
 		if (GetMinimumSizeOfText (out var size)) {
+			// TODO: This is a hack.
+			//_width  = size.Width;
+			//_height = size.Height;
 			_frame = new Rect (_frame.Location, size);
+			//throw new InvalidOperationException ("This is a hack.");
 			return true;
 		}
 		return false;
 	}
 
 	/// <summary>
-	/// Gets the width or height of the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> characters 
+	/// Gets the width or height of the <see cref="TextFormatter.HotKeySpecifier"/> characters
 	/// in the <see cref="Text"/> property.
 	/// </summary>
 	/// <remarks>
-	/// Only the first hotkey specifier found in <see cref="Text"/> is supported.
+	/// Only the first HotKey specifier found in <see cref="Text"/> is supported.
 	/// </remarks>
-	/// <param name="isWidth">If <see langword="true"/> (the default) the width required for the hotkey specifier is returned. Otherwise the height is returned.</param>
-	/// <returns>The number of characters required for the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/>. If the text direction specified
-	/// by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is returned.</returns>
+	/// <param name="isWidth">
+	/// If <see langword="true"/> (the default) the width required for the HotKey specifier is returned. Otherwise the height
+	/// is returned.
+	/// </param>
+	/// <returns>
+	/// The number of characters required for the <see cref="TextFormatter.HotKeySpecifier"/>. If the text
+	/// direction specified
+	/// by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is returned.
+	/// </returns>
 	public int GetHotKeySpecifierLength (bool isWidth = true)
 	{
 		if (isWidth) {
 			return TextFormatter.IsHorizontalDirection (TextDirection) &&
-				TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
-				? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
-		} else {
-			return TextFormatter.IsVerticalDirection (TextDirection) &&
-				TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+			       TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
 				? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
 		}
+		return TextFormatter.IsVerticalDirection (TextDirection) &&
+		       TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+			? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
 	}
 
 	/// <summary>
-	/// Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/>.
+	/// Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="TextFormatter.HotKeySpecifier"/>.
 	/// </summary>
 	/// <returns></returns>
-	public Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (),
+	internal Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (),
 		TextFormatter.Size.Height - GetHotKeySpecifierLength (false));
 
 	/// <summary>
-	/// Gets the dimensions required for <see cref="Text"/> accounting for a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> .
+	/// Sets <see cref="TextFormatter"/>.Size to the current <see cref="Bounds"/> size, adjusted for
+	/// <see cref="TextFormatter.HotKeySpecifier"/>.
 	/// </summary>
+	/// <remarks>
+	/// Use this API to set <see cref="TextFormatter.Size"/> when the view has changed such that the
+	/// size required to fit the text has changed.
+	/// changes.
+	/// </remarks>
 	/// <returns></returns>
-	public Size GetTextFormatterSizeNeededForTextAndHotKey ()
+	internal void SetTextFormatterSize ()
 	{
 		if (!IsInitialized) {
-			return Size.Empty;
+			TextFormatter.Size = Size.Empty;
+			return;
 		}
 
 		if (string.IsNullOrEmpty (TextFormatter.Text)) {
-			return Bounds.Size;
+			TextFormatter.Size = Bounds.Size;
+			return;
 		}
 
-		// BUGBUG: This IGNORES what Text is set to, using on only the current View size. This doesn't seem to make sense.
-		// BUGBUG: This uses Frame; in v2 it should be Bounds
-		return new Size (Bounds.Size.Width + GetHotKeySpecifierLength (),
+		TextFormatter.Size = new Size (Bounds.Size.Width + GetHotKeySpecifierLength (),
 			Bounds.Size.Height + GetHotKeySpecifierLength (false));
 	}
+
+	/// <summary>
+	/// Gets the Frame dimensions required to fit <see cref="Text"/> within <see cref="Bounds"/> using the text
+	/// <see cref="Direction"/> specified by the
+	/// <see cref="TextFormatter"/> property and accounting for any <see cref="HotKeySpecifier"/> characters.
+	/// </summary>
+	/// <returns>The <see cref="Size"/> the <see cref="Frame"/> needs to be set to fit the text.</returns>
+	public Size GetAutoSize ()
+	{
+		var x = 0;
+		var y = 0;
+		if (IsInitialized) {
+			x = Bounds.X;
+			y = Bounds.Y;
+		}
+		var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
+		int newWidth = rect.Size.Width - GetHotKeySpecifierLength () + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal);
+		int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical);
+		return new Size (newWidth, newHeight);
+	}
+
+	bool IsValidAutoSize (out Size autoSize)
+	{
+		var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
+		autoSize = new Size (rect.Size.Width - GetHotKeySpecifierLength (),
+			rect.Size.Height - GetHotKeySpecifierLength (false));
+		return !(ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) ||
+			 _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () ||
+			 _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false));
+	}
+
+	bool IsValidAutoSizeWidth (Dim width)
+	{
+		var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
+		var dimValue = width.Anchor (0);
+		return !(ValidatePosDim && !(width is Dim.DimAbsolute) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ());
+	}
+
+	bool IsValidAutoSizeHeight (Dim height)
+	{
+		var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
+		var dimValue = height.Anchor (0);
+		return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false));
+	}
 }

+ 80 - 88
Terminal.Gui/Views/Button.cs

@@ -1,4 +1,4 @@
-//
+//
 // Button.cs: Button control
 //
 // Authors:
@@ -10,65 +10,61 @@ using System.Text;
 
 namespace Terminal.Gui;
 /// <summary>
-///   Button is a <see cref="View"/> that provides an item that invokes raises the <see cref="Clicked"/> event.
+/// Button is a <see cref="View"/> that provides an item that invokes raises the <see cref="Clicked"/> event.
 /// </summary>
 /// <remarks>
-/// <para>
-///   Provides a button showing text that raises the <see cref="Clicked"/> event when clicked on with a mouse
-///   or when the user presses SPACE, ENTER, or the <see cref="View.HotKey"/>. The hot key is the first letter or digit following the first underscore ('_') 
-///   in the button text. 
-/// </para>
-/// <para>
-///   Use <see cref="View.HotKeySpecifier"/> to change the hot key specifier from the default of ('_'). 
-/// </para>
-/// <para>
-///   If no hot key specifier is found, the first uppercase letter encountered will be used as the hot key.
-/// </para>
-/// <para>
-///   When the button is configured as the default (<see cref="IsDefault"/>) and the user presses
-///   the ENTER key, if no other <see cref="View"/> processes the key, the <see cref="Button"/>'s
-///   <see cref="Clicked"/> event will will be fired.
-/// </para>
+///         <para>
+///         Provides a button showing text that raises the <see cref="Clicked"/> event when clicked on with a mouse
+///         or when the user presses SPACE, ENTER, or the <see cref="View.HotKey"/>. The hot key is the first letter or
+///         digit following the first underscore ('_')
+///         in the button text.
+///         </para>
+///         <para>
+///         Use <see cref="View.HotKeySpecifier"/> to change the hot key specifier from the default of ('_').
+///         </para>
+///         <para>
+///         When the button is configured as the default (<see cref="IsDefault"/>) and the user presses
+///         the ENTER key, if no other <see cref="View"/> processes the key, the <see cref="Button"/>'s
+///         <see cref="Clicked"/> event will will be fired.
+///         </para>
 /// </remarks>
 public class Button : View {
 	bool _isDefault;
 	Rune _leftBracket;
-	Rune _rightBracket;
 	Rune _leftDefault;
+	Rune _rightBracket;
 	Rune _rightDefault;
 
 	/// <summary>
-	///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
+	/// Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
 	/// </summary>
 	/// <remarks>
-	///   The width of the <see cref="Button"/> is computed based on the
-	///   text length. The height will always be 1.
+	/// The width of the <see cref="Button"/> is computed based on the
+	/// text length. The height will always be 1.
 	/// </remarks>
-	public Button () : this (text: string.Empty, is_default: false) { }
+	public Button () : this (string.Empty, false) { }
 
 	/// <summary>
-	///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
+	/// Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
 	/// </summary>
 	/// <remarks>
-	///   The width of the <see cref="Button"/> is computed based on the
-	///   text length. The height will always be 1.
+	/// The width of the <see cref="Button"/> is computed based on the
+	/// text length. The height will always be 1.
 	/// </remarks>
 	/// <param name="text">The button's text</param>
 	/// <param name="is_default">
-	///   If <c>true</c>, a special decoration is used, and the user pressing the enter key 
-	///   in a <see cref="Dialog"/> will implicitly activate this button.
+	/// If <c>true</c>, a special decoration is used, and the user pressing the enter key
+	/// in a <see cref="Dialog"/> will implicitly activate this button.
 	/// </param>
-	public Button (string text, bool is_default = false) : base (text)
-	{
-		SetInitialProperties (text, is_default);
-	}
+	public Button (string text, bool is_default = false) : base (text) => SetInitialProperties (text, is_default);
 
 	/// <summary>
-	///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Absolute"/> layout, based on the given text
+	/// Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Absolute"/> layout, based on the given
+	/// text
 	/// </summary>
 	/// <remarks>
-	///   The width of the <see cref="Button"/> is computed based on the
-	///   text length. The height will always be 1.
+	/// The width of the <see cref="Button"/> is computed based on the
+	/// text length. The height will always be 1.
 	/// </remarks>
 	/// <param name="x">X position where the button will be shown.</param>
 	/// <param name="y">Y position where the button will be shown.</param>
@@ -76,25 +72,46 @@ public class Button : View {
 	public Button (int x, int y, string text) : this (x, y, text, false) { }
 
 	/// <summary>
-	///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Absolute"/> layout, based on the given text.
+	/// Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Absolute"/> layout, based on the given
+	/// text.
 	/// </summary>
 	/// <remarks>
-	///   The width of the <see cref="Button"/> is computed based on the
-	///   text length. The height will always be 1.
+	/// The width of the <see cref="Button"/> is computed based on the
+	/// text length. The height will always be 1.
 	/// </remarks>
 	/// <param name="x">X position where the button will be shown.</param>
 	/// <param name="y">Y position where the button will be shown.</param>
 	/// <param name="text">The button's text</param>
 	/// <param name="is_default">
-	///   If <c>true</c>, a special decoration is used, and the user pressing the enter key 
-	///   in a <see cref="Dialog"/> will implicitly activate this button.
+	/// If <c>true</c>, a special decoration is used, and the user pressing the enter key
+	/// in a <see cref="Dialog"/> will implicitly activate this button.
 	/// </param>
 	public Button (int x, int y, string text, bool is_default)
-	    : base (new Rect (x, y, text.GetRuneCount () + 4 + (is_default ? 2 : 0), 1), text)
-	{
-		SetInitialProperties (text, is_default);
+		: base (new Rect (x, y, text.GetRuneCount () + 4 + (is_default ? 2 : 0), 1), text) => SetInitialProperties (text, is_default);
+
+	/// <summary>
+	/// Gets or sets whether the <see cref="Button"/> is the default action to activate in a dialog.
+	/// </summary>
+	/// <value><c>true</c> if is default; otherwise, <c>false</c>.</value>
+	public bool IsDefault {
+		get => _isDefault;
+		set {
+			_isDefault = value;
+			UpdateTextFormatterText ();
+			OnResizeNeeded ();
+		}
 	}
 
+	/// <summary>
+	/// 
+	/// </summary>
+	public bool NoDecorations { get; set; }
+
+	/// <summary>
+	/// 
+	/// </summary>
+	public bool NoPadding { get; set; }
+
 	// TODO: v2 - Remove constructors with parameters
 	/// <summary>
 	/// Private helper to set the initial properties of the View that were provided via constructors.
@@ -108,56 +125,34 @@ public class Button : View {
 
 		HotKeySpecifier = new Rune ('_');
 
-		_leftBracket = CM.Glyphs.LeftBracket;
-		_rightBracket = CM.Glyphs.RightBracket;
-		_leftDefault = CM.Glyphs.LeftDefaultIndicator;
-		_rightDefault = CM.Glyphs.RightDefaultIndicator;
+		_leftBracket = Glyphs.LeftBracket;
+		_rightBracket = Glyphs.RightBracket;
+		_leftDefault = Glyphs.LeftDefaultIndicator;
+		_rightDefault = Glyphs.RightDefaultIndicator;
 
 		CanFocus = true;
 		AutoSize = true;
 		_isDefault = is_default;
 		Text = text ?? string.Empty;
 
-		OnResizeNeeded ();
-
-		// Override default behavior of View - Command.Default sets focus
-		AddCommand (Command.Accept, () => { OnClicked (); return true; });
+		// Override default behavior of View
+		// Command.Default sets focus
+		AddCommand (Command.Accept, () => {
+			OnClicked ();
+			return true;
+		});
 		KeyBindings.Add (Key.Space, Command.Default, Command.Accept);
 		KeyBindings.Add (Key.Enter, Command.Default, Command.Accept);
 	}
-	
-	/// <summary>
-	/// Gets or sets whether the <see cref="Button"/> is the default action to activate in a dialog.
-	/// </summary>
-	/// <value><c>true</c> if is default; otherwise, <c>false</c>.</value>
-	public bool IsDefault {
-		get => _isDefault;
-		set {
-			_isDefault = value;
-			UpdateTextFormatterText ();
-			OnResizeNeeded ();
-		}
-	}
-
-	/// <summary>
-	/// 
-	/// </summary>
-	public bool NoDecorations { get; set; }
-
-	/// <summary>
-	/// 
-	/// </summary>
-	public bool NoPadding { get; set; }
 
 	/// <inheritdoc/>
 	protected override void UpdateTextFormatterText ()
 	{
 		if (NoDecorations) {
 			TextFormatter.Text = Text;
-		} else
-		if (IsDefault)
+		} else if (IsDefault) {
 			TextFormatter.Text = $"{_leftBracket}{_leftDefault} {Text} {_rightDefault}{_rightBracket}";
-		else {
+		} else {
 			if (NoPadding) {
 				TextFormatter.Text = $"{_leftBracket}{Text}{_rightBracket}";
 			} else {
@@ -170,19 +165,16 @@ public class Button : View {
 	/// <summary>
 	/// Virtual method to invoke the <see cref="Clicked"/> event.
 	/// </summary>
-	public virtual void OnClicked ()
-	{
-		Clicked?.Invoke (this, EventArgs.Empty);
-	}
+	public virtual void OnClicked () => Clicked?.Invoke (this, EventArgs.Empty);
 
 	/// <summary>
-	///   The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
-	///   or if the user presses the action key while this view is focused. (TODO: IsDefault)
+	/// The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
+	/// or if the user presses the action key while this view is focused. (TODO: IsDefault)
 	/// </summary>
 	/// <remarks>
-	///   Client code can hook up to this event, it is
-	///   raised when the button is activated either with
-	///   the mouse or the keyboard.
+	/// Client code can hook up to this event, it is
+	/// raised when the button is activated either with
+	/// the mouse or the keyboard.
 	/// </remarks>
 	public event EventHandler Clicked;
 
@@ -208,7 +200,7 @@ public class Button : View {
 	public override void PositionCursor ()
 	{
 		if (HotKey.IsValid && Text != "") {
-			for (int i = 0; i < TextFormatter.Text.GetRuneCount (); i++) {
+			for (var i = 0; i < TextFormatter.Text.GetRuneCount (); i++) {
 				if (TextFormatter.Text [i] == Text [0]) {
 					Move (i, 0);
 					return;
@@ -225,4 +217,4 @@ public class Button : View {
 
 		return base.OnEnter (view);
 	}
-}
+}

+ 0 - 2
Terminal.Gui/Views/CheckBox.cs

@@ -86,8 +86,6 @@ public class CheckBox : View {
 		AutoSize = true;
 		Text = s;
 
-		OnResizeNeeded ();
-
 		// Things this view knows how to do
 		AddCommand (Command.ToggleChecked, () => ToggleChecked ());
 		AddCommand (Command.Accept, () => {

+ 227 - 233
Terminal.Gui/Views/ColorPicker.cs

@@ -1,281 +1,275 @@
 using System;
 using System.Text;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
 
+/// <summary>
+/// Event arguments for the <see cref="Color"/> events.
+/// </summary>
+public class ColorEventArgs : EventArgs {
 	/// <summary>
-	/// Event arguments for the <see cref="Color"/> events.
+	/// Initializes a new instance of <see cref="ColorEventArgs"/>
 	/// </summary>
-	public class ColorEventArgs : EventArgs {
+	public ColorEventArgs () { }
 
-		/// <summary>
-		/// Initializes a new instance of <see cref="ColorEventArgs"/>
-		/// </summary>
-		public ColorEventArgs ()
-		{
-		}
+	/// <summary>
+	/// The new Thickness.
+	/// </summary>
+	public Color Color { get; set; }
+
+	/// <summary>
+	/// The previous Thickness.
+	/// </summary>
+	public Color PreviousColor { get; set; }
+}
 
-		/// <summary>
-		/// The new Thickness.
-		/// </summary>
-		public Color Color { get; set; }
+/// <summary>
+/// The <see cref="ColorPicker"/> <see cref="View"/> Color picker.
+/// </summary>
+public class ColorPicker : View {
+	int _boxHeight = 2;
+	int _boxWidth = 4;
 
-		/// <summary>
-		/// The previous Thickness.
-		/// </summary>
-		public Color PreviousColor { get; set; }
-	}
 
 	/// <summary>
-	/// The <see cref="ColorPicker"/> <see cref="View"/> Color picker.
+	/// Columns of color boxes
 	/// </summary>
-	public class ColorPicker : View {
-		private int _selectColorIndex = (int)Color.Black;
-
-
-		/// <summary>
-		/// Columns of color boxes
-		/// </summary>
-		private int _cols = 8;
-
-		/// <summary>
-		/// Rows of color boxes
-		/// </summary>
-		private int _rows = 2;
-
-		/// <summary>
-		/// Width of a color box
-		/// </summary>
-		public int BoxWidth {
-			get => _boxWidth;
-			set {
-				if (_boxWidth != value) {
-					_boxWidth = value;
-					if (IsInitialized) {
-						Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight));
-					}
-				}
-			}
-		}
-		private int _boxWidth = 4;
-
-		/// <summary>
-		/// Height of a color box
-		/// </summary>
-		public int BoxHeight {
-			get => _boxHeight;
-			set {
-				if (_boxHeight != value) {
-					_boxHeight = value;
-					if (IsInitialized) {
-						Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight));
-					}
-				}
-			}
-		}
-		int _boxHeight = 2;
-
-		/// <summary>
-		/// Cursor for the selected color.
-		/// </summary>
-		public Point Cursor {
-			get {
-				return new Point (_selectColorIndex % _cols, _selectColorIndex / _cols);
-			}
+	readonly int _cols = 8;
 
-			set {
-				var colorIndex = value.Y * _cols + value.X;
-				SelectedColor = (ColorName)colorIndex;
-			}
-		}
+	/// <summary>
+	/// Rows of color boxes
+	/// </summary>
+	readonly int _rows = 2;
 
-		/// <summary>
-		/// Fired when a color is picked.
-		/// </summary>
-		public event EventHandler<ColorEventArgs> ColorChanged;
-
-		/// <summary>
-		/// Selected color.
-		/// </summary>
-		public ColorName SelectedColor {
-			get {
-				return (ColorName)_selectColorIndex;
-			}
+	int _selectColorIndex = (int)Color.Black;
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="ColorPicker"/>.
+	/// </summary>
+	public ColorPicker () => SetInitialProperties ();
 
-			set {
-				ColorName prev = (ColorName)_selectColorIndex;
-				_selectColorIndex = (int)value;
-				ColorChanged?.Invoke (this, new ColorEventArgs () {
-					PreviousColor = new Color (prev),
-					Color = new Color (value),
-				});
-				SetNeedsDisplay ();
+	/// <summary>
+	/// Width of a color box
+	/// </summary>
+	public int BoxWidth {
+		get => _boxWidth;
+		set {
+			if (_boxWidth != value) {
+				_boxWidth = value;
+				SetNeedsLayout ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Initializes a new instance of <see cref="ColorPicker"/>.
-		/// </summary>
-		public ColorPicker ()
-		{
-			SetInitialProperties ();
+	/// <summary>
+	/// Height of a color box
+	/// </summary>
+	public int BoxHeight {
+		get => _boxHeight;
+		set {
+			if (_boxHeight != value) {
+				_boxHeight = value;
+				SetNeedsLayout ();
+			}
 		}
+	}
 
-		private void SetInitialProperties ()
-		{
-			CanFocus = true;
-			AddCommands ();
-			AddKeyBindings ();
-			LayoutStarted += (o, a) => {
-				Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight));
-			};
+	/// <summary>
+	/// Cursor for the selected color.
+	/// </summary>
+	public Point Cursor {
+		get => new (_selectColorIndex % _cols, _selectColorIndex / _cols);
+		set {
+			var colorIndex = value.Y * _cols + value.X;
+			SelectedColor = (ColorName)colorIndex;
 		}
+	}
 
-		/// <summary>
-		/// Add the commands.
-		/// </summary>
-		private void AddCommands ()
-		{
-			AddCommand (Command.Left, () => MoveLeft ());
-			AddCommand (Command.Right, () => MoveRight ());
-			AddCommand (Command.LineUp, () => MoveUp ());
-			AddCommand (Command.LineDown, () => MoveDown ());
+	/// <summary>
+	/// Selected color.
+	/// </summary>
+	public ColorName SelectedColor {
+		get => (ColorName)_selectColorIndex;
+		set {
+			var prev = (ColorName)_selectColorIndex;
+			_selectColorIndex = (int)value;
+			ColorChanged?.Invoke (this, new ColorEventArgs {
+				PreviousColor = new Color (prev),
+				Color = new Color (value)
+			});
+			SetNeedsDisplay ();
 		}
+	}
 
-		/// <summary>
-		/// Add the KeyBindinds.
-		/// </summary>
-		private void AddKeyBindings ()
-		{
-			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
-			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
-			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
-			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
-		}
+	/// <summary>
+	/// Fired when a color is picked.
+	/// </summary>
+	public event EventHandler<ColorEventArgs> ColorChanged;
+
+	void SetInitialProperties ()
+	{
+		CanFocus = true;
+		AddCommands ();
+		AddKeyBindings ();
+		LayoutStarted += (o, a) => {
+			var thickness = GetAdornmentsThickness ();
+			Width = _cols * BoxWidth + thickness.Vertical;
+			Height = _rows * BoxHeight + thickness.Horizontal;
+		};
+	}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			base.OnDrawContent (contentArea);
-
-			Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
-			var colorIndex = 0;
-
-			for (var y = 0; y < (Bounds.Height / BoxHeight); y++) {
-				for (var x = 0; x < (Bounds.Width / BoxWidth); x++) {
-					var foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
-					Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
-					var selected = x == Cursor.X && y == Cursor.Y;
-					DrawColorBox (x, y, selected);
-					colorIndex++;
-				}
-			}
-		}
+	/// <summary>
+	/// Add the commands.
+	/// </summary>
+	void AddCommands ()
+	{
+		AddCommand (Command.Left, () => MoveLeft ());
+		AddCommand (Command.Right, () => MoveRight ());
+		AddCommand (Command.LineUp, () => MoveUp ());
+		AddCommand (Command.LineDown, () => MoveDown ());
+	}
 
-		/// <summary>
-		/// Draw a box for one color.
-		/// </summary>
-		/// <param name="x">X location.</param>
-		/// <param name="y">Y location</param>
-		/// <param name="selected"></param>
-		private void DrawColorBox (int x, int y, bool selected)
-		{
-			var index = 0;
-
-			for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++) {
-				for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++) {
-					Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY);
-					Driver.AddRune ((Rune)' ');
-					index++;
-				}
-			}
+	/// <summary>
+	/// Add the KeyBindinds.
+	/// </summary>
+	void AddKeyBindings ()
+	{
+		KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+		KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+		KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+		KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+	}
 
-			if (selected) {
-				DrawFocusRect (new Rect (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight));
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		base.OnDrawContent (contentArea);
+
+		Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
+		var colorIndex = 0;
+
+		for (var y = 0; y < Bounds.Height / BoxHeight; y++) {
+			for (var x = 0; x < Bounds.Width / BoxWidth; x++) {
+				var foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
+				Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
+				var selected = x == Cursor.X && y == Cursor.Y;
+				DrawColorBox (x, y, selected);
+				colorIndex++;
 			}
 		}
+	}
 
-		private void DrawFocusRect (Rect rect)
-		{
-			var lc = new LineCanvas ();
-			if (rect.Width == 1) {
-				lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
-			} else if (rect.Height == 1) {
-				lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
-			} else {
-				lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
-				lc.AddLine (new Point (rect.Location.X, rect.Location.Y + rect.Height - 1), rect.Width, Orientation.Horizontal, LineStyle.Dotted);
-
-				lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
-				lc.AddLine (new Point (rect.Location.X + rect.Width - 1, rect.Location.Y), rect.Height, Orientation.Vertical, LineStyle.Dotted);
-			}
-			foreach (var p in lc.GetMap ()) {
-				AddRune (p.Key.X, p.Key.Y, p.Value);
+	/// <summary>
+	/// Draw a box for one color.
+	/// </summary>
+	/// <param name="x">X location.</param>
+	/// <param name="y">Y location</param>
+	/// <param name="selected"></param>
+	void DrawColorBox (int x, int y, bool selected)
+	{
+		var index = 0;
+
+		for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++) {
+			for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++) {
+				Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY);
+				Driver.AddRune ((Rune)' ');
+				index++;
 			}
 		}
 
-		/// <summary>
-		/// Moves the selected item index to the previous column.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool MoveLeft ()
-		{
-			if (Cursor.X > 0) SelectedColor--;
-			return true;
+		if (selected) {
+			DrawFocusRect (new Rect (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight));
 		}
+	}
 
-		/// <summary>
-		/// Moves the selected item index to the next column.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool MoveRight ()
-		{
-			if (Cursor.X < _cols - 1) SelectedColor++;
-			return true;
+	void DrawFocusRect (Rect rect)
+	{
+		var lc = new LineCanvas ();
+		if (rect.Width == 1) {
+			lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
+		} else if (rect.Height == 1) {
+			lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+		} else {
+			lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+			lc.AddLine (new Point (rect.Location.X, rect.Location.Y + rect.Height - 1), rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+
+			lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
+			lc.AddLine (new Point (rect.Location.X + rect.Width - 1, rect.Location.Y), rect.Height, Orientation.Vertical, LineStyle.Dotted);
+		}
+		foreach (var p in lc.GetMap ()) {
+			AddRune (p.Key.X, p.Key.Y, p.Value);
 		}
+	}
 
-		/// <summary>
-		/// Moves the selected item index to the previous row.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool MoveUp ()
-		{
-			if (Cursor.Y > 0) SelectedColor -= _cols;
-			return true;
+	/// <summary>
+	/// Moves the selected item index to the previous column.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool MoveLeft ()
+	{
+		if (Cursor.X > 0) {
+			SelectedColor--;
+		}
+		return true;
+	}
+
+	/// <summary>
+	/// Moves the selected item index to the next column.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool MoveRight ()
+	{
+		if (Cursor.X < _cols - 1) {
+			SelectedColor++;
 		}
+		return true;
+	}
 
-		/// <summary>
-		/// Moves the selected item index to the next row.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool MoveDown ()
-		{
-			if (Cursor.Y < _rows - 1) SelectedColor += _cols;
-			return true;
+	/// <summary>
+	/// Moves the selected item index to the previous row.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool MoveUp ()
+	{
+		if (Cursor.Y > 0) {
+			SelectedColor -= _cols;
 		}
+		return true;
+	}
 
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus) {
-				return false;
-			}
+	/// <summary>
+	/// Moves the selected item index to the next row.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool MoveDown ()
+	{
+		if (Cursor.Y < _rows - 1) {
+			SelectedColor += _cols;
+		}
+		return true;
+	}
 
-			SetFocus ();
-			if (me.X > Bounds.Width || me.Y > Bounds.Height) {
-				return true;
-			}
-			Cursor = new Point ((me.X ) / _boxWidth, (me.Y) / _boxHeight);
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus) {
+			return false;
+		}
 
+		SetFocus ();
+		if (me.X > Bounds.Width || me.Y > Bounds.Height) {
 			return true;
 		}
+		Cursor = new Point (me.X / _boxWidth, me.Y / _boxHeight);
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+		return true;
+	}
 
-			return base.OnEnter (view);
-		}
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+
+		return base.OnEnter (view);
 	}
-}
+}

+ 6 - 4
Terminal.Gui/Views/ComboBox.cs

@@ -241,7 +241,7 @@ public class ComboBox : View {
 	public ComboBox (string text) : base ()
 	{
 		_search = new TextField ("");
-		_listview = new ComboListView (this, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false };
+		_listview = new ComboListView (this, HideDropdownListOnClick) { CanFocus = true, TabStop = false };
 
 		SetInitialProperties ();
 		Text = text;
@@ -255,7 +255,7 @@ public class ComboBox : View {
 	public ComboBox (Rect rect, IList source) : base (rect)
 	{
 		_search = new TextField ("") { Width = rect.Width };
-		_listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base };
+		_listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { ColorScheme = Colors.ColorSchemes ["Base"] };
 
 		SetInitialProperties ();
 		SetSource (source);
@@ -268,7 +268,7 @@ public class ComboBox : View {
 	public ComboBox (IList source) : this (string.Empty)
 	{
 		_search = new TextField ("");
-		_listview = new ComboListView (this, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base };
+		_listview = new ComboListView (this, source, HideDropdownListOnClick) { ColorScheme = Colors.ColorSchemes ["Base"] };
 
 		SetInitialProperties ();
 		SetSource (source);
@@ -396,7 +396,9 @@ public class ComboBox : View {
 			_search.ReadOnly = value;
 			if (_search.ReadOnly) {
 				if (_search.ColorScheme != null) {
-					_search.ColorScheme.Normal = _search.ColorScheme.Focus;
+					_search.ColorScheme = new ColorScheme (_search.ColorScheme) {
+						Normal = _search.ColorScheme.Focus
+					};
 				}
 			}
 		}

+ 56 - 145
Terminal.Gui/Views/DateField.cs

@@ -20,16 +20,9 @@ namespace Terminal.Gui;
 /// </remarks>
 public class DateField : TextField {
 	DateTime _date;
-	bool _isShort;
-	int _longFieldLen = 10;
-	int _shortFieldLen = 8;
+	int _fieldLen = 10;
 	string _sepChar;
-	string _longFormat;
-	string _shortFormat;
-
-	int _fieldLen => _isShort ? _shortFieldLen : _longFieldLen;
-
-	string _format => _isShort ? _shortFormat : _longFormat;
+	string _format;
 
 	/// <summary>
 	///   DateChanged event, raised when the <see cref="Date"/> property has changed.
@@ -42,15 +35,6 @@ public class DateField : TextField {
 	/// </remarks>
 	public event EventHandler<DateTimeEventArgs<DateTime>> DateChanged;
 
-	/// <summary>
-	///    Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Absolute"/> layout.
-	/// </summary>
-	/// <param name="x">The x coordinate.</param>
-	/// <param name="y">The y coordinate.</param>
-	/// <param name="date">Initial date contents.</param>
-	/// <param name="isShort">If true, shows only two digits for the year.</param>
-	public DateField (int x, int y, DateTime date, bool isShort = false) : base (x, y, isShort ? 10 : 12, "") => SetInitialProperties (date, isShort);
-
 	/// <summary>
 	///  Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Computed"/> layout.
 	/// </summary>
@@ -66,16 +50,14 @@ public class DateField : TextField {
 		SetInitialProperties (date);
 	}
 
-	void SetInitialProperties (DateTime date, bool isShort = false)
+	void SetInitialProperties (DateTime date)
 	{
 		var cultureInfo = CultureInfo.CurrentCulture;
 		_sepChar = cultureInfo.DateTimeFormat.DateSeparator;
-		_longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern);
-		_shortFormat = GetShortFormat (_longFormat);
-		this._isShort = isShort;
+		_format = $" {cultureInfo.DateTimeFormat.ShortDatePattern}";
 		Date = date;
 		CursorPosition = 1;
-		TextChanged += DateField_Changed;
+		TextChanging += DateField_Changing;
 
 		// Things this view knows how to do
 		AddCommand (Command.DeleteCharRight, () => {
@@ -109,7 +91,6 @@ public class DateField : TextField {
 
 		KeyBindings.Add (Key.CursorRight, Command.Right);
 		KeyBindings.Add (Key.F.WithCtrl, Command.Right);
-
 	}
 
 	/// <inheritdoc />
@@ -127,45 +108,33 @@ public class DateField : TextField {
 		return false;
 	}
 
-	void DateField_Changed (object sender, TextChangedEventArgs e)
+	void DateField_Changing (object sender, TextChangingEventArgs e)
 	{
 		try {
-			var date = GetInvarianteDate (Text, _isShort);
-			if ($" {date}" != Text) {
-				Text = $" {date}";
-			}
-			if (_isShort) {
-				date = GetInvarianteDate (Text, false);
+			var cultureInfo = CultureInfo.CurrentCulture;
+			DateTimeFormatInfo ccFmt = cultureInfo.DateTimeFormat;
+			int spaces = 0;
+			for (int i = 0; i < e.NewText.Length; i++) {
+				if (e.NewText [i] == ' ') {
+					spaces++;
+				} else {
+					break;
+				}
 			}
-			if (!DateTime.TryParseExact (date, GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out var result)) {
-				Text = e.OldValue;
+			spaces += _fieldLen;
+			string trimedText = e.NewText [..spaces];
+			spaces -= _fieldLen;
+			trimedText = trimedText.Replace (new string (' ', spaces), " ");
+			var date = Convert.ToDateTime (trimedText, ccFmt).ToString (ccFmt.ShortDatePattern);
+			if ($" {date}" != e.NewText) {
+				e.NewText = $" {date}";
 			}
+			AdjCursorPosition (CursorPosition, true);
 		} catch (Exception) {
-			Text = e.OldValue;
+			e.Cancel = true;
 		}
 	}
 
-	string GetInvarianteFormat () => $"MM{_sepChar}dd{_sepChar}yyyy";
-
-	string GetLongFormat (string lf)
-	{
-		string [] frm = lf.Split (_sepChar);
-		for (int i = 0; i < frm.Length; i++) {
-			if (frm [i].Contains ("M") && frm [i].GetRuneCount () < 2) {
-				lf = lf.Replace ("M", "MM");
-			}
-			if (frm [i].Contains ("d") && frm [i].GetRuneCount () < 2) {
-				lf = lf.Replace ("d", "dd");
-			}
-			if (frm [i].Contains ("y") && frm [i].GetRuneCount () < 4) {
-				lf = lf.Replace ("yy", "yyyy");
-			}
-		}
-		return $" {lf}";
-	}
-
-	string GetShortFormat (string lf) => lf.Replace ("yyyy", "yy");
-
 	/// <summary>
 	///   Gets or sets the date of the <see cref="DateField"/>.
 	/// </summary>
@@ -188,28 +157,6 @@ public class DateField : TextField {
 		}
 	}
 
-	/// <summary>
-	/// Get or set the date format for the widget.
-	/// </summary>
-	public bool IsShortFormat {
-		get => _isShort;
-		set {
-			_isShort = value;
-			if (_isShort) {
-				Width = 10;
-			} else {
-				Width = 12;
-			}
-			bool ro = ReadOnly;
-			if (ro) {
-				ReadOnly = false;
-			}
-			SetText (Text);
-			ReadOnly = ro;
-			SetNeedsDisplay ();
-		}
-	}
-
 	/// <inheritdoc/>
 	public override int CursorPosition {
 		get => base.CursorPosition;
@@ -230,7 +177,7 @@ public class DateField : TextField {
 		var newText = text.GetRange (0, CursorPosition);
 		newText.Add (key);
 		if (CursorPosition < _fieldLen) {
-			newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
+			newText = [.. newText, .. text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))];
 		}
 		return SetText (StringExtensions.ToString (newText));
 	}
@@ -310,18 +257,12 @@ public class DateField : TextField {
 	{
 		string date = " ";
 		for (int i = 0; i < fm.Length; i++) {
-			if (fm [i].Contains ("M")) {
+			if (fm [i].Contains ('M')) {
 				date += $"{month,2:00}";
-			} else if (fm [i].Contains ("d")) {
+			} else if (fm [i].Contains ('d')) {
 				date += $"{day,2:00}";
 			} else {
-				if (_isShort && year.ToString ().Length == 4) {
-					date += $"{year.ToString ().Substring (2, 2)}";
-				} else if (_isShort) {
-					date += $"{year,2:00}";
-				} else {
-					date += $"{year,4:0000}";
-				}
+				date += $"{year,4:0000}";
 			}
 			if (i < 2) {
 				date += $"{_sepChar}";
@@ -330,40 +271,7 @@ public class DateField : TextField {
 		return date;
 	}
 
-	string GetInvarianteDate (string text, bool isShort)
-	{
-		string [] vals = text.Split (_sepChar);
-		string [] frm = (isShort ? $"MM{_sepChar}dd{_sepChar}yy" : GetInvarianteFormat ()).Split (_sepChar);
-		string [] date = { null, null, null };
-
-		for (int i = 0; i < frm.Length; i++) {
-			if (frm [i].Contains ("M")) {
-				date [0] = vals [i].Trim ();
-			} else if (frm [i].Contains ("d")) {
-				date [1] = vals [i].Trim ();
-			} else {
-				string yearString;
-				if (isShort && vals [i].Length > 2) {
-					yearString = vals [i].Substring (0, 2);
-				} else if (!isShort && vals [i].Length > 4) {
-					yearString = vals [i].Substring (0, 4);
-				} else {
-					yearString = vals [i].Trim ();
-				}
-				var year = int.Parse (yearString);
-				if (isShort && year.ToString ().Length == 4) {
-					date [2] = year.ToString ().Substring (2, 2);
-				} else if (isShort) {
-					date [2] = year.ToString ();
-				} else {
-					date [2] = $"{year,4:0000}";
-				}
-			}
-		}
-		return $"{date [0]}{_sepChar}{date [1]}{_sepChar}{date [2]}";
-	}
-
-	int GetFormatIndex (string [] fm, string t)
+	static int GetFormatIndex (string [] fm, string t)
 	{
 		int idx = -1;
 		for (int i = 0; i < fm.Length; i++) {
@@ -381,9 +289,8 @@ public class DateField : TextField {
 			CursorPosition = _fieldLen;
 			return;
 		}
-		if (Text [++CursorPosition] == _sepChar.ToCharArray () [0]) {
-			CursorPosition++;
-		}
+		CursorPosition++;
+		AdjCursorPosition (CursorPosition);
 	}
 
 	void DecCursorPosition ()
@@ -392,15 +299,29 @@ public class DateField : TextField {
 			CursorPosition = 1;
 			return;
 		}
-		if (Text [--CursorPosition] == _sepChar.ToCharArray () [0]) {
-			CursorPosition--;
-		}
+		CursorPosition--;
+		AdjCursorPosition (CursorPosition, false);
 	}
 
-	void AdjCursorPosition ()
+	void AdjCursorPosition (int point, bool increment = true)
 	{
-		if (Text [CursorPosition] == _sepChar.ToCharArray () [0]) {
-			CursorPosition++;
+		var newPoint = point;
+		if (point > _fieldLen) {
+			newPoint = _fieldLen;
+		}
+		if (point < 1) {
+			newPoint = 1;
+		}
+		if (newPoint != point) {
+			CursorPosition = newPoint;
+		}
+
+		while (Text [CursorPosition] == _sepChar [0]) {
+			if (increment) {
+				CursorPosition++;
+			} else {
+				CursorPosition--;
+			}
 		}
 	}
 
@@ -461,23 +382,13 @@ public class DateField : TextField {
 	/// <inheritdoc/>
 	public override bool MouseEvent (MouseEvent ev)
 	{
-		if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked)) {
-			return false;
-		}
-		if (!HasFocus) {
-			SetFocus ();
-		}
+		var result = base.MouseEvent (ev);
 
-		int point = ev.X;
-		if (point > _fieldLen) {
-			point = _fieldLen;
+		if (result && SelectedLength == 0 && ev.Flags.HasFlag (MouseFlags.Button1Pressed)) {
+			int point = ev.X;
+			AdjCursorPosition (point, true);
 		}
-		if (point < 1) {
-			point = 1;
-		}
-		CursorPosition = point;
-		AdjCursorPosition ();
-		return true;
+		return result;
 	}
 
 	/// <summary>

+ 240 - 0
Terminal.Gui/Views/DatePicker.cs

@@ -0,0 +1,240 @@
+//
+// DatePicker.cs: DatePicker control
+//
+// Author: Maciej Winnik
+//
+using System;
+using System.Data;
+using System.Globalization;
+using System.Linq;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// The <see cref="DatePicker"/> <see cref="View"/> Date Picker.
+/// </summary>
+public class DatePicker : View {
+
+	private DateField _dateField;
+	private Label _dateLabel;
+	private TableView _calendar;
+	private DataTable _table;
+	private Button _nextMonthButton;
+	private Button _previousMonthButton;
+
+	private DateTime _date = DateTime.Now;
+
+	/// <summary>
+	/// Format of date. The default is MM/dd/yyyy.
+	/// </summary>
+	public string Format { get; set; } = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
+
+	/// <summary>
+	/// Get or set the date.
+	/// </summary>
+	public DateTime Date {
+		get => _date;
+		set {
+			_date = value;
+			Text = _date.ToString (Format);
+		}
+	}
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="DatePicker"/>.
+	/// </summary>
+	public DatePicker () => SetInitialProperties (_date);
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="DatePicker"/> with the specified date.
+	/// </summary>
+	public DatePicker (DateTime date)
+	{
+		SetInitialProperties (date);
+	}
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="DatePicker"/> with the specified date and format.
+	/// </summary>
+	public DatePicker (DateTime date, string format)
+	{
+		Format = format;
+		SetInitialProperties (date);
+	}
+
+	private void SetInitialProperties (DateTime date)
+	{
+		Title = "Date Picker";
+		BorderStyle = LineStyle.Single;
+		Date = date;
+		_dateLabel = new Label ("Date: ") {
+			X = 0,
+			Y = 0,
+			Height = 1,
+		};
+
+		_dateField = new DateField (DateTime.Now) {
+			X = Pos.Right (_dateLabel),
+			Y = 0,
+			Width = Dim.Fill (1),
+			Height = 1
+		};
+
+		_calendar = new TableView () {
+			X = 0,
+			Y = Pos.Bottom (_dateLabel),
+			Height = 11,
+			Style = new TableStyle {
+				ShowHeaders = true,
+				ShowHorizontalBottomline = true,
+				ShowVerticalCellLines = true,
+				ExpandLastColumn = true,
+			}
+		};
+
+		_previousMonthButton = new Button (GetBackButtonText ()) {
+			X = Pos.Center () - 4,
+			Y = Pos.Bottom (_calendar) - 1,
+			Height = 1,
+			Width = CalculateCalendarWidth () / 2
+		};
+
+		_previousMonthButton.Clicked += (sender, e) => {
+			Date = _date.AddMonths (-1);
+			CreateCalendar ();
+			_dateField.Date = Date;
+		};
+
+		_nextMonthButton = new Button (GetForwardButtonText ()) {
+			X = Pos.Right (_previousMonthButton) + 2,
+			Y = Pos.Bottom (_calendar) - 1,
+			Height = 1,
+			Width = CalculateCalendarWidth () / 2
+		};
+
+		_nextMonthButton.Clicked += (sender, e) => {
+			Date = _date.AddMonths (1);
+			CreateCalendar ();
+			_dateField.Date = Date;
+		};
+
+		CreateCalendar ();
+		SelectDayOnCalendar (_date.Day);
+
+		_calendar.CellActivated += (sender, e) => {
+			var dayValue = _table.Rows [e.Row] [e.Col];
+			if (dayValue is null) {
+				return;
+			}
+			bool isDay = int.TryParse (dayValue.ToString (), out int day);
+			if (!isDay) {
+				return;
+			}
+			ChangeDayDate (day);
+			SelectDayOnCalendar (day);
+			Text = _date.ToString (Format);
+
+		};
+
+		Width = CalculateCalendarWidth () + 2;
+		Height = _calendar.Height + 3;
+
+		_dateField.DateChanged += DateField_DateChanged;
+
+		Add (_dateLabel, _dateField, _calendar, _previousMonthButton, _nextMonthButton);
+	}
+
+	private void DateField_DateChanged (object sender, DateTimeEventArgs<DateTime> e)
+	{
+		if (e.NewValue.Date.Day != _date.Day) {
+			SelectDayOnCalendar (e.NewValue.Day);
+		}
+		Date = e.NewValue;
+		CreateCalendar ();
+		SelectDayOnCalendar (_date.Day);
+	}
+
+	private void CreateCalendar ()
+	{
+		_calendar.Table = new DataTableSource (_table = CreateDataTable (_date.Month, _date.Year));
+	}
+
+	private void ChangeDayDate (int day)
+	{
+		_date = new DateTime (_date.Year, _date.Month, day);
+		_dateField.Date = _date;
+		CreateCalendar ();
+	}
+
+	private DataTable CreateDataTable (int month, int year)
+	{
+		_table = new DataTable ();
+		GenerateCalendarLabels ();
+		int amountOfDaysInMonth = DateTime.DaysInMonth (year, month);
+		DateTime dateValue = new DateTime (year, month, 1);
+		var dayOfWeek = dateValue.DayOfWeek;
+
+		_table.Rows.Add (new object [6]);
+		for (int i = 1; i <= amountOfDaysInMonth; i++) {
+			_table.Rows [^1] [(int)dayOfWeek] = i;
+			if (dayOfWeek == DayOfWeek.Saturday && i != amountOfDaysInMonth) {
+				_table.Rows.Add (new object [7]);
+			}
+			dayOfWeek = dayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : dayOfWeek + 1;
+		}
+		int missingRows = 6 - _table.Rows.Count;
+		for (int i = 0; i < missingRows; i++) {
+			_table.Rows.Add (new object [7]);
+		}
+
+		return _table;
+	}
+
+	private void GenerateCalendarLabels ()
+	{
+		_calendar.Style.ColumnStyles.Clear ();
+		for (int i = 0; i < 7; i++) {
+			var abbreviatedDayName = CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedDayName ((DayOfWeek)i);
+			_calendar.Style.ColumnStyles.Add (i, new ColumnStyle () {
+				MaxWidth = abbreviatedDayName.Length,
+				MinWidth = abbreviatedDayName.Length,
+				MinAcceptableWidth = abbreviatedDayName.Length
+			});
+			_table.Columns.Add (abbreviatedDayName);
+		}
+		_calendar.Width = CalculateCalendarWidth ();
+	}
+
+	private int CalculateCalendarWidth ()
+	{
+		return _calendar.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 7;
+	}
+
+	private void SelectDayOnCalendar (int day)
+	{
+		for (int i = 0; i < _table.Rows.Count; i++) {
+			for (int j = 0; j < _table.Columns.Count; j++) {
+				if (_table.Rows [i] [j].ToString () == day.ToString ()) {
+					_calendar.SetSelection (j, i, false);
+					return;
+				}
+			}
+		}
+	}
+
+	private string GetForwardButtonText () => Glyphs.RightArrow.ToString () + Glyphs.RightArrow.ToString ();
+
+	private string GetBackButtonText () => Glyphs.LeftArrow.ToString () + Glyphs.LeftArrow.ToString ();
+
+	///<inheritdoc/>
+	protected override void Dispose (bool disposing)
+	{
+		_dateLabel.Dispose ();
+		_calendar.Dispose ();
+		_dateField.Dispose ();
+		_table.Dispose ();
+		_previousMonthButton.Dispose ();
+		_nextMonthButton.Dispose ();
+		base.Dispose (disposing);
+	}
+}

+ 2 - 2
Terminal.Gui/Views/Dialog.cs

@@ -7,7 +7,7 @@ namespace Terminal.Gui;
 
 /// <summary>
 /// The <see cref="Dialog"/> <see cref="View"/> is a <see cref="Window"/> that by default is centered and contains one 
-/// or more <see cref="Button"/>s. It defaults to the <see cref="Colors.Dialog"/> color scheme and has a 1 cell padding around the edges.
+/// or more <see cref="Button"/>s. It defaults to the <c>Colors.ColorSchemes ["Dialog"]</c> color scheme and has a 1 cell padding around the edges.
 /// </summary>
 /// <remarks>
 ///  To run the <see cref="Dialog"/> modally, create the <see cref="Dialog"/>, and pass it to <see cref="Application.Run(Func{Exception, bool})"/>. 
@@ -65,7 +65,7 @@ public class Dialog : Window {
 		Width = Dim.Percent (85);// Dim.Auto (min: Dim.Percent (10));
 		Height = Dim.Percent (85);//Dim.Auto (min: Dim.Percent (50));
 
-		ColorScheme = Colors.Dialog;
+		ColorScheme = Colors.ColorSchemes ["Dialog"];
 
 		Modal = true;
 		ButtonAlignment = DefaultButtonAlignment;

+ 1153 - 1187
Terminal.Gui/Views/FileDialog.cs

@@ -1,1536 +1,1502 @@
 using System;
 using System.Collections.Generic;
-using System.Data;
 using System.IO;
 using System.IO.Abstractions;
 using System.Linq;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
-using System.Text;
 using Terminal.Gui.Resources;
-using static Terminal.Gui.ConfigurationManager;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+
+/// <summary>
+/// Modal dialog for selecting files/directories. Has auto-complete and expandable
+/// navigation pane (Recent, Root drives etc).
+/// </summary>
+public class FileDialog : Dialog {
 	/// <summary>
-	/// Modal dialog for selecting files/directories. Has auto-complete and expandable
-	/// navigation pane (Recent, Root drives etc).
+	/// Gets the Path separators for the operating system
 	/// </summary>
-	public partial class FileDialog : Dialog {
+	internal static char [] Separators = {
+		System.IO.Path.AltDirectorySeparatorChar,
+		System.IO.Path.DirectorySeparatorChar
+	};
 
-		/// <summary>
-		/// Gets settings for controlling how visual elements behave.  Style changes should
-		/// be made before the <see cref="Dialog"/> is loaded and shown to the user for the
-		/// first time.
-		/// </summary>
-		public FileDialogStyle Style { get; }
+	/// <summary>
+	/// Characters to prevent entry into <see cref="tbPath"/>. Note that this is not using
+	/// <see cref="System.IO.Path.GetInvalidFileNameChars"/> because we do want to allow directory
+	/// separators, arrow keys etc.
+	/// </summary>
+	static readonly char [] badChars = {
+		'"', '<', '>', '|', '*', '?'
+	};
 
-		/// <summary>
-		/// The maximum number of results that will be collected
-		/// when searching before stopping.
-		/// </summary>
-		/// <remarks>
-		/// This prevents performance issues e.g. when searching
-		/// root of file system for a common letter (e.g. 'e').
-		/// </remarks>
-		[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-		public static int MaxSearchResults { get; set; } = 10000;
+	Dictionary<IDirectoryInfo, string> _treeRoots = new ();
+	MenuBarItem allowedTypeMenu;
+	MenuBar allowedTypeMenuBar;
+	MenuItem [] allowedTypeMenuItems;
+	readonly Button btnBack;
+	readonly Button btnCancel;
+	readonly Button btnForward;
+	readonly Button btnOk;
+	readonly Button btnToggleSplitterCollapse;
+	readonly Button btnUp;
 
-		/// <summary>
-		/// True if the file/folder must exist already to be selected.
-		/// This prevents user from entering the name of something that
-		/// doesn't exist. Defaults to false.
-		/// </summary>
-		public bool MustExist { get; set; }
+	int currentSortColumn;
 
-		/// <summary>
-		/// Gets the Path separators for the operating system
-		/// </summary>
-		internal static char [] Separators = new []
-		{
-			System.IO.Path.AltDirectorySeparatorChar,
-			System.IO.Path.DirectorySeparatorChar,
-		};
+	bool currentSortIsAsc = true;
 
-		/// <summary>
-		/// Characters to prevent entry into <see cref="tbPath"/>. Note that this is not using
-		/// <see cref="System.IO.Path.GetInvalidFileNameChars"/> because we do want to allow directory
-		/// separators, arrow keys etc.
-		/// </summary>
-		private static char [] badChars = new []
-		{
-			'"','<','>','|','*','?',
-		};
+	bool disposed;
+	string feedback;
+	readonly IFileSystem fileSystem;
 
-		/// <summary>
-		/// The UI selected <see cref="IAllowedType"/> from combo box. May be null.
-		/// </summary>
-		public IAllowedType CurrentFilter { get; private set; }
+	readonly FileDialogHistory history;
+	bool loaded;
 
-		private bool pushingState = false;
-		private bool loaded = false;
+	/// <summary>
+	/// Locking object for ensuring only a single <see cref="SearchState"/> executes at once.
+	/// </summary>
+	internal object onlyOneSearchLock = new ();
 
-		/// <summary>
-		/// Gets the currently open directory and known children presented in the dialog.
-		/// </summary>
-		internal FileDialogState State { get; private set; }
+	bool pushingState;
+	readonly SpinnerView spinnerView;
+	readonly TileView splitContainer;
 
-		/// <summary>
-		/// Locking object for ensuring only a single <see cref="SearchState"/> executes at once.
-		/// </summary>
-		internal object onlyOneSearchLock = new object ();
-
-		private bool disposed = false;
-		private IFileSystem fileSystem;
-		private TextField tbPath;
-
-		private FileDialogHistory history;
-
-		private TableView tableView;
-		private TreeView<IFileSystemInfo> treeView;
-		private TileView splitContainer;
-		private Button btnOk;
-		private Button btnCancel;
-		private Button btnToggleSplitterCollapse;
-		private Button btnForward;
-		private Button btnBack;
-		private Button btnUp;
-		private string feedback;
-		private TextField tbFind;
-		private SpinnerView spinnerView;
-		private MenuBar allowedTypeMenuBar;
-		private MenuBarItem allowedTypeMenu;
-		private MenuItem [] allowedTypeMenuItems;
-
-		private int currentSortColumn;
-
-		private bool currentSortIsAsc = true;
-		private Dictionary<IDirectoryInfo, string> _treeRoots = new Dictionary<IDirectoryInfo, string> ();
+	readonly TableView tableView;
+	readonly TextField tbFind;
+	readonly TextField tbPath;
+	readonly TreeView<IFileSystemInfo> treeView;
 
-		/// <summary>
-		/// Event fired when user attempts to confirm a selection (or multi selection).
-		/// Allows you to cancel the selection or undertake alternative behavior e.g.
-		/// open a dialog "File already exists, Overwrite? yes/no".
-		/// </summary>
-		public event EventHandler<FilesSelectedEventArgs> FilesSelected;
+	/// <summary>
+	/// Initializes a new instance of the <see cref="FileDialog"/> class.
+	/// </summary>
+	public FileDialog () : this (new FileSystem ()) { }
 
-		/// <summary>
-		/// Gets or sets behavior of the <see cref="FileDialog"/> when the user attempts
-		/// to delete a selected file(s).  Set to null to prevent deleting.
-		/// </summary>
-		/// <remarks>Ensure you use a try/catch block with appropriate
-		/// error handling (e.g. showing a <see cref="MessageBox"/></remarks>
-		public IFileOperations FileOperationsHandler { get; set; } = new DefaultFileOperations ();
+	/// <summary>
+	/// Initializes a new instance of the <see cref="FileDialog"/> class with
+	/// a custom <see cref="IFileSystem"/>.
+	/// </summary>
+	/// <remarks>This overload is mainly useful for testing.</remarks>
+	public FileDialog (IFileSystem fileSystem)
+	{
+		this.fileSystem = fileSystem;
+		Style = new FileDialogStyle (fileSystem);
+
+		btnOk = new Button (Style.OkButtonText) {
+			Y = Pos.AnchorEnd (1),
+			X = Pos.Function (CalculateOkButtonPosX),
+			IsDefault = true
+		};
+		btnOk.Clicked += (s, e) => Accept (true);
+		btnOk.KeyDown += (s, k) => {
+			NavigateIf (k, KeyCode.CursorLeft, btnCancel);
+			NavigateIf (k, KeyCode.CursorUp, tableView);
+		};
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="FileDialog"/> class.
-		/// </summary>
-		public FileDialog () : this (new FileSystem ())
-		{
+		btnCancel = new Button (Strings.btnCancel) {
+			Y = Pos.AnchorEnd (1),
+			X = Pos.Right (btnOk) + 1
+		};
+		btnCancel.KeyDown += (s, k) => {
+			NavigateIf (k, KeyCode.CursorLeft, btnToggleSplitterCollapse);
+			NavigateIf (k, KeyCode.CursorUp, tableView);
+			NavigateIf (k, KeyCode.CursorRight, btnOk);
+		};
+		btnCancel.Clicked += (s, e) => {
+			Application.RequestStop ();
+		};
 
-		}
+		btnUp = new Button { X = 0, Y = 1, NoPadding = true };
+		btnUp.Text = GetUpButtonText ();
+		btnUp.Clicked += (s, e) => history.Up ();
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="FileDialog"/> class with
-		/// a custom <see cref="IFileSystem"/>.
-		/// </summary>
-		/// <remarks>This overload is mainly useful for testing.</remarks>
-		public FileDialog (IFileSystem fileSystem)
-		{
-			this.fileSystem = fileSystem;
-			Style = new FileDialogStyle (fileSystem);
+		btnBack = new Button { X = Pos.Right (btnUp) + 1, Y = 1, NoPadding = true };
+		btnBack.Text = GetBackButtonText ();
+		btnBack.Clicked += (s, e) => history.Back ();
 
-			this.btnOk = new Button (Style.OkButtonText) {
-				Y = Pos.AnchorEnd (1),
-				X = Pos.Function (CalculateOkButtonPosX),
-				IsDefault = true
-			};
-			this.btnOk.Clicked += (s, e) => this.Accept (true);
-			this.btnOk.KeyDown += (s, k) => {
-				this.NavigateIf (k, KeyCode.CursorLeft, this.btnCancel);
-				this.NavigateIf (k, KeyCode.CursorUp, this.tableView);
-			};
+		btnForward = new Button { X = Pos.Right (btnBack) + 1, Y = 1, NoPadding = true };
+		btnForward.Text = GetForwardButtonText ();
+		btnForward.Clicked += (s, e) => history.Forward ();
 
-			this.btnCancel = new Button (Strings.btnCancel) {
-				Y = Pos.AnchorEnd (1),
-				X = Pos.Right (btnOk) + 1
-			};
-			this.btnCancel.KeyDown += (s, k) => {
-				this.NavigateIf (k, KeyCode.CursorLeft, this.btnToggleSplitterCollapse);
-				this.NavigateIf (k, KeyCode.CursorUp, this.tableView);
-				this.NavigateIf (k, KeyCode.CursorRight, this.btnOk);
-			};
-			this.btnCancel.Clicked += (s, e) => {
-				Application.RequestStop ();
-			};
+		tbPath = new TextField {
+			Width = Dim.Fill (),
+			CaptionColor = new Color (Color.Black)
+		};
+		tbPath.KeyDown += (s, k) => {
 
-			this.btnUp = new Button () { X = 0, Y = 1, NoPadding = true };
-			btnUp.Text = GetUpButtonText ();
-			this.btnUp.Clicked += (s, e) => this.history.Up ();
+			ClearFeedback ();
 
-			this.btnBack = new Button () { X = Pos.Right (btnUp) + 1, Y = 1, NoPadding = true };
-			btnBack.Text = GetBackButtonText ();
-			this.btnBack.Clicked += (s, e) => this.history.Back ();
+			AcceptIf (k, KeyCode.Enter);
 
-			this.btnForward = new Button () { X = Pos.Right (btnBack) + 1, Y = 1, NoPadding = true };
-			btnForward.Text = GetForwardButtonText ();
-			this.btnForward.Clicked += (s, e) => this.history.Forward ();
+			SuppressIfBadChar (k);
+		};
 
-			this.tbPath = new TextField {
-				Width = Dim.Fill (0),
-				CaptionColor = new Color (Color.Black)
-			};
-			this.tbPath.KeyDown += (s, k) => {
+		tbPath.Autocomplete = new AppendAutocomplete (tbPath);
+		tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
 
-				ClearFeedback ();
+		splitContainer = new TileView {
+			X = 0,
+			Y = 2,
+			Width = Dim.Fill (),
+			Height = Dim.Fill (1)
+		};
 
-				this.AcceptIf (k, KeyCode.Enter);
+		Initialized += (s, e) => {
+			splitContainer.SetSplitterPos (0, 30);
+			splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
+		};
+		//			this.splitContainer.Border.BorderStyle = BorderStyle.None;
 
-				this.SuppressIfBadChar (k);
-			};
+		tableView = new TableView {
+			Width = Dim.Fill (),
+			Height = Dim.Fill (),
+			FullRowSelect = true,
+			CollectionNavigator = new FileDialogCollectionNavigator (this)
+		};
+		tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
+		tableView.MouseClick += OnTableViewMouseClick;
+		tableView.Style.InvertSelectedCellFirstCharacter = true;
+		Style.TableStyle = tableView.Style;
 
-			tbPath.Autocomplete = new AppendAutocomplete (tbPath);
-			tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
+		var nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0);
+		nameStyle.MinWidth = 10;
+		nameStyle.ColorGetter = ColorGetter;
 
-			this.splitContainer = new TileView () {
-				X = 0,
-				Y = 2,
-				Width = Dim.Fill (0),
-				Height = Dim.Fill (1),
-			};
-			
-			Initialized += (s, e) => {
-				this.splitContainer.SetSplitterPos (0, 30);
-				this.splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
-			};
-			//			this.splitContainer.Border.BorderStyle = BorderStyle.None;
+		var sizeStyle = Style.TableStyle.GetOrCreateColumnStyle (1);
+		sizeStyle.MinWidth = 10;
+		sizeStyle.ColorGetter = ColorGetter;
 
-			this.tableView = new TableView {
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-				FullRowSelect = true,
-				CollectionNavigator = new FileDialogCollectionNavigator (this)
-			};
-			this.tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
-			this.tableView.MouseClick += OnTableViewMouseClick;
-			tableView.Style.InvertSelectedCellFirstCharacter = true;
-			Style.TableStyle = tableView.Style;
-
-			var nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0);
-			nameStyle.MinWidth = 10;
-			nameStyle.ColorGetter = this.ColorGetter;
-
-			var sizeStyle = Style.TableStyle.GetOrCreateColumnStyle (1);
-			sizeStyle.MinWidth = 10;
-			sizeStyle.ColorGetter = this.ColorGetter;
-
-			var dateModifiedStyle = Style.TableStyle.GetOrCreateColumnStyle (2);
-			dateModifiedStyle.MinWidth = 30;
-			dateModifiedStyle.ColorGetter = this.ColorGetter;
-
-			var typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3);
-			typeStyle.MinWidth = 6;
-			typeStyle.ColorGetter = this.ColorGetter;
-
-			this.tableView.KeyDown += (s, k) => {
-				if (this.tableView.SelectedRow <= 0) {
-					this.NavigateIf (k, KeyCode.CursorUp, this.tbPath);
-				}
-				if (this.tableView.SelectedRow == this.tableView.Table.Rows - 1) {
-					this.NavigateIf (k, KeyCode.CursorDown, this.btnToggleSplitterCollapse);
-				}
+		var dateModifiedStyle = Style.TableStyle.GetOrCreateColumnStyle (2);
+		dateModifiedStyle.MinWidth = 30;
+		dateModifiedStyle.ColorGetter = ColorGetter;
 
-				if (splitContainer.Tiles.First ().ContentView.Visible && tableView.SelectedColumn == 0) {
-					this.NavigateIf (k, KeyCode.CursorLeft, this.treeView);
-				}
+		var typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3);
+		typeStyle.MinWidth = 6;
+		typeStyle.ColorGetter = ColorGetter;
 
-				if (k.Handled) {
-					return;
-				}
-			};
+		tableView.KeyDown += (s, k) => {
+			if (tableView.SelectedRow <= 0) {
+				NavigateIf (k, KeyCode.CursorUp, tbPath);
+			}
+			if (tableView.SelectedRow == tableView.Table.Rows - 1) {
+				NavigateIf (k, KeyCode.CursorDown, btnToggleSplitterCollapse);
+			}
 
-			this.treeView = new TreeView<IFileSystemInfo> () {
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-			};
+			if (splitContainer.Tiles.First ().ContentView.Visible && tableView.SelectedColumn == 0) {
+				NavigateIf (k, KeyCode.CursorLeft, treeView);
+			}
 
-			var fileDialogTreeBuilder = new FileSystemTreeBuilder ();
-			this.treeView.TreeBuilder = fileDialogTreeBuilder;
-			this.treeView.AspectGetter = this.AspectGetter;
-			this.Style.TreeStyle = treeView.Style;
+			if (k.Handled) { }
+		};
 
-			this.treeView.SelectionChanged += this.TreeView_SelectionChanged;
+		treeView = new TreeView<IFileSystemInfo> {
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
 
-			this.splitContainer.Tiles.ElementAt (0).ContentView.Add (this.treeView);
-			this.splitContainer.Tiles.ElementAt (1).ContentView.Add (this.tableView);
+		var fileDialogTreeBuilder = new FileSystemTreeBuilder ();
+		treeView.TreeBuilder = fileDialogTreeBuilder;
+		treeView.AspectGetter = AspectGetter;
+		Style.TreeStyle = treeView.Style;
 
-			this.btnToggleSplitterCollapse = new Button (GetToggleSplitterText (false)) {
-				Y = Pos.AnchorEnd (1),
-			};
-			this.btnToggleSplitterCollapse.Clicked += (s, e) => {
-				var tile = this.splitContainer.Tiles.ElementAt (0);
+		treeView.SelectionChanged += TreeView_SelectionChanged;
 
-				var newState = !tile.ContentView.Visible;
-				tile.ContentView.Visible = newState;
-				this.btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState);
-				this.LayoutSubviews ();
-			};
+		splitContainer.Tiles.ElementAt (0).ContentView.Add (treeView);
+		splitContainer.Tiles.ElementAt (1).ContentView.Add (tableView);
 
-			tbFind = new TextField {
-				X = Pos.Right (this.btnToggleSplitterCollapse) + 1,
-				CaptionColor = new Color (Color.Black),
-				Width = 30,
-				Y = Pos.AnchorEnd (1),
-				HotKey = KeyCode.F | KeyCode.AltMask
-			};
-			spinnerView = new SpinnerView () {
-				X = Pos.Right (tbFind) + 1,
-				Y = Pos.AnchorEnd (1),
-				Visible = false,
-			};
+		btnToggleSplitterCollapse = new Button (GetToggleSplitterText (false)) {
+			Y = Pos.AnchorEnd (1)
+		};
+		btnToggleSplitterCollapse.Clicked += (s, e) => {
+			var tile = splitContainer.Tiles.ElementAt (0);
 
-			tbFind.TextChanged += (s, o) => RestartSearch ();
-			tbFind.KeyDown += (s, o) => {
-				if (o.KeyCode == KeyCode.Enter) {
-					RestartSearch ();
-					o.Handled = true;
-				}
+			var newState = !tile.ContentView.Visible;
+			tile.ContentView.Visible = newState;
+			btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState);
+			LayoutSubviews ();
+		};
 
-				if (o.KeyCode == KeyCode.Esc) {
-					if (CancelSearch ()) {
-						o.Handled = true;
-					}
-				}
-				if (tbFind.CursorIsAtEnd ()) {
-					NavigateIf (o, KeyCode.CursorRight, btnCancel);
-				}
-				if (tbFind.CursorIsAtStart ()) {
-					NavigateIf (o, KeyCode.CursorLeft, btnToggleSplitterCollapse);
+		tbFind = new TextField {
+			X = Pos.Right (btnToggleSplitterCollapse) + 1,
+			CaptionColor = new Color (Color.Black),
+			Width = 30,
+			Y = Pos.AnchorEnd (1),
+			HotKey = KeyCode.F | KeyCode.AltMask
+		};
+		spinnerView = new SpinnerView {
+			X = Pos.Right (tbFind) + 1,
+			Y = Pos.AnchorEnd (1),
+			Visible = false
+		};
+
+		tbFind.TextChanged += (s, o) => RestartSearch ();
+		tbFind.KeyDown += (s, o) => {
+			if (o.KeyCode == KeyCode.Enter) {
+				RestartSearch ();
+				o.Handled = true;
+			}
+
+			if (o.KeyCode == KeyCode.Esc) {
+				if (CancelSearch ()) {
+					o.Handled = true;
 				}
-			};
+			}
+			if (tbFind.CursorIsAtEnd ()) {
+				NavigateIf (o, KeyCode.CursorRight, btnCancel);
+			}
+			if (tbFind.CursorIsAtStart ()) {
+				NavigateIf (o, KeyCode.CursorLeft, btnToggleSplitterCollapse);
+			}
+		};
 
-			this.tableView.Style.ShowHorizontalHeaderOverline = true;
-			this.tableView.Style.ShowVerticalCellLines = true;
-			this.tableView.Style.ShowVerticalHeaderLines = true;
-			this.tableView.Style.AlwaysShowHeaders = true;
-			this.tableView.Style.ShowHorizontalHeaderUnderline = true;
-			this.tableView.Style.ShowHorizontalScrollIndicators = true;
+		tableView.Style.ShowHorizontalHeaderOverline = true;
+		tableView.Style.ShowVerticalCellLines = true;
+		tableView.Style.ShowVerticalHeaderLines = true;
+		tableView.Style.AlwaysShowHeaders = true;
+		tableView.Style.ShowHorizontalHeaderUnderline = true;
+		tableView.Style.ShowHorizontalScrollIndicators = true;
 
-			this.history = new FileDialogHistory (this);
+		history = new FileDialogHistory (this);
 
-			this.tbPath.TextChanged += (s, e) => this.PathChanged ();
+		tbPath.TextChanged += (s, e) => PathChanged ();
 
-			this.tableView.CellActivated += this.CellActivate;
-			this.tableView.KeyUp += (s, k) => k.Handled = this.TableView_KeyUp (k);
-			this.tableView.SelectedCellChanged += this.TableView_SelectedCellChanged;
+		tableView.CellActivated += CellActivate;
+		tableView.KeyUp += (s, k) => k.Handled = TableView_KeyUp (k);
+		tableView.SelectedCellChanged += TableView_SelectedCellChanged;
 
-			this.tableView.KeyBindings.Add (KeyCode.Home, Command.TopHome);
-			this.tableView.KeyBindings.Add (KeyCode.End, Command.BottomEnd);
-			this.tableView.KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.TopHomeExtend);
-			this.tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.BottomEndExtend);
+		tableView.KeyBindings.Add (KeyCode.Home, Command.TopHome);
+		tableView.KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+		tableView.KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.TopHomeExtend);
+		tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.BottomEndExtend);
 
-			this.treeView.KeyDown += (s, k) => {
+		treeView.KeyDown += (s, k) => {
 
-				var selected = treeView.SelectedObject;
-				if (selected != null) {
-					if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) {
-						this.NavigateIf (k, KeyCode.CursorRight, this.tableView);
-					} else
-					if (treeView.GetObjectRow (selected) == 0) {
-						this.NavigateIf (k, KeyCode.CursorUp, this.tbPath);
-					}
+			var selected = treeView.SelectedObject;
+			if (selected != null) {
+				if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) {
+					NavigateIf (k, KeyCode.CursorRight, tableView);
+				} else if (treeView.GetObjectRow (selected) == 0) {
+					NavigateIf (k, KeyCode.CursorUp, tbPath);
 				}
+			}
 
-				if (k.Handled) {
-					return;
-				}
+			if (k.Handled) {
+				return;
+			}
 
-				k.Handled = this.TreeView_KeyDown (k);
+			k.Handled = TreeView_KeyDown (k);
 
-			};
+		};
 
-			this.AllowsMultipleSelection = false;
+		AllowsMultipleSelection = false;
+
+		UpdateNavigationVisibility ();
+
+		// Determines tab order
+		Add (btnToggleSplitterCollapse);
+		Add (tbFind);
+		Add (spinnerView);
+		Add (btnOk);
+		Add (btnCancel);
+		Add (btnUp);
+		Add (btnBack);
+		Add (btnForward);
+		Add (tbPath);
+		Add (splitContainer);
+	}
 
-			this.UpdateNavigationVisibility ();
+	/// <summary>
+	/// Gets settings for controlling how visual elements behave.  Style changes should
+	/// be made before the <see cref="Dialog"/> is loaded and shown to the user for the
+	/// first time.
+	/// </summary>
+	public FileDialogStyle Style { get; }
 
-			// Determines tab order
-			this.Add (this.btnToggleSplitterCollapse);
-			this.Add (this.tbFind);
-			this.Add (this.spinnerView);
-			this.Add (this.btnOk);
-			this.Add (this.btnCancel);
-			this.Add (this.btnUp);
-			this.Add (this.btnBack);
-			this.Add (this.btnForward);
-			this.Add (this.tbPath);
-			this.Add (this.splitContainer);
-		}
+	/// <summary>
+	/// The maximum number of results that will be collected
+	/// when searching before stopping.
+	/// </summary>
+	/// <remarks>
+	/// This prevents performance issues e.g. when searching
+	/// root of file system for a common letter (e.g. 'e').
+	/// </remarks>
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+	public static int MaxSearchResults { get; set; } = 10000;
 
-		private int CalculateOkButtonPosX ()
-		{
-			return this.Bounds.Width
-				- btnOk.Bounds.Width
-				- btnCancel.Bounds.Width
-				- 1
-				// TODO: Fiddle factor, seems the Bounds are wrong for someone
-				- 2;
-		}
+	/// <summary>
+	/// True if the file/folder must exist already to be selected.
+	/// This prevents user from entering the name of something that
+	/// doesn't exist. Defaults to false.
+	/// </summary>
+	public bool MustExist { get; set; }
 
-		private string AspectGetter (object o)
-		{
-			var fsi = (IFileSystemInfo)o;
+	/// <summary>
+	/// The UI selected <see cref="IAllowedType"/> from combo box. May be null.
+	/// </summary>
+	public IAllowedType CurrentFilter { get; private set; }
 
-			if (o is IDirectoryInfo dir && _treeRoots.ContainsKey (dir)) {
+	/// <summary>
+	/// Gets the currently open directory and known children presented in the dialog.
+	/// </summary>
+	internal FileDialogState State { get; private set; }
 
-				// Directory has a special name e.g. 'Pictures'
-				return _treeRoots [dir];
-			}
+	/// <summary>
+	/// Gets or sets behavior of the <see cref="FileDialog"/> when the user attempts
+	/// to delete a selected file(s).  Set to null to prevent deleting.
+	/// </summary>
+	/// <remarks>
+	/// Ensure you use a try/catch block with appropriate
+	/// error handling (e.g. showing a <see cref="MessageBox"/>
+	/// </remarks>
+	public IFileOperations FileOperationsHandler { get; set; } = new DefaultFileOperations ();
+
+	/// <summary>
+	/// Gets or Sets which <see cref="System.IO.FileSystemInfo"/> type can be selected.
+	/// Defaults to <see cref="OpenMode.Mixed"/> (i.e. <see cref="DirectoryInfo"/> or
+	/// <see cref="FileInfo"/>).
+	/// </summary>
+	public OpenMode OpenMode { get; set; } = OpenMode.Mixed;
 
-			return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim ();
+	/// <summary>
+	/// Gets or Sets the selected path in the dialog. This is the result that should
+	/// be used if <see cref="AllowsMultipleSelection"/> is off and <see cref="Canceled"/>
+	/// is true.
+	/// </summary>
+	public string Path {
+		get => tbPath.Text;
+		set {
+			tbPath.Text = value;
+			tbPath.MoveEnd ();
 		}
+	}
 
-		private void OnTableViewMouseClick (object sender, MouseEventEventArgs e)
-		{
-			var clickedCell = this.tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
+	/// <summary>
+	/// Defines how the dialog matches files/folders when using the search
+	/// box. Provide a custom implementation if you want to tailor how matching
+	/// is performed.
+	/// </summary>
+	public ISearchMatcher SearchMatcher { get; set; } = new DefaultSearchMatcher ();
 
-			if (clickedCol != null) {
-				if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+	/// <summary>
+	/// Gets or Sets a value indicating whether to allow selecting
+	/// multiple existing files/directories. Defaults to false.
+	/// </summary>
+	public bool AllowsMultipleSelection {
+		get => tableView.MultiSelect;
+		set => tableView.MultiSelect = value;
+	}
 
-					// left click in a header
-					this.SortColumn (clickedCol.Value);
-				} else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+	/// <summary>
+	/// Gets or Sets a collection of file types that the user can/must select. Only applies
+	/// when <see cref="OpenMode"/> is <see cref="OpenMode.File"/> or <see cref="OpenMode.Mixed"/>.
+	/// </summary>
+	/// <remarks>
+	/// <see cref="AllowedTypeAny"/> adds the option to select any type (*.*). If this
+	/// collection is empty then any type is supported and no Types drop-down is shown.
+	/// </remarks>
+	public List<IAllowedType> AllowedTypes { get; set; } = new ();
 
-					// right click in a header
-					this.ShowHeaderContextMenu (clickedCol.Value, e);
-				}
-			} else {
-				if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+	/// <summary>
+	/// Gets a value indicating whether the <see cref="FileDialog"/> was closed
+	/// without confirming a selection.
+	/// </summary>
+	public bool Canceled { get; private set; } = true;
 
-					// right click in rest of table
-					this.ShowCellContextMenu (clickedCell, e);
-				}
-			}
-		}
+	/// <summary>
+	/// Gets all files/directories selected or an empty collection
+	/// <see cref="AllowsMultipleSelection"/> is <see langword="false"/> or <see cref="Canceled"/>.
+	/// </summary>
+	/// <remarks>If selecting only a single file/directory then you should use <see cref="Path"/> instead.</remarks>
+	public IReadOnlyList<string> MultiSelected { get; private set; }
+		= Enumerable.Empty<string> ().ToList ().AsReadOnly ();
 
-		private string GetForwardButtonText ()
-		{
-			return "-" + CM.Glyphs.RightArrow;
-		}
+	/// <summary>
+	/// Event fired when user attempts to confirm a selection (or multi selection).
+	/// Allows you to cancel the selection or undertake alternative behavior e.g.
+	/// open a dialog "File already exists, Overwrite? yes/no".
+	/// </summary>
+	public event EventHandler<FilesSelectedEventArgs> FilesSelected;
 
-		private string GetBackButtonText ()
-		{
-			return CM.Glyphs.LeftArrow + "-";
+	int CalculateOkButtonPosX ()
+	{
+		if (!IsInitialized || !btnOk.IsInitialized || !btnCancel.IsInitialized) {
+			return 0;
 		}
+		return Bounds.Width -
+		       btnOk.Bounds.Width -
+		       btnCancel.Bounds.Width -
+		       1
+		       // TODO: Fiddle factor, seems the Bounds are wrong for someone
+		       -
+		       2;
+	}
 
-		private string GetUpButtonText ()
-		{
-			return Style.UseUnicodeCharacters ? "◭" : "▲";
-		}
+	string AspectGetter (object o)
+	{
+		var fsi = (IFileSystemInfo)o;
 
-		private string GetToggleSplitterText (bool isExpanded)
-		{
-			return isExpanded ?
-				new string ((char)CM.Glyphs.LeftArrow.Value, 2) :
-				new string ((char)CM.Glyphs.RightArrow.Value, 2);
+		if (o is IDirectoryInfo dir && _treeRoots.ContainsKey (dir)) {
+
+			// Directory has a special name e.g. 'Pictures'
+			return _treeRoots [dir];
 		}
 
-		private void Delete ()
-		{
-			var toDelete = GetFocusedFiles ();
+		return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim ();
+	}
 
-			if (toDelete != null && FileOperationsHandler.Delete (toDelete)) {
-				RefreshState ();
-			}
-		}
+	void OnTableViewMouseClick (object sender, MouseEventEventArgs e)
+	{
+		var clickedCell = tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var clickedCol);
 
-		private void Rename ()
-		{
-			var toRename = GetFocusedFiles ();
+		if (clickedCol != null) {
+			if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
 
-			if (toRename?.Length == 1) {
-				var newNamed = FileOperationsHandler.Rename (this.fileSystem, toRename.Single ());
+				// left click in a header
+				SortColumn (clickedCol.Value);
+			} else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
 
-				if (newNamed != null) {
-					RefreshState ();
-					RestoreSelection (newNamed);
-				}
+				// right click in a header
+				ShowHeaderContextMenu (clickedCol.Value, e);
 			}
-		}
-		private void New ()
-		{
-			if (State != null) {
-				var created = FileOperationsHandler.New (this.fileSystem, State.Directory);
-				if (created != null) {
-					RefreshState ();
-					RestoreSelection (created);
-				}
+		} else {
+			if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+
+				// right click in rest of table
+				ShowCellContextMenu (clickedCell, e);
 			}
 		}
-		private IFileSystemInfo [] GetFocusedFiles ()
-		{
+	}
 
-			if (!tableView.HasFocus || !tableView.CanFocus || FileOperationsHandler == null) {
-				return null;
-			}
+	string GetForwardButtonText () => "-" + Glyphs.RightArrow;
 
-			tableView.EnsureValidSelection ();
+	string GetBackButtonText () => Glyphs.LeftArrow + "-";
 
-			if (tableView.SelectedRow < 0) {
-				return null;
-			}
+	string GetUpButtonText () => Style.UseUnicodeCharacters ? "◭" : "▲";
 
-			return tableView.GetAllSelectedCells ()
-				.Select (c => c.Y)
-				.Distinct ()
-				.Select (RowToStats)
-				.Where (s => !s.IsParent)
-				.Select (d => d.FileSystemInfo)
-				.ToArray ();
-		}
+	string GetToggleSplitterText (bool isExpanded) => isExpanded ?
+		new string ((char)Glyphs.LeftArrow.Value, 2) :
+		new string ((char)Glyphs.RightArrow.Value, 2);
 
+	void Delete ()
+	{
+		var toDelete = GetFocusedFiles ();
 
-//		/// <inheritdoc/>
-//		public override bool OnHotKey (KeyEventArgs keyEvent)
-//		{
-//#if BROKE_IN_2927
-//			// BUGBUG: Ctrl-F is forward in a TextField. 
-//			if (this.NavigateIf (keyEvent, Key.Alt | Key.F, this.tbFind)) {
-//				return true;
-//			}
-//#endif
+		if (toDelete != null && FileOperationsHandler.Delete (toDelete)) {
+			RefreshState ();
+		}
+	}
 
-//			ClearFeedback ();
+	void Rename ()
+	{
+		var toRename = GetFocusedFiles ();
 
-//			if (allowedTypeMenuBar != null &&
-//				keyEvent.ConsoleDriverKey == Key.Tab &&
-//				allowedTypeMenuBar.IsMenuOpen) {
-//				allowedTypeMenuBar.CloseMenu (false, false, false);
-//			}
+		if (toRename?.Length == 1) {
+			var newNamed = FileOperationsHandler.Rename (fileSystem, toRename.Single ());
 
-//			return base.OnHotKey (keyEvent);
-//		}
-		private void RestartSearch ()
-		{
-			if (disposed || State?.Directory == null) {
-				return;
+			if (newNamed != null) {
+				RefreshState ();
+				RestoreSelection (newNamed);
 			}
+		}
+	}
 
-			if (State is SearchState oldSearch) {
-				oldSearch.Cancel ();
+	void New ()
+	{
+		if (State != null) {
+			var created = FileOperationsHandler.New (fileSystem, State.Directory);
+			if (created != null) {
+				RefreshState ();
+				RestoreSelection (created);
 			}
+		}
+	}
 
-			// user is clearing search terms
-			if (tbFind.Text == null || tbFind.Text.Length == 0) {
-
-				// Wait for search cancellation (if any) to finish
-				// then push the current dir state
-				lock (onlyOneSearchLock) {
-					PushState (new FileDialogState (State.Directory, this), false);
-				}
-				return;
-			}
+	IFileSystemInfo [] GetFocusedFiles ()
+	{
 
-			PushState (new SearchState (State?.Directory, this, tbFind.Text), true);
+		if (!tableView.HasFocus || !tableView.CanFocus || FileOperationsHandler == null) {
+			return null;
 		}
 
-		/// <inheritdoc/>
-		protected override void Dispose (bool disposing)
-		{
-			disposed = true;
-			base.Dispose (disposing);
+		tableView.EnsureValidSelection ();
 
-			CancelSearch ();
+		if (tableView.SelectedRow < 0) {
+			return null;
 		}
 
-		private bool CancelSearch ()
-		{
-			if (State is SearchState search) {
-				return search.Cancel ();
-			}
+		return tableView.GetAllSelectedCells ()
+				.Select (c => c.Y)
+				.Distinct ()
+				.Select (RowToStats)
+				.Where (s => !s.IsParent)
+				.Select (d => d.FileSystemInfo)
+				.ToArray ();
+	}
 
-			return false;
+
+	//		/// <inheritdoc/>
+	//		public override bool OnHotKey (KeyEventArgs keyEvent)
+	//		{
+	//#if BROKE_IN_2927
+	//			// BUGBUG: Ctrl-F is forward in a TextField. 
+	//			if (this.NavigateIf (keyEvent, Key.Alt | Key.F, this.tbFind)) {
+	//				return true;
+	//			}
+	//#endif
+
+	//			ClearFeedback ();
+
+	//			if (allowedTypeMenuBar != null &&
+	//				keyEvent.ConsoleDriverKey == Key.Tab &&
+	//				allowedTypeMenuBar.IsMenuOpen) {
+	//				allowedTypeMenuBar.CloseMenu (false, false, false);
+	//			}
+
+	//			return base.OnHotKey (keyEvent);
+	//		}
+	void RestartSearch ()
+	{
+		if (disposed || State?.Directory == null) {
+			return;
 		}
 
-		private void ClearFeedback ()
-		{
-			feedback = null;
+		if (State is SearchState oldSearch) {
+			oldSearch.Cancel ();
 		}
 
-		/// <summary>
-		/// Gets or Sets which <see cref="System.IO.FileSystemInfo"/> type can be selected.
-		/// Defaults to <see cref="OpenMode.Mixed"/> (i.e. <see cref="DirectoryInfo"/> or
-		/// <see cref="FileInfo"/>).
-		/// </summary>
-		public OpenMode OpenMode { get; set; } = OpenMode.Mixed;
+		// user is clearing search terms
+		if (tbFind.Text == null || tbFind.Text.Length == 0) {
 
-		/// <summary>
-		/// Gets or Sets the selected path in the dialog. This is the result that should
-		/// be used if <see cref="AllowsMultipleSelection"/> is off and <see cref="Canceled"/>
-		/// is true.
-		/// </summary>
-		public string Path {
-			get => this.tbPath.Text;
-			set {
-				this.tbPath.Text = value;
-				this.tbPath.MoveEnd ();
+			// Wait for search cancellation (if any) to finish
+			// then push the current dir state
+			lock (onlyOneSearchLock) {
+				PushState (new FileDialogState (State.Directory, this), false);
 			}
+			return;
 		}
 
-		/// <summary>
-		/// Defines how the dialog matches files/folders when using the search
-		/// box. Provide a custom implementation if you want to tailor how matching
-		/// is performed.
-		/// </summary>
-		public ISearchMatcher SearchMatcher { get; set; } = new DefaultSearchMatcher ();
+		PushState (new SearchState (State?.Directory, this, tbFind.Text), true);
+	}
 
-		/// <summary>
-		/// Gets or Sets a value indicating whether to allow selecting 
-		/// multiple existing files/directories. Defaults to false.
-		/// </summary>
-		public bool AllowsMultipleSelection {
-			get => this.tableView.MultiSelect;
-			set => this.tableView.MultiSelect = value;
+	/// <inheritdoc/>
+	protected override void Dispose (bool disposing)
+	{
+		disposed = true;
+		base.Dispose (disposing);
+
+		CancelSearch ();
+	}
+
+	bool CancelSearch ()
+	{
+		if (State is SearchState search) {
+			return search.Cancel ();
 		}
 
-		/// <summary>
-		/// Gets or Sets a collection of file types that the user can/must select. Only applies
-		/// when <see cref="OpenMode"/> is <see cref="OpenMode.File"/> or <see cref="OpenMode.Mixed"/>.
-		/// </summary>
-		/// <remarks><see cref="AllowedTypeAny"/> adds the option to select any type (*.*). If this
-		/// collection is empty then any type is supported and no Types drop-down is shown.</remarks> 
-		public List<IAllowedType> AllowedTypes { get; set; } = new List<IAllowedType> ();
+		return false;
+	}
 
-		/// <summary>
-		/// Gets a value indicating whether the <see cref="FileDialog"/> was closed
-		/// without confirming a selection.
-		/// </summary>
-		public bool Canceled { get; private set; } = true;
+	void ClearFeedback () => feedback = null;
 
-		/// <summary>
-		/// Gets all files/directories selected or an empty collection
-		/// <see cref="AllowsMultipleSelection"/> is <see langword="false"/> or <see cref="Canceled"/>.
-		/// </summary>
-		/// <remarks>If selecting only a single file/directory then you should use <see cref="Path"/> instead.</remarks>
-		public IReadOnlyList<string> MultiSelected { get; private set; }
-			= Enumerable.Empty<string> ().ToList ().AsReadOnly ();
+	/// <inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		base.OnDrawContent (contentArea);
 
-		/// <inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			base.OnDrawContent (contentArea);
+		if (!string.IsNullOrWhiteSpace (feedback)) {
+			var feedbackWidth = feedback.EnumerateRunes ().Sum (c => c.GetColumns ());
+			var feedbackPadLeft = (Bounds.Width - feedbackWidth) / 2 - 1;
 
-			if (!string.IsNullOrWhiteSpace (feedback)) {
-				var feedbackWidth = feedback.EnumerateRunes ().Sum (c => c.GetColumns ());
-				var feedbackPadLeft = ((Bounds.Width - feedbackWidth) / 2) - 1;
+			feedbackPadLeft = Math.Min (Bounds.Width, feedbackPadLeft);
+			feedbackPadLeft = Math.Max (0, feedbackPadLeft);
 
-				feedbackPadLeft = Math.Min (Bounds.Width, feedbackPadLeft);
-				feedbackPadLeft = Math.Max (0, feedbackPadLeft);
+			var feedbackPadRight = Bounds.Width - (feedbackPadLeft + feedbackWidth + 2);
+			feedbackPadRight = Math.Min (Bounds.Width, feedbackPadRight);
+			feedbackPadRight = Math.Max (0, feedbackPadRight);
 
-				var feedbackPadRight = Bounds.Width - (feedbackPadLeft + feedbackWidth + 2);
-				feedbackPadRight = Math.Min (Bounds.Width, feedbackPadRight);
-				feedbackPadRight = Math.Max (0, feedbackPadRight);
+			Move (0, Bounds.Height / 2);
 
-				Move (0, Bounds.Height / 2);
+			Driver.SetAttribute (new Attribute (Color.Red, ColorScheme.Normal.Background));
+			Driver.AddStr (new string (' ', feedbackPadLeft));
+			Driver.AddStr (feedback);
+			Driver.AddStr (new string (' ', feedbackPadRight));
+		}
+	}
 
-				Driver.SetAttribute (new Attribute (Color.Red, this.ColorScheme.Normal.Background));
-				Driver.AddStr (new string (' ', feedbackPadLeft));
-				Driver.AddStr (feedback);
-				Driver.AddStr (new string (' ', feedbackPadRight));
-			}
+	/// <inheritdoc/>
+	public override void OnLoaded ()
+	{
+		base.OnLoaded ();
+		if (loaded) {
+			return;
 		}
+		loaded = true;
 
-		/// <inheritdoc/>
-		public override void OnLoaded ()
-		{
-			base.OnLoaded ();
-			if (loaded) {
-				return;
-			}
-			loaded = true;
+		// May have been updated after instance was constructed
+		btnOk.Text = Style.OkButtonText;
+		btnCancel.Text = Style.CancelButtonText;
+		btnUp.Text = GetUpButtonText ();
+		btnBack.Text = GetBackButtonText ();
+		btnForward.Text = GetForwardButtonText ();
+		btnToggleSplitterCollapse.Text = GetToggleSplitterText (false);
 
-			// May have been updated after instance was constructed
-			this.btnOk.Text = Style.OkButtonText;
-			this.btnCancel.Text = Style.CancelButtonText;
-			this.btnUp.Text = this.GetUpButtonText ();
-			this.btnBack.Text = this.GetBackButtonText ();
-			this.btnForward.Text = this.GetForwardButtonText ();
-			this.btnToggleSplitterCollapse.Text = this.GetToggleSplitterText (false);
+		if (Style.FlipOkCancelButtonLayoutOrder) {
+			btnCancel.X = Pos.Function (CalculateOkButtonPosX);
+			btnOk.X = Pos.Right (btnCancel) + 1;
 
-			if (Style.FlipOkCancelButtonLayoutOrder) {
-				btnCancel.X = Pos.Function (this.CalculateOkButtonPosX);
-				btnOk.X = Pos.Right (btnCancel) + 1;
 
+			// Flip tab order too for consistency
+			var p1 = btnOk.TabIndex;
+			var p2 = btnCancel.TabIndex;
 
-				// Flip tab order too for consistency
-				var p1 = this.btnOk.TabIndex;
-				var p2 = this.btnCancel.TabIndex;
+			btnOk.TabIndex = p2;
+			btnCancel.TabIndex = p1;
+		}
 
-				this.btnOk.TabIndex = p2;
-				this.btnCancel.TabIndex = p1;
-			}
+		tbPath.Caption = Style.PathCaption;
+		tbFind.Caption = Style.SearchCaption;
 
-			tbPath.Caption = Style.PathCaption;
-			tbFind.Caption = Style.SearchCaption;
+		tbPath.Autocomplete.ColorScheme = new ColorScheme (tbPath.ColorScheme) {
+			Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background)
+		};
 
-			tbPath.Autocomplete.ColorScheme.Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background);
+		_treeRoots = Style.TreeRootGetter ();
+		Style.IconProvider.IsOpenGetter = treeView.IsExpanded;
 
-			_treeRoots = Style.TreeRootGetter ();
-			Style.IconProvider.IsOpenGetter = treeView.IsExpanded;
+		treeView.AddObjects (_treeRoots.Keys);
 
-			treeView.AddObjects (_treeRoots.Keys);
+		// if filtering on file type is configured then create the ComboBox and establish
+		// initial filtering by extension(s)
+		if (AllowedTypes.Any ()) {
 
-			// if filtering on file type is configured then create the ComboBox and establish
-			// initial filtering by extension(s)
-			if (this.AllowedTypes.Any ()) {
+			CurrentFilter = AllowedTypes [0];
 
-				this.CurrentFilter = this.AllowedTypes [0];
+			// Fiddle factor
+			var width = AllowedTypes.Max (a => a.ToString ().Length) + 6;
 
-				// Fiddle factor
-				var width = this.AllowedTypes.Max (a => a.ToString ().Length) + 6;
+			allowedTypeMenu = new MenuBarItem ("<placeholder>",
+				allowedTypeMenuItems = AllowedTypes.Select (
+									   (a, i) => new MenuItem (a.ToString (), null, () => {
+										   AllowedTypeMenuClicked (i);
+									   }))
+								   .ToArray ());
 
-				allowedTypeMenu = new MenuBarItem ("<placeholder>",
-					allowedTypeMenuItems = AllowedTypes.Select (
-						(a, i) => new MenuItem (a.ToString (), null, () => {
-							AllowedTypeMenuClicked (i);
-						}))
-					.ToArray ());
+			allowedTypeMenuBar = new MenuBar (new [] { allowedTypeMenu }) {
+				Width = width,
+				Y = 1,
+				X = Pos.AnchorEnd (width),
 
-				allowedTypeMenuBar = new MenuBar (new [] { allowedTypeMenu }) {
-					Width = width,
-					Y = 1,
-					X = Pos.AnchorEnd (width),
+				// TODO: Does not work, if this worked then we could tab to it instead
+				// of having to hit F9
+				CanFocus = true,
+				TabStop = true
+			};
+			AllowedTypeMenuClicked (0);
 
-					// TODO: Does not work, if this worked then we could tab to it instead
-					// of having to hit F9
-					CanFocus = true,
-					TabStop = true
-				};
-				AllowedTypeMenuClicked (0);
+			allowedTypeMenuBar.Enter += (s, e) => {
+				allowedTypeMenuBar.OpenMenu (0);
+			};
 
-				allowedTypeMenuBar.Enter += (s, e) => {
-					allowedTypeMenuBar.OpenMenu (0);
-				};
+			allowedTypeMenuBar.DrawContentComplete += (s, e) => {
 
-				allowedTypeMenuBar.DrawContentComplete += (s, e) => {
+				allowedTypeMenuBar.Move (e.Rect.Width - 1, 0);
+				Driver.AddRune (Glyphs.DownArrow);
 
-					allowedTypeMenuBar.Move (e.Rect.Width - 1, 0);
-					Driver.AddRune (CM.Glyphs.DownArrow);
+			};
 
-				};
+			Add (allowedTypeMenuBar);
+		}
 
-				this.Add (allowedTypeMenuBar);
-			}
+		// if no path has been provided
+		if (tbPath.Text.Length <= 0) {
+			Path = Environment.CurrentDirectory;
+		}
 
-			// if no path has been provided
-			if (this.tbPath.Text.Length <= 0) {
-				this.Path = Environment.CurrentDirectory;
-			}
+		// to streamline user experience and allow direct typing of paths
+		// with zero navigation we start with focus in the text box and any
+		// default/current path fully selected and ready to be overwritten
+		tbPath.FocusFirst ();
+		tbPath.SelectAll ();
 
-			// to streamline user experience and allow direct typing of paths
-			// with zero navigation we start with focus in the text box and any
-			// default/current path fully selected and ready to be overwritten
-			this.tbPath.FocusFirst ();
-			this.tbPath.SelectAll ();
+		if (string.IsNullOrEmpty (Title)) {
+			Title = GetDefaultTitle ();
+		}
+		LayoutSubviews ();
+	}
 
-			if (string.IsNullOrEmpty (Title)) {
-				this.Title = GetDefaultTitle ();
-			}
-			this.LayoutSubviews ();
+	/// <summary>
+	/// Gets a default dialog title, when <see cref="View.Title"/> is not set or empty,
+	/// result of the function will be shown.
+	/// </summary>
+	protected virtual string GetDefaultTitle ()
+	{
+		List<string> titleParts = new () {
+			Strings.fdOpen
+		};
+		if (MustExist) {
+			titleParts.Add (Strings.fdExisting);
 		}
-		/// <summary>
-		/// Gets a default dialog title, when <see cref="View.Title"/> is not set or empty, 
-		/// result of the function will be shown.
-		/// </summary>
-		protected virtual string GetDefaultTitle ()
-		{
-			List<string> titleParts = new () {
-				Strings.fdOpen
-			};
-			if (MustExist) {
-				titleParts.Add (Strings.fdExisting);
-			}
-			switch (OpenMode) {
-			case OpenMode.File:
-				titleParts.Add (Strings.fdFile);
-				break;
-			case OpenMode.Directory:
-				titleParts.Add (Strings.fdDirectory);
-				break;
-			}
-			return string.Join (' ', titleParts);
+		switch (OpenMode) {
+		case OpenMode.File:
+			titleParts.Add (Strings.fdFile);
+			break;
+		case OpenMode.Directory:
+			titleParts.Add (Strings.fdDirectory);
+			break;
 		}
+		return string.Join (' ', titleParts);
+	}
 
-		private void AllowedTypeMenuClicked (int idx)
-		{
+	void AllowedTypeMenuClicked (int idx)
+	{
 
-			var allow = AllowedTypes [idx];
-			for (int i = 0; i < AllowedTypes.Count; i++) {
-				allowedTypeMenuItems [i].Checked = i == idx;
-			}
-			allowedTypeMenu.Title = allow.ToString ();
+		var allow = AllowedTypes [idx];
+		for (var i = 0; i < AllowedTypes.Count; i++) {
+			allowedTypeMenuItems [i].Checked = i == idx;
+		}
+		allowedTypeMenu.Title = allow.ToString ();
 
-			this.CurrentFilter = allow;
+		CurrentFilter = allow;
 
-			this.tbPath.ClearAllSelection ();
-			this.tbPath.Autocomplete.ClearSuggestions ();
+		tbPath.ClearAllSelection ();
+		tbPath.Autocomplete.ClearSuggestions ();
 
-			if (this.State != null) {
-				this.State.RefreshChildren ();
-				this.WriteStateToTableView ();
-			}
+		if (State != null) {
+			State.RefreshChildren ();
+			WriteStateToTableView ();
 		}
+	}
 
-		private void SuppressIfBadChar (Key k)
-		{
-			// don't let user type bad letters
-			var ch = (char)k;
+	void SuppressIfBadChar (Key k)
+	{
+		// don't let user type bad letters
+		var ch = (char)k;
 
-			if (badChars.Contains (ch)) {
-				k.Handled = true;
-			}
+		if (badChars.Contains (ch)) {
+			k.Handled = true;
 		}
+	}
 
-		private bool TreeView_KeyDown (Key keyEvent)
-		{
-			if (this.treeView.HasFocus && Separators.Contains ((char)keyEvent)) {
-				this.tbPath.FocusFirst ();
+	bool TreeView_KeyDown (Key keyEvent)
+	{
+		if (treeView.HasFocus && Separators.Contains ((char)keyEvent)) {
+			tbPath.FocusFirst ();
 
-				// let that keystroke go through on the tbPath instead
-				return true;
-			}
-
-			return false;
+			// let that keystroke go through on the tbPath instead
+			return true;
 		}
 
-		private void AcceptIf (Key keyEvent, KeyCode isKey)
-		{
-			if (!keyEvent.Handled && keyEvent.KeyCode == isKey) {
-				keyEvent.Handled = true;
+		return false;
+	}
 
-				// User hit Enter in text box so probably wants the
-				// contents of the text box as their selection not
-				// whatever lingering selection is in TableView
-				this.Accept (false);
-			}
+	void AcceptIf (Key keyEvent, KeyCode isKey)
+	{
+		if (!keyEvent.Handled && keyEvent.KeyCode == isKey) {
+			keyEvent.Handled = true;
+
+			// User hit Enter in text box so probably wants the
+			// contents of the text box as their selection not
+			// whatever lingering selection is in TableView
+			Accept (false);
 		}
+	}
 
-		private void Accept (IEnumerable<FileSystemInfoStats> toMultiAccept)
-		{
-			if (!this.AllowsMultipleSelection) {
-				return;
-			}
+	void Accept (IEnumerable<FileSystemInfoStats> toMultiAccept)
+	{
+		if (!AllowsMultipleSelection) {
+			return;
+		}
 
-			// Don't include ".." (IsParent) in multiselections
-			this.MultiSelected = toMultiAccept
+		// Don't include ".." (IsParent) in multiselections
+		MultiSelected = toMultiAccept
 				.Where (s => !s.IsParent)
 				.Select (s => s.FileSystemInfo.FullName)
 				.ToList ().AsReadOnly ();
 
-			this.Path = this.MultiSelected.Count == 1 ? this.MultiSelected [0] : string.Empty;
+		Path = MultiSelected.Count == 1 ? MultiSelected [0] : string.Empty;
+
+		FinishAccept ();
+	}
 
-			FinishAccept ();
+	void Accept (IFileInfo f)
+	{
+		if (!IsCompatibleWithOpenMode (f.FullName, out var reason)) {
+			feedback = reason;
+			SetNeedsDisplay ();
+			return;
 		}
 
-		private void Accept (IFileInfo f)
-		{
-			if (!this.IsCompatibleWithOpenMode (f.FullName, out var reason)) {
+		Path = f.FullName;
+
+		if (AllowsMultipleSelection) {
+			MultiSelected = new List<string> { f.FullName }.AsReadOnly ();
+		}
+
+		FinishAccept ();
+	}
+
+	void Accept (bool allowMulti)
+	{
+		if (allowMulti && TryAcceptMulti ()) {
+			return;
+		}
+
+		if (!IsCompatibleWithOpenMode (tbPath.Text, out var reason)) {
+			if (reason != null) {
 				feedback = reason;
 				SetNeedsDisplay ();
-				return;
 			}
+			return;
+		}
 
-			this.Path = f.FullName;
+		FinishAccept ();
+	}
 
-			if (AllowsMultipleSelection) {
-				this.MultiSelected = new List<string> { f.FullName }.AsReadOnly ();
-			}
+	void FinishAccept ()
+	{
+		var e = new FilesSelectedEventArgs (this);
 
-			FinishAccept ();
-		}
+		FilesSelected?.Invoke (this, e);
 
-		private void Accept (bool allowMulti)
-		{
-			if (allowMulti && TryAcceptMulti ()) {
-				return;
-			}
+		if (e.Cancel) {
+			return;
+		}
 
-			if (!this.IsCompatibleWithOpenMode (this.tbPath.Text, out string reason)) {
-				if (reason != null) {
-					feedback = reason;
-					SetNeedsDisplay ();
-				}
-				return;
-			}
+		// if user uses Path selection mode (e.g. Enter in text box)
+		// then also copy to MultiSelected
+		if (AllowsMultipleSelection && !MultiSelected.Any ()) {
 
-			FinishAccept ();
+			MultiSelected = string.IsNullOrWhiteSpace (Path) ?
+				Enumerable.Empty<string> ().ToList ().AsReadOnly () :
+				new List<string> { Path }.AsReadOnly ();
 		}
 
-		private void FinishAccept ()
-		{
-			var e = new FilesSelectedEventArgs (this);
+		Canceled = false;
+		Application.RequestStop ();
+	}
 
-			this.FilesSelected?.Invoke (this, e);
+	bool NavigateIf (Key keyEvent, KeyCode isKey, View to)
+	{
+		if (keyEvent.KeyCode == isKey) {
 
-			if (e.Cancel) {
-				return;
+			to.FocusFirst ();
+			if (to == tbPath) {
+				tbPath.MoveEnd ();
 			}
+			return true;
+		}
 
-			// if user uses Path selection mode (e.g. Enter in text box)
-			// then also copy to MultiSelected
-			if (AllowsMultipleSelection && (!MultiSelected.Any ())) {
-
-				MultiSelected = string.IsNullOrWhiteSpace (Path) ?
-						Enumerable.Empty<string> ().ToList ().AsReadOnly () :
-						new List<string> () { Path }.AsReadOnly ();
-			}
+		return false;
+	}
 
-			this.Canceled = false;
-			Application.RequestStop ();
+	void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
+	{
+		if (e.NewValue == null) {
+			return;
 		}
 
-		private bool NavigateIf (Key keyEvent, KeyCode isKey, View to)
-		{
-			if (keyEvent.KeyCode == isKey) {
+		Path = e.NewValue.FullName;
+	}
 
-				to.FocusFirst ();
-				if (to == tbPath) {
-					tbPath.MoveEnd ();
-				}
-				return true;
-			}
+	void UpdateNavigationVisibility ()
+	{
+		btnBack.Visible = history.CanBack ();
+		btnForward.Visible = history.CanForward ();
+		btnUp.Visible = history.CanUp ();
+	}
 
-			return false;
+	void TableView_SelectedCellChanged (object sender, SelectedCellChangedEventArgs obj)
+	{
+		if (!tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows == 0) {
+			return;
 		}
 
-		private void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
-		{
-			if (e.NewValue == null) {
-				return;
-			}
-
-			this.Path = e.NewValue.FullName;
+		if (tableView.MultiSelect && tableView.MultiSelectedRegions.Any ()) {
+			return;
 		}
 
-		private void UpdateNavigationVisibility ()
-		{
-			this.btnBack.Visible = this.history.CanBack ();
-			this.btnForward.Visible = this.history.CanForward ();
-			this.btnUp.Visible = this.history.CanUp ();
-		}
+		var stats = RowToStats (obj.NewRow);
 
-		private void TableView_SelectedCellChanged (object sender, SelectedCellChangedEventArgs obj)
-		{
-			if (!this.tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows == 0) {
-				return;
-			}
+		if (stats == null) {
+			return;
+		}
+		IFileSystemInfo dest;
 
-			if (this.tableView.MultiSelect && this.tableView.MultiSelectedRegions.Any ()) {
-				return;
-			}
+		if (stats.IsParent) {
+			dest = State.Directory;
+		} else {
+			dest = stats.FileSystemInfo;
+		}
 
-			var stats = this.RowToStats (obj.NewRow);
+		try {
+			pushingState = true;
 
-			if (stats == null) {
-				return;
-			}
-			IFileSystemInfo dest;
+			Path = dest.FullName;
+			State.Selected = stats;
+			tbPath.Autocomplete.ClearSuggestions ();
 
-			if (stats.IsParent) {
-				dest = State.Directory;
-			} else {
-				dest = stats.FileSystemInfo;
-			}
+		} finally {
 
-			try {
-				this.pushingState = true;
+			pushingState = false;
+		}
+	}
 
-				this.Path = dest.FullName;
-				this.State.Selected = stats;
-				this.tbPath.Autocomplete.ClearSuggestions ();
+	bool TableView_KeyUp (Key keyEvent)
+	{
+		if (keyEvent.KeyCode == KeyCode.Backspace) {
+			return history.Back ();
+		}
+		if (keyEvent.KeyCode == (KeyCode.ShiftMask | KeyCode.Backspace)) {
+			return history.Forward ();
+		}
 
-			} finally {
+		if (keyEvent.KeyCode == KeyCode.Delete) {
 
-				this.pushingState = false;
-			}
+			Delete ();
+			return true;
 		}
 
-		private bool TableView_KeyUp (Key keyEvent)
-		{
-			if (keyEvent.KeyCode == KeyCode.Backspace) {
-				return this.history.Back ();
-			}
-			if (keyEvent.KeyCode == (KeyCode.ShiftMask | KeyCode.Backspace)) {
-				return this.history.Forward ();
-			}
+		if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.R)) {
 
-			if (keyEvent.KeyCode == KeyCode.Delete) {
+			Rename ();
+			return true;
+		}
 
-				Delete ();
-				return true;
-			}
+		if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.N)) {
+			New ();
+			return true;
+		}
 
-			if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.R)) {
+		return false;
+	}
 
-				Rename ();
-				return true;
-			}
+	void CellActivate (object sender, CellActivatedEventArgs obj)
+	{
+		if (TryAcceptMulti ()) {
+			return;
+		}
 
-			if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.N)) {
-				New ();
-				return true;
-			}
+		var stats = RowToStats (obj.Row);
 
-			return false;
+		if (stats.FileSystemInfo is IDirectoryInfo d) {
+			PushState (d, true);
+			return;
 		}
 
-		private void CellActivate (object sender, CellActivatedEventArgs obj)
-		{
-			if (TryAcceptMulti ()) {
-				return;
-			}
-
-			var stats = this.RowToStats (obj.Row);
+		if (stats.FileSystemInfo is IFileInfo f) {
+			Accept (f);
+		}
+	}
 
-			if (stats.FileSystemInfo is IDirectoryInfo d) {
-				this.PushState (d, true);
-				return;
-			}
+	bool TryAcceptMulti ()
+	{
+		var multi = MultiRowToStats ();
+		string reason = null;
 
-			if (stats.FileSystemInfo is IFileInfo f) {
-				this.Accept (f);
-			}
+		if (!multi.Any ()) {
+			return false;
 		}
 
-		private bool TryAcceptMulti ()
-		{
-			var multi = this.MultiRowToStats ();
-			string reason = null;
-
-			if (!multi.Any ()) {
-				return false;
-			}
+		if (multi.All (m => IsCompatibleWithOpenMode (
+			m.FileSystemInfo.FullName, out reason))) {
+			Accept (multi);
+			return true;
+		}
+		if (reason != null) {
+			feedback = reason;
+			SetNeedsDisplay ();
+		}
 
-			if (multi.All (m => this.IsCompatibleWithOpenMode (
-				m.FileSystemInfo.FullName, out reason))) {
-				this.Accept (multi);
-				return true;
-			} else {
-				if (reason != null) {
-					feedback = reason;
-					SetNeedsDisplay ();
-				}
+		return false;
+	}
 
-				return false;
-			}
+	/// <summary>
+	/// Returns true if there are no <see cref="AllowedTypes"/> or one of them agrees
+	/// that <paramref name="file"/> <see cref="IAllowedType.IsAllowed(string)"/>.
+	/// </summary>
+	/// <param name="file"></param>
+	/// <returns></returns>
+	public bool IsCompatibleWithAllowedExtensions (IFileInfo file)
+	{
+		// no restrictions
+		if (!AllowedTypes.Any ()) {
+			return true;
 		}
+		return MatchesAllowedTypes (file);
+	}
 
-		/// <summary>
-		/// Returns true if there are no <see cref="AllowedTypes"/> or one of them agrees
-		/// that <paramref name="file"/> <see cref="IAllowedType.IsAllowed(string)"/>.
-		/// </summary>
-		/// <param name="file"></param>
-		/// <returns></returns>
-		public bool IsCompatibleWithAllowedExtensions (IFileInfo file)
-		{
-			// no restrictions
-			if (!this.AllowedTypes.Any ()) {
-				return true;
-			}
-			return this.MatchesAllowedTypes (file);
+	bool IsCompatibleWithAllowedExtensions (string path)
+	{
+		// no restrictions
+		if (!AllowedTypes.Any ()) {
+			return true;
 		}
 
-		private bool IsCompatibleWithAllowedExtensions (string path)
-		{
-			// no restrictions
-			if (!this.AllowedTypes.Any ()) {
-				return true;
-			}
+		return AllowedTypes.Any (t => t.IsAllowed (path));
+	}
 
-			return this.AllowedTypes.Any (t => t.IsAllowed (path));
+	/// <summary>
+	/// Returns true if any <see cref="AllowedTypes"/> matches <paramref name="file"/>.
+	/// </summary>
+	/// <param name="file"></param>
+	/// <returns></returns>
+	bool MatchesAllowedTypes (IFileInfo file) => AllowedTypes.Any (t => t.IsAllowed (file.FullName));
+
+	bool IsCompatibleWithOpenMode (string s, out string reason)
+	{
+		reason = null;
+		if (string.IsNullOrWhiteSpace (s)) {
+			return false;
 		}
 
-		/// <summary>
-		/// Returns true if any <see cref="AllowedTypes"/> matches <paramref name="file"/>.
-		/// </summary>
-		/// <param name="file"></param>
-		/// <returns></returns>
-		private bool MatchesAllowedTypes (IFileInfo file)
-		{
-			return this.AllowedTypes.Any (t => t.IsAllowed (file.FullName));
+		if (!IsCompatibleWithAllowedExtensions (s)) {
+			reason = Style.WrongFileTypeFeedback;
+			return false;
 		}
-		private bool IsCompatibleWithOpenMode (string s, out string reason)
-		{
-			reason = null;
-			if (string.IsNullOrWhiteSpace (s)) {
+
+		switch (OpenMode) {
+		case OpenMode.Directory:
+			if (MustExist && !Directory.Exists (s)) {
+				reason = Style.DirectoryMustExistFeedback;
 				return false;
 			}
 
-			if (!this.IsCompatibleWithAllowedExtensions (s)) {
-				reason = Style.WrongFileTypeFeedback;
+			if (File.Exists (s)) {
+				reason = Style.FileAlreadyExistsFeedback;
 				return false;
 			}
+			return true;
+		case OpenMode.File:
 
-			switch (this.OpenMode) {
-			case OpenMode.Directory:
-				if (MustExist && !Directory.Exists (s)) {
-					reason = Style.DirectoryMustExistFeedback;
-					return false;
-				}
-
-				if (File.Exists (s)) {
-					reason = Style.FileAlreadyExistsFeedback;
-					return false;
-				}
-				return true;
-			case OpenMode.File:
-
-				if (MustExist && !File.Exists (s)) {
-					reason = Style.FileMustExistFeedback;
-					return false;
-				}
-				if (Directory.Exists (s)) {
-					reason = Style.DirectoryAlreadyExistsFeedback;
-					return false;
-				}
-				return true;
-			case OpenMode.Mixed:
-				if (MustExist && !File.Exists (s) && !Directory.Exists (s)) {
-					reason = Style.FileOrDirectoryMustExistFeedback;
-					return false;
-				}
-				return true;
-			default: throw new ArgumentOutOfRangeException (nameof (this.OpenMode));
+			if (MustExist && !File.Exists (s)) {
+				reason = Style.FileMustExistFeedback;
+				return false;
 			}
-		}
-
-		/// <summary>
-		/// Changes the dialog such that <paramref name="d"/> is being explored.
-		/// </summary>
-		/// <param name="d"></param>
-		/// <param name="addCurrentStateToHistory"></param>
-		/// <param name="setPathText"></param>
-		/// <param name="clearForward"></param>
-		/// <param name="pathText">Optional alternate string to set path to.</param>
-		internal void PushState (IDirectoryInfo d, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
-		{
-			// no change of state
-			if (d == this.State?.Directory) {
-				return;
+			if (Directory.Exists (s)) {
+				reason = Style.DirectoryAlreadyExistsFeedback;
+				return false;
 			}
-			if (d.FullName == this.State?.Directory.FullName) {
-				return;
+			return true;
+		case OpenMode.Mixed:
+			if (MustExist && !File.Exists (s) && !Directory.Exists (s)) {
+				reason = Style.FileOrDirectoryMustExistFeedback;
+				return false;
 			}
-
-			PushState (new FileDialogState (d, this), addCurrentStateToHistory, setPathText, clearForward, pathText);
+			return true;
+		default: throw new ArgumentOutOfRangeException (nameof (OpenMode));
 		}
+	}
 
-		private void RefreshState ()
-		{
-			State.RefreshChildren ();
-			PushState (State, false, false, false);
+	/// <summary>
+	/// Changes the dialog such that <paramref name="d"/> is being explored.
+	/// </summary>
+	/// <param name="d"></param>
+	/// <param name="addCurrentStateToHistory"></param>
+	/// <param name="setPathText"></param>
+	/// <param name="clearForward"></param>
+	/// <param name="pathText">Optional alternate string to set path to.</param>
+	internal void PushState (IDirectoryInfo d, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
+	{
+		// no change of state
+		if (d == State?.Directory) {
+			return;
+		}
+		if (d.FullName == State?.Directory.FullName) {
+			return;
 		}
 
-		private void PushState (FileDialogState newState, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
-		{
-			if (State is SearchState search) {
-				search.Cancel ();
-			}
+		PushState (new FileDialogState (d, this), addCurrentStateToHistory, setPathText, clearForward, pathText);
+	}
 
-			try {
-				this.pushingState = true;
+	void RefreshState ()
+	{
+		State.RefreshChildren ();
+		PushState (State, false, false, false);
+	}
 
-				// push the old state to history
-				if (addCurrentStateToHistory) {
-					this.history.Push (this.State, clearForward);
-				}
+	void PushState (FileDialogState newState, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
+	{
+		if (State is SearchState search) {
+			search.Cancel ();
+		}
 
-				this.tbPath.Autocomplete.ClearSuggestions ();
+		try {
+			pushingState = true;
 
-				if (pathText != null) {
-					this.Path = pathText;
-				} else
-				if (setPathText) {
-					this.Path = newState.Directory.FullName;
-				}
+			// push the old state to history
+			if (addCurrentStateToHistory) {
+				history.Push (State, clearForward);
+			}
 
-				this.State = newState;
-				this.tbPath.Autocomplete.GenerateSuggestions (
-					new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, this.State));
+			tbPath.Autocomplete.ClearSuggestions ();
 
-				this.WriteStateToTableView ();
+			if (pathText != null) {
+				Path = pathText;
+			} else if (setPathText) {
+				Path = newState.Directory.FullName;
+			}
 
-				if (clearForward) {
-					this.history.ClearForward ();
-				}
+			State = newState;
+			tbPath.Autocomplete.GenerateSuggestions (
+				new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State));
 
-				this.tableView.RowOffset = 0;
-				this.tableView.SelectedRow = 0;
+			WriteStateToTableView ();
 
-				this.SetNeedsDisplay ();
-				this.UpdateNavigationVisibility ();
+			if (clearForward) {
+				history.ClearForward ();
+			}
 
-			} finally {
+			tableView.RowOffset = 0;
+			tableView.SelectedRow = 0;
 
-				this.pushingState = false;
-			}
-			ClearFeedback ();
-		}
+			SetNeedsDisplay ();
+			UpdateNavigationVisibility ();
 
-		private void WriteStateToTableView ()
-		{
-			if (this.State == null) {
-				return;
-			}
-			this.tableView.Table = new FileDialogTableSource (this, this.State, this.Style, currentSortColumn, currentSortIsAsc);
+		} finally {
 
-			this.ApplySort ();
-			this.tableView.Update ();
+			pushingState = false;
 		}
+		ClearFeedback ();
+	}
 
-		private ColorScheme ColorGetter (CellColorGetterArgs args)
-		{
-			var stats = this.RowToStats (args.RowIndex);
-
-			if (!Style.UseColors) {
-				return tableView.ColorScheme;
-			}
+	void WriteStateToTableView ()
+	{
+		if (State == null) {
+			return;
+		}
+		tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc);
 
+		ApplySort ();
+		tableView.Update ();
+	}
 
-			var color = Style.ColorProvider.GetColor (stats.FileSystemInfo) ?? new Color (Color.White);
-			var black = new Color (Color.Black);
+	ColorScheme ColorGetter (CellColorGetterArgs args)
+	{
+		var stats = RowToStats (args.RowIndex);
 
-			// TODO: Add some kind of cache for this
-			return new ColorScheme {
-				Normal = new Attribute (color, black),
-				HotNormal = new Attribute (color, black),
-				Focus = new Attribute (black, color),
-				HotFocus = new Attribute (black, color),
-			};
+		if (!Style.UseColors) {
+			return tableView.ColorScheme;
 		}
 
-		/// <summary>
-		/// If <see cref="TableView.MultiSelect"/> is this returns a union of all
-		/// <see cref="FileSystemInfoStats"/> in the selection.
-		/// </summary>
-		/// <returns></returns>
-		private IEnumerable<FileSystemInfoStats> MultiRowToStats ()
-		{
-			var toReturn = new HashSet<FileSystemInfoStats> ();
-
-			if (this.AllowsMultipleSelection && this.tableView.MultiSelectedRegions.Any ()) {
 
-				foreach (var p in this.tableView.GetAllSelectedCells ()) {
+		var color = Style.ColorProvider.GetColor (stats.FileSystemInfo) ?? new Color (Color.White);
+		var black = new Color (Color.Black);
 
-					var add = this.State?.Children [p.Y];
-					if (add != null) {
-						toReturn.Add (add);
-					}
-				}
-			}
+		// TODO: Add some kind of cache for this
+		return new ColorScheme {
+			Normal = new Attribute (color, black),
+			HotNormal = new Attribute (color, black),
+			Focus = new Attribute (black, color),
+			HotFocus = new Attribute (black, color)
+		};
+	}
 
-			return toReturn;
-		}
-		private FileSystemInfoStats RowToStats (int rowIndex)
-		{
-			return this.State?.Children [rowIndex];
-		}
+	/// <summary>
+	/// If <see cref="TableView.MultiSelect"/> is this returns a union of all
+	/// <see cref="FileSystemInfoStats"/> in the selection.
+	/// </summary>
+	/// <returns></returns>
+	IEnumerable<FileSystemInfoStats> MultiRowToStats ()
+	{
+		var toReturn = new HashSet<FileSystemInfoStats> ();
 
-		private void PathChanged ()
-		{
-			// avoid re-entry
-			if (this.pushingState) {
-				return;
-			}
+		if (AllowsMultipleSelection && tableView.MultiSelectedRegions.Any ()) {
 
-			var path = this.tbPath.Text;
+			foreach (var p in tableView.GetAllSelectedCells ()) {
 
-			if (string.IsNullOrWhiteSpace (path)) {
-				return;
+				var add = State?.Children [p.Y];
+				if (add != null) {
+					toReturn.Add (add);
+				}
 			}
+		}
 
-			var dir = this.StringToDirectoryInfo (path);
+		return toReturn;
+	}
 
-			if (dir.Exists) {
-				this.PushState (dir, true, false);
-			} else
-			if (dir.Parent?.Exists ?? false) {
-				this.PushState (dir.Parent, true, false);
-			}
+	FileSystemInfoStats RowToStats (int rowIndex) => State?.Children [rowIndex];
 
-			tbPath.Autocomplete.GenerateSuggestions (new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State));
+	void PathChanged ()
+	{
+		// avoid re-entry
+		if (pushingState) {
+			return;
 		}
 
-		private IDirectoryInfo StringToDirectoryInfo (string path)
-		{
-			// if you pass new DirectoryInfo("C:") you get a weird object
-			// where the FullName is in fact the current working directory.
-			// really not what most users would expect
-			if (Regex.IsMatch (path, "^\\w:$")) {
-				return fileSystem.DirectoryInfo.New (path + System.IO.Path.DirectorySeparatorChar);
-			}
+		var path = tbPath.Text;
 
-			return fileSystem.DirectoryInfo.New (path);
+		if (string.IsNullOrWhiteSpace (path)) {
+			return;
 		}
 
-		/// <summary>
-		/// Select <paramref name="toRestore"/> in the table view (if present)
-		/// </summary>
-		/// <param name="toRestore"></param>
-		internal void RestoreSelection (IFileSystemInfo toRestore)
-		{
-			tableView.SelectedRow = State.Children.IndexOf (r => r.FileSystemInfo == toRestore);
-			tableView.EnsureSelectedCellIsVisible ();
+		var dir = StringToDirectoryInfo (path);
+
+		if (dir.Exists) {
+			PushState (dir, true, false);
+		} else if (dir.Parent?.Exists ?? false) {
+			PushState (dir.Parent, true, false);
 		}
 
-		internal void ApplySort ()
-		{
-			var stats = State?.Children ?? new FileSystemInfoStats [0];
+		tbPath.Autocomplete.GenerateSuggestions (new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State));
+	}
 
-			// This portion is never reordered (aways .. at top then folders)
-			var forcedOrder = stats
-			.OrderByDescending (f => f.IsParent)
-					.ThenBy (f => f.IsDir ? -1 : 100);
+	IDirectoryInfo StringToDirectoryInfo (string path)
+	{
+		// if you pass new DirectoryInfo("C:") you get a weird object
+		// where the FullName is in fact the current working directory.
+		// really not what most users would expect
+		if (Regex.IsMatch (path, "^\\w:$")) {
+			return fileSystem.DirectoryInfo.New (path + System.IO.Path.DirectorySeparatorChar);
+		}
 
-			// This portion is flexible based on the column clicked (e.g. alphabetical)
-			var ordered =
-				this.currentSortIsAsc ?
-					forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)) :
-					forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f));
+		return fileSystem.DirectoryInfo.New (path);
+	}
 
-			State.Children = ordered.ToArray ();
+	/// <summary>
+	/// Select <paramref name="toRestore"/> in the table view (if present)
+	/// </summary>
+	/// <param name="toRestore"></param>
+	internal void RestoreSelection (IFileSystemInfo toRestore)
+	{
+		tableView.SelectedRow = State.Children.IndexOf (r => r.FileSystemInfo == toRestore);
+		tableView.EnsureSelectedCellIsVisible ();
+	}
 
-			this.tableView.Update ();
-		}
+	internal void ApplySort ()
+	{
+		var stats = State?.Children ?? new FileSystemInfoStats [0];
 
-		private void SortColumn (int clickedCol)
-		{
-			this.GetProposedNewSortOrder (clickedCol, out var isAsc);
-			this.SortColumn (clickedCol, isAsc);
-			this.tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc);
-		}
+		// This portion is never reordered (aways .. at top then folders)
+		var forcedOrder = stats
+				  .OrderByDescending (f => f.IsParent)
+				  .ThenBy (f => f.IsDir ? -1 : 100);
 
-		internal void SortColumn (int col, bool isAsc)
-		{
-			// set a sort order
-			this.currentSortColumn = col;
-			this.currentSortIsAsc = isAsc;
+		// This portion is flexible based on the column clicked (e.g. alphabetical)
+		var ordered =
+			currentSortIsAsc ?
+				forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)) :
+				forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f));
 
-			this.ApplySort ();
-		}
+		State.Children = ordered.ToArray ();
 
-		private string GetProposedNewSortOrder (int clickedCol, out bool isAsc)
-		{
-			// work out new sort order
-			if (this.currentSortColumn == clickedCol && this.currentSortIsAsc) {
-				isAsc = false;
-				return string.Format (Strings.fdCtxSortDesc, tableView.Table.ColumnNames [clickedCol]);
-			} else {
-				isAsc = true;
-				return string.Format (Strings.fdCtxSortAsc, tableView.Table.ColumnNames [clickedCol]);
-			}
-		}
+		tableView.Update ();
+	}
 
-		private void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e)
-		{
-			var sort = this.GetProposedNewSortOrder (clickedCol, out var isAsc);
+	void SortColumn (int clickedCol)
+	{
+		GetProposedNewSortOrder (clickedCol, out var isAsc);
+		SortColumn (clickedCol, isAsc);
+		tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc);
+	}
 
-			var contextMenu = new ContextMenu (
-				e.MouseEvent.X + 1,
-				e.MouseEvent.Y + 1,
-				new MenuBarItem (new MenuItem []
-				{
-					new MenuItem(string.Format (Strings.fdCtxHide, StripArrows (tableView.Table.ColumnNames[clickedCol])), string.Empty, () => this.HideColumn (clickedCol)),
-					new MenuItem(StripArrows (sort), string.Empty, () => this.SortColumn (clickedCol, isAsc)),
-				})
-			);
+	internal void SortColumn (int col, bool isAsc)
+	{
+		// set a sort order
+		currentSortColumn = col;
+		currentSortIsAsc = isAsc;
 
-			contextMenu.Show ();
-		}
+		ApplySort ();
+	}
 
-		private static string StripArrows (string columnName)
-		{
-			return columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty);
+	string GetProposedNewSortOrder (int clickedCol, out bool isAsc)
+	{
+		// work out new sort order
+		if (currentSortColumn == clickedCol && currentSortIsAsc) {
+			isAsc = false;
+			return string.Format (Strings.fdCtxSortDesc, tableView.Table.ColumnNames [clickedCol]);
 		}
+		isAsc = true;
+		return string.Format (Strings.fdCtxSortAsc, tableView.Table.ColumnNames [clickedCol]);
+	}
 
-		private void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e)
-		{
-			if (clickedCell == null) {
-				return;
-			}
+	void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e)
+	{
+		var sort = GetProposedNewSortOrder (clickedCol, out var isAsc);
 
-			var contextMenu = new ContextMenu (
-				e.MouseEvent.X + 1,
-				e.MouseEvent.Y + 1,
-				new MenuBarItem (new MenuItem []
-				{
-					new MenuItem(Strings.fdCtxNew, string.Empty, New),
-					new MenuItem(Strings.fdCtxRename, string.Empty, Rename),
-					new MenuItem(Strings.fdCtxDelete,string.Empty, Delete),
-				})
-			);
+		var contextMenu = new ContextMenu (
+			e.MouseEvent.X + 1,
+			e.MouseEvent.Y + 1,
+			new MenuBarItem (new MenuItem [] {
+				new (string.Format (Strings.fdCtxHide, StripArrows (tableView.Table.ColumnNames [clickedCol])), string.Empty, () => HideColumn (clickedCol)),
+				new (StripArrows (sort), string.Empty, () => SortColumn (clickedCol, isAsc))
+			})
+		);
 
-			tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false);
+		contextMenu.Show ();
+	}
 
-			contextMenu.Show ();
-		}
+	static string StripArrows (string columnName) => columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty);
 
-		private void HideColumn (int clickedCol)
-		{
-			var style = this.tableView.Style.GetOrCreateColumnStyle (clickedCol);
-			style.Visible = false;
-			this.tableView.Update ();
+	void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e)
+	{
+		if (clickedCell == null) {
+			return;
 		}
 
-		/// <summary>
-		/// State representing a recursive search from <see cref="FileDialogState.Directory"/>
-		/// downwards.
-		/// </summary>
-		internal class SearchState : FileDialogState {
+		var contextMenu = new ContextMenu (
+			e.MouseEvent.X + 1,
+			e.MouseEvent.Y + 1,
+			new MenuBarItem (new MenuItem [] {
+				new (Strings.fdCtxNew, string.Empty, New),
+				new (Strings.fdCtxRename, string.Empty, Rename),
+				new (Strings.fdCtxDelete, string.Empty, Delete)
+			})
+		);
 
-			bool cancel = false;
-			bool finished = false;
+		tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false);
 
-			// TODO: Add thread safe child adding
-			List<FileSystemInfoStats> found = new List<FileSystemInfoStats> ();
-			object oLockFound = new object ();
-			CancellationTokenSource token = new CancellationTokenSource ();
+		contextMenu.Show ();
+	}
 
-			public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : base (dir, parent)
-			{
-				parent.SearchMatcher.Initialize (searchTerms);
-				Children = new FileSystemInfoStats [0];
-				BeginSearch ();
-			}
+	void HideColumn (int clickedCol)
+	{
+		var style = tableView.Style.GetOrCreateColumnStyle (clickedCol);
+		style.Visible = false;
+		tableView.Update ();
+	}
 
-			private void BeginSearch ()
-			{
-				Task.Run (() => {
-					RecursiveFind (Directory);
-					finished = true;
-				});
+	/// <summary>
+	/// State representing a recursive search from <see cref="FileDialogState.Directory"/>
+	/// downwards.
+	/// </summary>
+	internal class SearchState : FileDialogState {
+		bool cancel;
+		bool finished;
 
-				Task.Run (() => {
-					UpdateChildren ();
-				});
-			}
+		// TODO: Add thread safe child adding
+		readonly List<FileSystemInfoStats> found = new ();
+		readonly object oLockFound = new ();
+		readonly CancellationTokenSource token = new ();
 
-			private void UpdateChildren ()
-			{
-				lock (Parent.onlyOneSearchLock) {
-					while (!cancel && !finished) {
+		public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : base (dir, parent)
+		{
+			parent.SearchMatcher.Initialize (searchTerms);
+			Children = new FileSystemInfoStats [0];
+			BeginSearch ();
+		}
 
-						try {
-							Task.Delay (250).Wait (token.Token);
-						} catch (OperationCanceledException) {
-							cancel = true;
-						}
+		void BeginSearch ()
+		{
+			Task.Run (() => {
+				RecursiveFind (Directory);
+				finished = true;
+			});
+
+			Task.Run (() => {
+				UpdateChildren ();
+			});
+		}
 
-						if (cancel || finished) {
-							break;
-						}
+		void UpdateChildren ()
+		{
+			lock (Parent.onlyOneSearchLock) {
+				while (!cancel && !finished) {
 
-						UpdateChildrenToFound ();
+					try {
+						Task.Delay (250).Wait (token.Token);
+					} catch (OperationCanceledException) {
+						cancel = true;
 					}
 
-					if (finished && !cancel) {
-						UpdateChildrenToFound ();
+					if (cancel || finished) {
+						break;
 					}
 
-					Application.Invoke (() => {
-						Parent.spinnerView.Visible = false;
-					});
+					UpdateChildrenToFound ();
 				}
-			}
 
-			private void UpdateChildrenToFound ()
-			{
-				lock (oLockFound) {
-					Children = found.ToArray ();
+				if (finished && !cancel) {
+					UpdateChildrenToFound ();
 				}
 
 				Application.Invoke (() => {
-					Parent.tbPath.Autocomplete.GenerateSuggestions (
-						new AutocompleteFilepathContext (Parent.tbPath.Text, Parent.tbPath.CursorPosition, this)
-						);
-					Parent.WriteStateToTableView ();
-
-					Parent.spinnerView.Visible = true;
-					Parent.spinnerView.SetNeedsDisplay ();
+					Parent.spinnerView.Visible = false;
 				});
 			}
+		}
+
+		void UpdateChildrenToFound ()
+		{
+			lock (oLockFound) {
+				Children = found.ToArray ();
+			}
 
-			private void RecursiveFind (IDirectoryInfo directory)
-			{
-				foreach (var f in GetChildren (directory)) {
+			Application.Invoke (() => {
+				Parent.tbPath.Autocomplete.GenerateSuggestions (
+					new AutocompleteFilepathContext (Parent.tbPath.Text, Parent.tbPath.CursorPosition, this)
+				);
+				Parent.WriteStateToTableView ();
 
-					if (cancel) {
-						return;
-					}
+				Parent.spinnerView.Visible = true;
+				Parent.spinnerView.SetNeedsDisplay ();
+			});
+		}
 
-					if (f.IsParent) {
-						continue;
-					}
+		void RecursiveFind (IDirectoryInfo directory)
+		{
+			foreach (var f in GetChildren (directory)) {
 
-					lock (oLockFound) {
-						if (found.Count >= FileDialog.MaxSearchResults) {
-							finished = true;
-							return;
-						}
-					}
+				if (cancel) {
+					return;
+				}
+
+				if (f.IsParent) {
+					continue;
+				}
 
-					if (Parent.SearchMatcher.IsMatch (f.FileSystemInfo)) {
-						lock (oLockFound) {
-							found.Add (f);
-						}
+				lock (oLockFound) {
+					if (found.Count >= MaxSearchResults) {
+						finished = true;
+						return;
 					}
+				}
 
-					if (f.FileSystemInfo is IDirectoryInfo sub) {
-						RecursiveFind (sub);
+				if (Parent.SearchMatcher.IsMatch (f.FileSystemInfo)) {
+					lock (oLockFound) {
+						found.Add (f);
 					}
 				}
-			}
 
-			internal override void RefreshChildren ()
-			{
+				if (f.FileSystemInfo is IDirectoryInfo sub) {
+					RecursiveFind (sub);
+				}
 			}
+		}
 
-			/// <summary>
-			/// Cancels the current search (if any).  Returns true if a search
-			/// was running and cancellation was successfully set.
-			/// </summary>
-			/// <returns></returns>
-			internal bool Cancel ()
-			{
-				var alreadyCancelled = token.IsCancellationRequested || cancel;
+		internal override void RefreshChildren () { }
+
+		/// <summary>
+		/// Cancels the current search (if any).  Returns true if a search
+		/// was running and cancellation was successfully set.
+		/// </summary>
+		/// <returns></returns>
+		internal bool Cancel ()
+		{
+			var alreadyCancelled = token.IsCancellationRequested || cancel;
 
-				cancel = true;
-				token.Cancel ();
+			cancel = true;
+			token.Cancel ();
 
-				return !alreadyCancelled;
-			}
+			return !alreadyCancelled;
 		}
-		internal class FileDialogCollectionNavigator : CollectionNavigatorBase {
-			private FileDialog fileDialog;
+	}
 
-			public FileDialogCollectionNavigator (FileDialog fileDialog)
-			{
-				this.fileDialog = fileDialog;
-			}
+	internal class FileDialogCollectionNavigator : CollectionNavigatorBase {
+		readonly FileDialog fileDialog;
 
-			protected override object ElementAt (int idx)
-			{
-				var val = FileDialogTableSource.GetRawColumnValue (fileDialog.tableView.SelectedColumn, fileDialog.State?.Children [idx]);
-				if (val == null) {
-					return string.Empty;
-				}
+		public FileDialogCollectionNavigator (FileDialog fileDialog) => this.fileDialog = fileDialog;
 
-				return val.ToString ().Trim ('.');
+		protected override object ElementAt (int idx)
+		{
+			var val = FileDialogTableSource.GetRawColumnValue (fileDialog.tableView.SelectedColumn, fileDialog.State?.Children [idx]);
+			if (val == null) {
+				return string.Empty;
 			}
 
-			protected override int GetCollectionLength ()
-			{
-				return fileDialog.State?.Children.Length ?? 0;
-			}
+			return val.ToString ().Trim ('.');
 		}
+
+		protected override int GetCollectionLength () => fileDialog.State?.Children.Length ?? 0;
 	}
 }

+ 5 - 3
Terminal.Gui/Views/FileSystemColorProvider.cs

@@ -14,7 +14,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="file"></param>
 		/// <returns></returns>
-		public Color GetColor (IFileSystemInfo file)
+		public Color? GetColor (IFileSystemInfo file)
 		{
 			if (FilenameToColor.ContainsKey (file.Name)) {
 				return FilenameToColor [file.Name];
@@ -443,8 +443,10 @@ namespace Terminal.Gui {
 
 		private static Color StringToColor (string str)
 		{
-			Color.TryParse (str, out var c);
-			return c ?? throw new System.Exception ("Failed to parse Color from " + str);
+			if (!Color.TryParse (str, out var c)) {
+				throw new System.Exception ("Failed to parse Color from " + str);
+			}
+			return c;
 		}
 	}
 }

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

@@ -50,7 +50,7 @@ namespace Terminal.Gui {
 		{
 			this.Title = title;
 			Border.Thickness = new Thickness (1);
-			Border.BorderStyle = DefaultBorderStyle;
+			Border.LineStyle = DefaultBorderStyle;
 			//Border.ColorScheme = ColorScheme;
 			Border.Data = "Border";
 		}

+ 30 - 37
Terminal.Gui/Views/GraphView/Annotations.cs

@@ -114,62 +114,55 @@ namespace Terminal.Gui {
 	/// A box containing symbol definitions e.g. meanings for colors in a graph.
 	/// The 'Key' to the graph
 	/// </summary>
-	public class LegendAnnotation : IAnnotation {
-
+	public class LegendAnnotation : View, IAnnotation {
 		/// <summary>
-		/// True to draw a solid border around the legend.
-		/// Defaults to true.  This border will be within the
-		/// <see cref="Bounds"/> and so reduces the width/height
-		/// available for text by 2
+		/// Returns false i.e. Legends render after series
 		/// </summary>
-		public bool Border { get; set; } = true;
+		public bool BeforeSeries => false;
 
 		/// <summary>
-		/// Defines the screen area available for the legend to render in
+		/// Ordered collection of entries that are rendered in the legend.
 		/// </summary>
-		public Rect Bounds { get; set; }
+		List<Tuple<GraphCellToRender, string>> _entries = new List<Tuple<GraphCellToRender, string>> ();
 
 		/// <summary>
-		/// Returns false i.e. Lengends render after series
+		/// Creates a new empty legend at the empty screen coordinates.
 		/// </summary>
-		public bool BeforeSeries => false;
+		public LegendAnnotation () : this (Rect.Empty) { }
 
 		/// <summary>
-		/// Ordered collection of entries that are rendered in the legend.
-		/// </summary>
-		List<Tuple<GraphCellToRender, string>> entries = new List<Tuple<GraphCellToRender, string>> ();
-
-		/// <summary>
-		/// Creates a new empty legend at the given screen coordinates
+		/// Creates a new empty legend at the given screen coordinates.
 		/// </summary>
 		/// <param name="legendBounds">Defines the area available for the legend to render in
 		/// (within the graph).  This is in screen units (i.e. not graph space)</param>
 		public LegendAnnotation (Rect legendBounds)
 		{
-			Bounds = legendBounds;
+			X = legendBounds.X;
+			Y = legendBounds.Y;
+			Width = legendBounds.Width;
+			Height = legendBounds.Height;
+			BorderStyle = LineStyle.Single;
 		}
 
 		/// <summary>
-		/// Draws the Legend and all entries into the area within <see cref="Bounds"/>
+		/// Draws the Legend and all entries into the area within <see cref="View.Bounds"/>
 		/// </summary>
 		/// <param name="graph"></param>
 		public void Render (GraphView graph)
 		{
-			if (Border) {
-				graph.Border.DrawFrame (Bounds, true);
+			if (!IsInitialized) {
+				ColorScheme = new ColorScheme () { Normal = Application.Driver.GetAttribute () };
+				graph.Add (this);
 			}
 
-			// start the legend at
-			int y = Bounds.Top + (Border ? 1 : 0);
-			int x = Bounds.Left + (Border ? 1 : 0);
-
-			// how much horizontal space is available for writing legend entries?
-			int availableWidth = Bounds.Width - (Border ? 2 : 0);
-			int availableHeight = Bounds.Height - (Border ? 2 : 0);
+			if (BorderStyle != LineStyle.None) {
+				OnDrawAdornments ();
+				OnRenderLineCanvas ();
+			}
 
 			int linesDrawn = 0;
 
-			foreach (var entry in entries) {
+			foreach (var entry in _entries) {
 
 				if (entry.Item1.Color.HasValue) {
 					Application.Driver.SetAttribute (entry.Item1.Color.Value);
@@ -178,35 +171,35 @@ namespace Terminal.Gui {
 				}
 
 				// add the symbol
-				graph.AddRune (x, y + linesDrawn, entry.Item1.Rune);
+				AddRune (0, linesDrawn, entry.Item1.Rune);
 
 				// switch to normal coloring (for the text)
 				graph.SetDriverColorToGraphColor ();
 
 				// add the text
-				graph.Move (x + 1, y + linesDrawn);
+				Move (1, linesDrawn);
 
-				string str = TextFormatter.ClipOrPad (entry.Item2, availableWidth - 1);
+				string str = TextFormatter.ClipOrPad (entry.Item2, Bounds.Width - 1);
 				Application.Driver.AddStr (str);
 
 				linesDrawn++;
-
+				
 				// Legend has run out of space
-				if (linesDrawn >= availableHeight) {
+				if (linesDrawn >= Bounds.Height) {
 					break;
 				}
 			}
 		}
 
 		/// <summary>
-		/// Adds an entry into the legend.  Duplicate entries are permissable
+		/// Adds an entry into the legend.  Duplicate entries are permissible
 		/// </summary>
 		/// <param name="graphCellToRender">The symbol appearing on the graph that should appear in the legend</param>
 		/// <param name="text">Text to render on this line of the legend.  Will be truncated
-		/// if outside of Legend <see cref="Bounds"/></param>
+		/// if outside of Legend <see cref="View.Bounds"/></param>
 		public void AddEntry (GraphCellToRender graphCellToRender, string text)
 		{
-			entries.Add (Tuple.Create (graphCellToRender, text));
+			_entries.Add (Tuple.Create (graphCellToRender, text));
 		}
 	}
 

+ 280 - 271
Terminal.Gui/Views/GraphView/GraphView.cs

@@ -1,321 +1,330 @@
-using System.Text;
+#nullable enable
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
+
+namespace Terminal.Gui; 
+
+/// <summary>
+/// View for rendering graphs (bar, scatter, etc...).
+/// </summary>
+public class GraphView : View {
 
-#nullable enable
-namespace Terminal.Gui {
 	/// <summary>
-	/// Control for rendering graphs (bar, scatter etc)
+	/// Creates a new graph with a 1 to 1 graph space with absolute layout.
 	/// </summary>
-	public class GraphView : View {
-
-		/// <summary>
-		/// Horizontal axis
-		/// </summary>
-		/// <value></value>
-		public HorizontalAxis AxisX { get; set; }
-
-		/// <summary>
-		/// Vertical axis
-		/// </summary>
-		/// <value></value>
-		public VerticalAxis AxisY { get; set; }
-
-		/// <summary>
-		/// Collection of data series that are rendered in the graph
-		/// </summary>
-		public List<ISeries> Series { get; } = new List<ISeries> ();
-
-		/// <summary>
-		/// Elements drawn into graph after series have been drawn e.g. Legends etc
-		/// </summary>
-		public List<IAnnotation> Annotations { get; } = new List<IAnnotation> ();
-
-		/// <summary>
-		/// Amount of space to leave on left of control.  Graph content (<see cref="Series"/>)
-		/// will not be rendered in margins but axis labels may be
-		/// </summary>
-		public uint MarginLeft { get; set; }
-
-		/// <summary>
-		/// Amount of space to leave on bottom of control.  Graph content (<see cref="Series"/>)
-		/// will not be rendered in margins but axis labels may be
-		/// </summary>
-		public uint MarginBottom { get; set; }
-
-		/// <summary>
-		/// The graph space position of the bottom left of the control.
-		/// Changing this scrolls the viewport around in the graph
-		/// </summary>
-		/// <value></value>
-		public PointF ScrollOffset { get; set; } = new PointF (0, 0);
-
-		/// <summary>
-		/// Translates console width/height into graph space. Defaults
-		/// to 1 row/col of console space being 1 unit of graph space. 
-		/// </summary>
-		/// <returns></returns>
-		public PointF CellSize { get; set; } = new PointF (1, 1);
-
-		/// <summary>
-		/// The color of the background of the graph and axis/labels
-		/// </summary>
-		public Attribute? GraphColor { get; set; }
-
-		/// <summary>
-		/// Creates a new graph with a 1 to 1 graph space with absolute layout
-		/// </summary>
-		public GraphView ()
-		{
-			CanFocus = true;
-
-			AxisX = new HorizontalAxis ();
-			AxisY = new VerticalAxis ();
-
-			// Things this view knows how to do
-			AddCommand (Command.ScrollUp, () => { Scroll (0, CellSize.Y); return true; });
-			AddCommand (Command.ScrollDown, () => { Scroll (0, -CellSize.Y); return true; });
-			AddCommand (Command.ScrollRight, () => { Scroll (CellSize.X, 0); return true; });
-			AddCommand (Command.ScrollLeft, () => { Scroll (-CellSize.X, 0); return true; });
-			AddCommand (Command.PageUp, () => { PageUp (); return true; });
-			AddCommand (Command.PageDown, () => { PageDown (); return true; });
-
-			KeyBindings.Add (KeyCode.CursorRight, Command.ScrollRight);
-			KeyBindings.Add (KeyCode.CursorLeft, Command.ScrollLeft);
-			KeyBindings.Add (KeyCode.CursorUp, Command.ScrollUp);
-			KeyBindings.Add (KeyCode.CursorDown, Command.ScrollDown);
-
-			// Not bound by default (preserves backwards compatibility)
-			//KeyBindings.Add (Key.PageUp, Command.PageUp);
-			//KeyBindings.Add (Key.PageDown, Command.PageDown);
-		}
+	public GraphView ()
+	{
+		CanFocus = true;
+
+		AxisX = new HorizontalAxis ();
+		AxisY = new VerticalAxis ();
+
+		// Things this view knows how to do
+		AddCommand (Command.ScrollUp, () => {
+			Scroll (0, CellSize.Y);
+			return true;
+		});
+		AddCommand (Command.ScrollDown, () => {
+			Scroll (0, -CellSize.Y);
+			return true;
+		});
+		AddCommand (Command.ScrollRight, () => {
+			Scroll (CellSize.X, 0);
+			return true;
+		});
+		AddCommand (Command.ScrollLeft, () => {
+			Scroll (-CellSize.X, 0);
+			return true;
+		});
+		AddCommand (Command.PageUp, () => {
+			PageUp ();
+			return true;
+		});
+		AddCommand (Command.PageDown, () => {
+			PageDown ();
+			return true;
+		});
+
+		KeyBindings.Add (KeyCode.CursorRight, Command.ScrollRight);
+		KeyBindings.Add (KeyCode.CursorLeft, Command.ScrollLeft);
+		KeyBindings.Add (KeyCode.CursorUp, Command.ScrollUp);
+		KeyBindings.Add (KeyCode.CursorDown, Command.ScrollDown);
+
+		// Not bound by default (preserves backwards compatibility)
+		//KeyBindings.Add (Key.PageUp, Command.PageUp);
+		//KeyBindings.Add (Key.PageDown, Command.PageDown);
+	}
 
-		/// <summary>
-		/// Clears all settings configured on the graph and resets all properties
-		/// to default values (<see cref="CellSize"/>, <see cref="ScrollOffset"/> etc) 
-		/// </summary>
-		public void Reset ()
-		{
-			ScrollOffset = new PointF (0, 0);
-			CellSize = new PointF (1, 1);
-			AxisX.Reset ();
-			AxisY.Reset ();
-			Series.Clear ();
-			Annotations.Clear ();
-			GraphColor = null;
-			SetNeedsDisplay ();
-		}
+	/// <summary>
+	/// Horizontal axis.
+	/// </summary>
+	/// <value></value>
+	public HorizontalAxis AxisX { get; set; }
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			if (CellSize.X == 0 || CellSize.Y == 0) {
-				throw new Exception ($"{nameof (CellSize)} cannot be 0");
-			}
+	/// <summary>
+	/// Vertical axis.
+	/// </summary>
+	/// <value></value>
+	public VerticalAxis AxisY { get; set; }
 
-			SetDriverColorToGraphColor ();
+	/// <summary>
+	/// Collection of data series that are rendered in the graph.
+	/// </summary>
+	public List<ISeries> Series { get; } = new ();
 
-			Move (0, 0);
+	/// <summary>
+	/// Elements drawn into graph after series have been drawn e.g. Legends etc.
+	/// </summary>
+	public List<IAnnotation> Annotations { get; } = new ();
 
-			// clear all old content
-			for (int i = 0; i < Bounds.Height; i++) {
-				Move (0, i);
-				Driver.AddStr (new string (' ', Bounds.Width));
-			}
+	/// <summary>
+	/// Amount of space to leave on left of the graph. Graph content (<see cref="Series"/>)
+	/// will not be rendered in margins but axis labels may be. Use <see cref="Padding"/> to
+	/// add a margin outside of the GraphView.
+	/// </summary>
+	public uint MarginLeft { get; set; }
 
-			// If there is no data do not display a graph
-			if (!Series.Any () && !Annotations.Any ()) {
-				return;
-			}
+	/// <summary>
+	/// Amount of space to leave on bottom of the graph. Graph content (<see cref="Series"/>)
+	/// will not be rendered in margins but axis labels may be. Use <see cref="Padding"/> to
+	/// add a margin outside of the GraphView.
+	/// </summary>
+	public uint MarginBottom { get; set; }
 
-			// The drawable area of the graph (anything that isn't in the margins)
-			var graphScreenWidth = Bounds.Width - ((int)MarginLeft);
-			var graphScreenHeight = Bounds.Height - (int)MarginBottom;
+	/// <summary>
+	/// The graph space position of the bottom left of the graph.
+	/// Changing this scrolls the viewport around in the graph.
+	/// </summary>
+	/// <value></value>
+	public PointF ScrollOffset { get; set; } = new (0, 0);
 
-			// if the margins take up the full draw bounds don't render
-			if (graphScreenWidth < 0 || graphScreenHeight < 0) {
-				return;
-			}
+	/// <summary>
+	/// Translates console width/height into graph space. Defaults
+	/// to 1 row/col of console space being 1 unit of graph space.
+	/// </summary>
+	/// <returns></returns>
+	public PointF CellSize { get; set; } = new (1, 1);
 
-			// Draw 'before' annotations
-			foreach (var a in Annotations.ToArray ().Where (a => a.BeforeSeries)) {
-				a.Render (this);
-			}
+	/// <summary>
+	/// The color of the background of the graph and axis/labels.
+	/// </summary>
+	public Attribute? GraphColor { get; set; }
 
-			SetDriverColorToGraphColor ();
+	/// <summary>
+	/// Clears all settings configured on the graph and resets all properties
+	/// to default values (<see cref="CellSize"/>, <see cref="ScrollOffset"/> etc) .
+	/// </summary>
+	public void Reset ()
+	{
+		ScrollOffset = new PointF (0, 0);
+		CellSize = new PointF (1, 1);
+		AxisX.Reset ();
+		AxisY.Reset ();
+		Series.Clear ();
+		Annotations.Clear ();
+		GraphColor = null;
+		SetNeedsDisplay ();
+	}
 
-			AxisY.DrawAxisLine (this);
-			AxisX.DrawAxisLine (this);
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		if (CellSize.X == 0 || CellSize.Y == 0) {
+			throw new Exception ($"{nameof (CellSize)} cannot be 0");
+		}
 
-			AxisY.DrawAxisLabels (this);
-			AxisX.DrawAxisLabels (this);
+		SetDriverColorToGraphColor ();
 
-			// Draw a cross where the two axis cross
-			var axisIntersection = new Point (AxisY.GetAxisXPosition (this), AxisX.GetAxisYPosition (this));
+		Move (0, 0);
 
-			if (AxisX.Visible && AxisY.Visible) {
-				Move (axisIntersection.X, axisIntersection.Y);
-				AddRune (axisIntersection.X, axisIntersection.Y, (Rune)'\u253C');
-			}
+		// clear all old content
+		for (var i = 0; i < Bounds.Height; i++) {
+			Move (0, i);
+			Driver.AddStr (new string (' ', Bounds.Width));
+		}
 
-			SetDriverColorToGraphColor ();
+		// If there is no data do not display a graph
+		if (!Series.Any () && !Annotations.Any ()) {
+			return;
+		}
 
-			Rect drawBounds = new Rect ((int)MarginLeft, 0, graphScreenWidth, graphScreenHeight);
+		// The drawable area of the graph (anything that isn't in the margins)
+		var graphScreenWidth = Bounds.Width - (int)MarginLeft;
+		var graphScreenHeight = Bounds.Height - (int)MarginBottom;
 
-			RectangleF graphSpace = ScreenToGraphSpace (drawBounds);
+		// if the margins take up the full draw bounds don't render
+		if (graphScreenWidth < 0 || graphScreenHeight < 0) {
+			return;
+		}
 
-			foreach (var s in Series.ToArray ()) {
+		// Draw 'before' annotations
+		foreach (var a in Annotations.ToArray ().Where (a => a.BeforeSeries)) {
+			a.Render (this);
+		}
 
-				s.DrawSeries (this, drawBounds, graphSpace);
+		SetDriverColorToGraphColor ();
 
-				// If a series changes the graph color reset it
-				SetDriverColorToGraphColor ();
-			}
+		AxisY.DrawAxisLine (this);
+		AxisX.DrawAxisLine (this);
 
-			SetDriverColorToGraphColor ();
+		AxisY.DrawAxisLabels (this);
+		AxisX.DrawAxisLabels (this);
 
-			// Draw 'after' annotations
-			foreach (var a in Annotations.ToArray ().Where (a => !a.BeforeSeries)) {
-				a.Render (this);
-			}
-		}
+		// Draw a cross where the two axis cross
+		var axisIntersection = new Point (AxisY.GetAxisXPosition (this), AxisX.GetAxisYPosition (this));
 
-		/// <summary>
-		/// Sets the color attribute of <see cref="Application.Driver"/> to the <see cref="GraphColor"/>
-		/// (if defined) or <see cref="ColorScheme"/> otherwise.
-		/// </summary>
-		public void SetDriverColorToGraphColor ()
-		{
-			Driver.SetAttribute (GraphColor ?? (GetNormalColor ()));
+		if (AxisX.Visible && AxisY.Visible) {
+			Move (axisIntersection.X, axisIntersection.Y);
+			AddRune (axisIntersection.X, axisIntersection.Y, (Rune)'\u253C');
 		}
 
-		/// <summary>
-		/// Returns the section of the graph that is represented by the given
-		/// screen position
-		/// </summary>
-		/// <param name="col"></param>
-		/// <param name="row"></param>
-		/// <returns></returns>
-		public RectangleF ScreenToGraphSpace (int col, int row)
-		{
-			return new RectangleF (
-				ScrollOffset.X + ((col - MarginLeft) * CellSize.X),
-				ScrollOffset.Y + ((Bounds.Height - (row + MarginBottom + 1)) * CellSize.Y),
-				CellSize.X, CellSize.Y);
-		}
+		SetDriverColorToGraphColor ();
 
-		/// <summary>
-		/// Returns the section of the graph that is represented by the screen area
-		/// </summary>
-		/// <param name="screenArea"></param>
-		/// <returns></returns>
-		public RectangleF ScreenToGraphSpace (Rect screenArea)
-		{
-			// get position of the bottom left
-			var pos = ScreenToGraphSpace (screenArea.Left, screenArea.Bottom - 1);
-
-			return new RectangleF (pos.X, pos.Y, screenArea.Width * CellSize.X, screenArea.Height * CellSize.Y);
-		}
-		/// <summary>
-		/// Calculates the screen location for a given point in graph space.
-		/// Bear in mind these be off screen
-		/// </summary>
-		/// <param name="location">Point in graph space that may or may not be represented in the
-		/// visible area of graph currently presented.  E.g. 0,0 for origin</param>
-		/// <returns>Screen position (Column/Row) which would be used to render the graph <paramref name="location"/>.
-		/// Note that this can be outside the current client area of the control</returns>
-		public Point GraphSpaceToScreen (PointF location)
-		{
-			return new Point (
-
-				(int)((location.X - ScrollOffset.X) / CellSize.X) + (int)MarginLeft,
-				 // screen coordinates are top down while graph coordinates are bottom up
-				 (Bounds.Height - 1) - (int)MarginBottom - (int)((location.Y - ScrollOffset.Y) / CellSize.Y)
-				);
-		}
+		var drawBounds = new Rect ((int)MarginLeft, 0, graphScreenWidth, graphScreenHeight);
 
-		/// <inheritdoc/>
-		/// <remarks>Also ensures that cursor is invisible after entering the <see cref="GraphView"/>.</remarks>
-		public override bool OnEnter (View view)
-		{
-			Driver.SetCursorVisibility (CursorVisibility.Invisible);
-			return base.OnEnter (view);
-		}
+		var graphSpace = ScreenToGraphSpace (drawBounds);
 
-		/// <summary>
-		/// Scrolls the graph up 1 page
-		/// </summary>
-		public void PageUp ()
-		{
-			Scroll (0, CellSize.Y * Bounds.Height);
-		}
+		foreach (var s in Series.ToArray ()) {
+
+			s.DrawSeries (this, drawBounds, graphSpace);
 
-		/// <summary>
-		/// Scrolls the graph down 1 page
-		/// </summary>
-		public void PageDown ()
-		{
-			Scroll (0, -1 * CellSize.Y * Bounds.Height);
+			// If a series changes the graph color reset it
+			SetDriverColorToGraphColor ();
 		}
-		/// <summary>
-		/// Scrolls the view by a given number of units in graph space.
-		/// See <see cref="CellSize"/> to translate this into rows/cols
-		/// </summary>
-		/// <param name="offsetX"></param>
-		/// <param name="offsetY"></param>
-		public void Scroll (float offsetX, float offsetY)
-		{
-			ScrollOffset = new PointF (
-				ScrollOffset.X + offsetX,
-				ScrollOffset.Y + offsetY);
-
-			SetNeedsDisplay ();
+
+		SetDriverColorToGraphColor ();
+
+		// Draw 'after' annotations
+		foreach (var a in Annotations.ToArray ().Where (a => !a.BeforeSeries)) {
+			a.Render (this);
 		}
+	}
 
-		#region Bresenham's line algorithm
-		// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C.23
+	/// <summary>
+	/// Sets the color attribute of <see cref="Application.Driver"/> to the <see cref="GraphColor"/>
+	/// (if defined) or <see cref="ColorScheme"/> otherwise.
+	/// </summary>
+	public void SetDriverColorToGraphColor () => Driver.SetAttribute (GraphColor ?? GetNormalColor ());
 
-		int ipart (decimal x) { return (int)x; }
+	/// <summary>
+	/// Returns the section of the graph that is represented by the given
+	/// screen position.
+	/// </summary>
+	/// <param name="col"></param>
+	/// <param name="row"></param>
+	/// <returns></returns>
+	public RectangleF ScreenToGraphSpace (int col, int row) => new (
+		ScrollOffset.X + (col - MarginLeft) * CellSize.X,
+		ScrollOffset.Y + (Bounds.Height - (row + MarginBottom + 1)) * CellSize.Y,
+		CellSize.X, CellSize.Y);
 
-		decimal fpart (decimal x)
-		{
-			if (x < 0) return (1 - (x - Math.Floor (x)));
-			return (x - Math.Floor (x));
-		}
+	/// <summary>
+	/// Returns the section of the graph that is represented by the screen area.
+	/// </summary>
+	/// <param name="screenArea"></param>
+	/// <returns></returns>
+	public RectangleF ScreenToGraphSpace (Rect screenArea)
+	{
+		// get position of the bottom left
+		var pos = ScreenToGraphSpace (screenArea.Left, screenArea.Bottom - 1);
+
+		return new RectangleF (pos.X, pos.Y, screenArea.Width * CellSize.X, screenArea.Height * CellSize.Y);
+	}
 
-		/// <summary>
-		/// Draws a line between two points in screen space.  Can be diagonals.
-		/// </summary>
-		/// <param name="start"></param>
-		/// <param name="end"></param>
-		/// <param name="symbol">The symbol to use for the line</param>
-		public void DrawLine (Point start, Point end, Rune symbol)
-		{
-			if (Equals (start, end)) {
-				return;
-			}
+	/// <summary>
+	/// Calculates the screen location for a given point in graph space.
+	/// Bear in mind these may be off screen.
+	/// </summary>
+	/// <param name="location">
+	/// Point in graph space that may or may not be represented in the
+	/// visible area of graph currently presented.  E.g. 0,0 for origin.
+	/// </param>
+	/// <returns>
+	/// Screen position (Column/Row) which would be used to render the graph <paramref name="location"/>.
+	/// Note that this can be outside the current content area of the view.
+	/// </returns>
+	public Point GraphSpaceToScreen (PointF location) => new (
+		(int)((location.X - ScrollOffset.X) / CellSize.X) + (int)MarginLeft,
+		// screen coordinates are top down while graph coordinates are bottom up
+		Bounds.Height - 1 - (int)MarginBottom - (int)((location.Y - ScrollOffset.Y) / CellSize.Y)
+	);
+
+	/// <inheritdoc/>
+	/// <remarks>Also ensures that cursor is invisible after entering the <see cref="GraphView"/>.</remarks>
+	public override bool OnEnter (View view)
+	{
+		Driver.SetCursorVisibility (CursorVisibility.Invisible);
+		return base.OnEnter (view);
+	}
 
-			int x0 = start.X;
-			int y0 = start.Y;
-			int x1 = end.X;
-			int y1 = end.Y;
+	/// <summary>
+	/// Scrolls the graph up 1 page.
+	/// </summary>
+	public void PageUp () => Scroll (0, CellSize.Y * Bounds.Height);
 
-			int dx = Math.Abs (x1 - x0), sx = x0 < x1 ? 1 : -1;
-			int dy = Math.Abs (y1 - y0), sy = y0 < y1 ? 1 : -1;
-			int err = (dx > dy ? dx : -dy) / 2, e2;
+	/// <summary>
+	/// Scrolls the graph down 1 page.
+	/// </summary>
+	public void PageDown () => Scroll (0, -1 * CellSize.Y * Bounds.Height);
 
-			while (true) {
+	/// <summary>
+	/// Scrolls the view by a given number of units in graph space.
+	/// See <see cref="CellSize"/> to translate this into rows/cols.
+	/// </summary>
+	/// <param name="offsetX"></param>
+	/// <param name="offsetY"></param>
+	public void Scroll (float offsetX, float offsetY)
+	{
+		ScrollOffset = new PointF (
+			ScrollOffset.X + offsetX,
+			ScrollOffset.Y + offsetY);
+
+		SetNeedsDisplay ();
+	}
 
-				AddRune (x0, y0, symbol);
+	#region Bresenham's line algorithm
+	// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C.23
 
-				if (x0 == x1 && y0 == y1) break;
-				e2 = err;
-				if (e2 > -dx) { err -= dy; x0 += sx; }
-				if (e2 < dy) { err += dx; y0 += sy; }
-			}
+	/// <summary>
+	/// Draws a line between two points in screen space. Can be diagonals.
+	/// </summary>
+	/// <param name="start"></param>
+	/// <param name="end"></param>
+	/// <param name="symbol">The symbol to use for the line</param>
+	public void DrawLine (Point start, Point end, Rune symbol)
+	{
+		if (Equals (start, end)) {
+			return;
 		}
 
-		#endregion
+		var x0 = start.X;
+		var y0 = start.Y;
+		var x1 = end.X;
+		var y1 = end.Y;
+
+		int dx = Math.Abs (x1 - x0), sx = x0 < x1 ? 1 : -1;
+		int dy = Math.Abs (y1 - y0), sy = y0 < y1 ? 1 : -1;
+		int err = (dx > dy ? dx : -dy) / 2, e2;
+
+		while (true) {
+
+			AddRune (x0, y0, symbol);
+
+			if (x0 == x1 && y0 == y1) {
+				break;
+			}
+			e2 = err;
+			if (e2 > -dx) {
+				err -= dy;
+				x0 += sx;
+			}
+			if (e2 < dy) {
+				err += dx;
+				y0 += sy;
+			}
+		}
 	}
+	#endregion
 }

+ 21 - 14
Terminal.Gui/Views/HexView.cs

@@ -95,6 +95,17 @@ public partial class HexView : View {
 		KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.EndOfLine);
 		KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.StartOfPage);
 		KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.EndOfPage);
+
+		LayoutComplete += HexView_LayoutComplete;
+	}
+
+	private void HexView_LayoutComplete (object sender, LayoutEventArgs e)
+	{
+		// Small buffers will just show the position, with the bsize field value (4 bytes)
+		bytesPerLine = bsize;
+		if (Bounds.Width - displayWidth > 17) {
+			bytesPerLine = bsize * ((Bounds.Width - displayWidth) / 18);
+		}
 	}
 
 	/// <summary>
@@ -174,20 +185,6 @@ public partial class HexView : View {
 		}
 	}
 
-	/// <inheritdoc/>
-	public override Rect Frame {
-		get => base.Frame;
-		set {
-			base.Frame = value;
-
-			// Small buffers will just show the position, with the bsize field value (4 bytes)
-			bytesPerLine = bsize;
-			if (value.Width - displayWidth > 17) {
-				bytesPerLine = bsize * ((value.Width - displayWidth) / 18);
-			}
-		}
-	}
-
 	//
 	// This is used to support editing of the buffer on a peer List<>, 
 	// the offset corresponds to an offset relative to DisplayStart, and
@@ -214,6 +211,7 @@ public partial class HexView : View {
 		Driver.SetAttribute (current);
 		Move (0, 0);
 
+		// BUGBUG: Bounds!!!!
 		var frame = Frame;
 
 		int nblocks = bytesPerLine / bsize;
@@ -309,6 +307,7 @@ public partial class HexView : View {
 		int delta = (int)(pos - DisplayStart);
 		int line = delta / bytesPerLine;
 
+		// BUGBUG: Bounds!
 		SetNeedsDisplay (new Rect (0, line, Frame.Width, 1));
 	}
 
@@ -331,6 +330,7 @@ public partial class HexView : View {
 	bool MoveEnd ()
 	{
 		position = source.Length;
+		// BUGBUG: Bounds!
 		if (position >= DisplayStart + bytesPerLine * Frame.Height) {
 			SetDisplayStart (position);
 			SetNeedsDisplay ();
@@ -396,6 +396,7 @@ public partial class HexView : View {
 		if (position < source.Length) {
 			position++;
 		}
+		// BUGBUG: Bounds!
 		if (position >= DisplayStart + bytesPerLine * Frame.Height) {
 			SetDisplayStart (DisplayStart + bytesPerLine);
 			SetNeedsDisplay ();
@@ -424,6 +425,7 @@ public partial class HexView : View {
 
 	bool MoveDown (int bytes)
 	{
+		// BUGBUG: Bounds!
 		RedisplayLine (position);
 		if (position + bytes < source.Length) {
 			position += bytes;
@@ -513,6 +515,8 @@ public partial class HexView : View {
 	/// <inheritdoc/>
 	public override bool MouseEvent (MouseEvent me)
 	{
+		// BUGBUG: Test this with a border! Assumes Frame == Bounds!
+
 		if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
 								&& !me.Flags.HasFlag (MouseFlags.WheeledDown) && !me.Flags.HasFlag (MouseFlags.WheeledUp)) {
 			return false;
@@ -595,6 +599,9 @@ public partial class HexView : View {
 	/// </summary>
 	public Point CursorPosition {
 		get {
+			if (!IsInitialized) {
+				return new Point (0, 0);
+			}
 			int delta = (int)position;
 			int line = delta / bytesPerLine + 1;
 			int item = delta % bytesPerLine + 1;

+ 98 - 123
Terminal.Gui/Views/Label.cs

@@ -1,141 +1,116 @@
-//
-// Label.cs: Label control
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
+using System;
+
+namespace Terminal.Gui; 
+
+/// <summary>
+/// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separated by newline
+/// characters.
+/// Multi-line Labels support word wrap.
+/// </summary>
+/// <remarks>
+/// The <see cref="Label"/> view is functionality identical to <see cref="View"/> and is included for API backwards
+/// compatibility.
+/// </remarks>
+public class Label : View {
+	/// <inheritdoc/>
+	public Label () => SetInitialProperties ();
+
+	/// <inheritdoc/>
+	public Label (Rect frame, bool autosize = false) : base (frame) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (string text, bool autosize = true) : base (text) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (Rect rect, string text, bool autosize = false) : base (rect, text) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (int x, int y, string text, bool autosize = true) : base (x, y, text) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (string text, TextDirection direction, bool autosize = true)
+		: base (text, direction) => SetInitialProperties (autosize);
+
+	void SetInitialProperties (bool autosize = true)
+	{
+		Height   = 1;
+		AutoSize = autosize;
+		// Things this view knows how to do
+		AddCommand (Command.Default, () => {
+			// BUGBUG: This is a hack, but it does work.
+			var can = CanFocus;
+			CanFocus = true;
+			SetFocus ();
+			SuperView.FocusNext ();
+			CanFocus = can;
+			return true;
+		});
+		AddCommand (Command.Accept, () => AcceptKey ());
+
+		// Default key bindings for this view
+		KeyBindings.Add (KeyCode.Space, Command.Accept);
+	}
 
-using System;
-using System.Text;
+	bool AcceptKey ()
+	{
+		if (!HasFocus) {
+			SetFocus ();
+		}
+		OnClicked ();
+		return true;
+	}
 
-namespace Terminal.Gui {
 	/// <summary>
-	/// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separated by newline characters.
-	/// Multi-line Labels support word wrap.
+	/// The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
+	/// or if the user presses the action key while this view is focused. (TODO: IsDefault)
 	/// </summary>
 	/// <remarks>
-	/// The <see cref="Label"/> view is functionality identical to <see cref="View"/> and is included for API backwards compatibility.
+	/// Client code can hook up to this event, it is
+	/// raised when the button is activated either with
+	/// the mouse or the keyboard.
 	/// </remarks>
-	public class Label : View {
-		/// <inheritdoc/>
-		public Label ()
-		{
-			SetInitialProperties ();
-		}
-
-		/// <inheritdoc/>
-		public Label (Rect frame, bool autosize = false) : base (frame)
-		{
-			SetInitialProperties (autosize);
-		}
+	public event EventHandler Clicked;
 
-		/// <inheritdoc/>
-		public Label (string text, bool autosize = true) : base (text)
-		{
-			SetInitialProperties (autosize);
-		}
-
-		/// <inheritdoc/>
-		public Label (Rect rect, string text, bool autosize = false) : base (rect, text)
-		{
-			SetInitialProperties (autosize);
-		}
-
-		/// <inheritdoc/>
-		public Label (int x, int y, string text, bool autosize = true) : base (x, y, text)
-		{
-			SetInitialProperties (autosize);
-		}
-
-		/// <inheritdoc/>
-		public Label (string text, TextDirection direction, bool autosize = true)
-			: base (text, direction)
-		{
-			SetInitialProperties (autosize);
+	/// <summary>
+	/// Method invoked when a mouse event is generated
+	/// </summary>
+	/// <param name="mouseEvent"></param>
+	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+	public override bool OnMouseEvent (MouseEvent mouseEvent)
+	{
+		var args = new MouseEventEventArgs (mouseEvent);
+		if (OnMouseClick (args)) {
+			return true;
 		}
-
-		void SetInitialProperties (bool autosize = true)
-		{
-			Height = 1;
-			AutoSize = autosize;
-			// Things this view knows how to do
-			AddCommand (Command.Default, () => {
-				// BUGBUG: This is a hack, but it does work.
-				var can = CanFocus;
-				CanFocus = true;
-				SetFocus ();
-				SuperView.FocusNext ();
-				CanFocus = can;
-				return true;
-			});
-			AddCommand (Command.Accept, () => AcceptKey ());
-
-			// Default key bindings for this view
-			KeyBindings.Add (KeyCode.Space, Command.Accept);
+		if (MouseEvent (mouseEvent)) {
+			return true;
 		}
 
-		bool AcceptKey ()
-		{
-			if (!HasFocus) {
+		if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
+			if (!HasFocus && SuperView != null) {
+				if (!SuperView.HasFocus) {
+					SuperView.SetFocus ();
+				}
 				SetFocus ();
+				SetNeedsDisplay ();
 			}
+
 			OnClicked ();
 			return true;
 		}
-		
-		/// <summary>
-		///   The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
-		///   or if the user presses the action key while this view is focused. (TODO: IsDefault)
-		/// </summary>
-		/// <remarks>
-		///   Client code can hook up to this event, it is
-		///   raised when the button is activated either with
-		///   the mouse or the keyboard.
-		/// </remarks>
-		public event EventHandler Clicked;
-
-		/// <summary>
-		/// Method invoked when a mouse event is generated
-		/// </summary>
-		/// <param name="mouseEvent"></param>
-		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public override bool OnMouseEvent (MouseEvent mouseEvent)
-		{
-			MouseEventEventArgs args = new MouseEventEventArgs (mouseEvent);
-			if (OnMouseClick (args))
-				return true;
-			if (MouseEvent (mouseEvent))
-				return true;
-
-			if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
-				if (!HasFocus && SuperView != null) {
-					if (!SuperView.HasFocus) {
-						SuperView.SetFocus ();
-					}
-					SetFocus ();
-					SetNeedsDisplay ();
-				}
-
-				OnClicked ();
-				return true;
-			}
-			return false;
-		}
-
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+		return false;
+	}
 
-			return base.OnEnter (view);
-		}
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
-		/// <summary>
-		/// Virtual method to invoke the <see cref="Clicked"/> event.
-		/// </summary>
-		public virtual void OnClicked ()
-		{
-			Clicked?.Invoke (this, EventArgs.Empty);
-		}
+		return base.OnEnter (view);
 	}
-}
+
+	/// <summary>
+	/// Virtual method to invoke the <see cref="Clicked"/> event.
+	/// </summary>
+	public virtual void OnClicked () => Clicked?.Invoke (this, EventArgs.Empty);
+}

+ 2 - 2
Terminal.Gui/Views/Line.cs

@@ -23,7 +23,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <inheritdoc/>
-		public override bool OnDrawFrames ()
+		public override bool OnDrawAdornments ()
 		{
 			var screenBounds = BoundsToScreen (Bounds);
 			LineCanvas lc;
@@ -43,7 +43,7 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override void OnDrawContent (Rect contentArea)
 		{
-			OnDrawFrames ();
+			OnDrawAdornments ();
 		}
 	}
 }

+ 1 - 1
Terminal.Gui/Views/Menu/ContextMenu.cs

@@ -99,7 +99,7 @@ public sealed class ContextMenu : IDisposable {
 		}
 		_container = Application.Current;
 		_container.Closing += Container_Closing;
-		var frame = new Rect (0, 0, View.Driver.Cols, View.Driver.Rows);
+		var frame = Application.Driver.Bounds;
 		var position = Position;
 		if (Host != null) {
 			Host.BoundsToScreen (frame.X, frame.Y, out int x, out int y);

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

@@ -633,7 +633,7 @@ class Menu : View {
 		Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);
 		Driver.SetAttribute (GetNormalColor ());
 
-		OnDrawFrames ();
+		OnDrawAdornments ();
 		OnRenderLineCanvas ();
 
 		for (int i = Bounds.Y; i < _barItems.Children.Length; i++) {

+ 3 - 3
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -278,7 +278,7 @@ public class MenuBar : View {
 		//CanFocus = true;
 		_selected = -1;
 		_selectedSub = -1;
-		ColorScheme = Colors.Menu;
+		ColorScheme = Colors.ColorSchemes ["Menu"];
 		WantMousePositionReports = true;
 		IsMenuOpen = false;
 
@@ -1445,7 +1445,7 @@ public class MenuBar : View {
 		if (Driver == null) {
 			return Point.Empty;
 		}
-		var superViewFrame = SuperView == null ? new Rect (0, 0, Driver.Cols, Driver.Rows) : SuperView.Frame;
+		var superViewFrame = SuperView == null ? Driver.Bounds : SuperView.Frame;
 		var sv = SuperView == null ? Application.Current : SuperView;
 		var boundsOffset = sv.GetBoundsOffset ();
 		return new Point (superViewFrame.X - sv.Frame.X - boundsOffset.X,
@@ -1458,7 +1458,7 @@ public class MenuBar : View {
 	/// <returns>The location offset.</returns>
 	internal Point GetScreenOffsetFromCurrent ()
 	{
-		var screen = new Rect (0, 0, Driver.Cols, Driver.Rows);
+		var screen = Driver.Bounds;
 		var currentFrame = Application.Current.Frame;
 		var boundsOffset = Application.Top.GetBoundsOffset ();
 		return new Point (screen.X - currentFrame.X - boundsOffset.X

+ 6 - 6
Terminal.Gui/Views/MessageBox.cs

@@ -278,9 +278,9 @@ namespace Terminal.Gui {
 			}
 
 			if (useErrorColors) {
-				d.ColorScheme = Colors.Error;
+				d.ColorScheme = Colors.ColorSchemes ["Error"];
 			} else {
-				d.ColorScheme = Colors.Dialog;
+				d.ColorScheme = Colors.ColorSchemes ["Dialog"];
 			}
 
 			var messageLabel = new Label () {
@@ -303,20 +303,20 @@ namespace Terminal.Gui {
 				// TODO: replace with Dim.Fit when implemented
 				var maxBounds = d.SuperView?.Bounds ?? Application.Top.Bounds;
 				if (wrapMessage) {
-					messageLabel.TextFormatter.Size = new Size (maxBounds.Size.Width - d.GetFramesThickness ().Horizontal, maxBounds.Size.Height - d.GetFramesThickness ().Vertical);
+					messageLabel.TextFormatter.Size = new Size (maxBounds.Size.Width - d.GetAdornmentsThickness ().Horizontal, maxBounds.Size.Height - d.GetAdornmentsThickness ().Vertical);
 				}
 				var msg = messageLabel.TextFormatter.Format ();
 				var messageSize = messageLabel.TextFormatter.GetFormattedSize ();
 
 				// Ensure the width fits the text + buttons
-				var newWidth = Math.Max (width, Math.Max (messageSize.Width + d.GetFramesThickness ().Horizontal,
-								d.GetButtonsWidth () + d.buttons.Count + d.GetFramesThickness ().Horizontal));
+				var newWidth = Math.Max (width, Math.Max (messageSize.Width + d.GetAdornmentsThickness ().Horizontal,
+								d.GetButtonsWidth () + d.buttons.Count + d.GetAdornmentsThickness ().Horizontal));
 				if (newWidth > d.Frame.Width) {
 					d.Width = newWidth;
 				}
 				// Ensure height fits the text + vspace + buttons
 				var lastLine = messageLabel.TextFormatter.Lines [^1];
-				d.Height = Math.Max (height, messageSize.Height + (lastLine.EndsWith ("\r\n") || lastLine.EndsWith ('\n') ? 1 : 2) + d.GetFramesThickness ().Vertical);
+				d.Height = Math.Max (height, messageSize.Height + (lastLine.EndsWith ("\r\n") || lastLine.EndsWith ('\n') ? 1 : 2) + d.GetAdornmentsThickness ().Vertical);
 				d.SetRelativeLayout (d.SuperView?.Frame ?? Application.Top.Frame);
 			};
 

+ 9 - 2
Terminal.Gui/Views/ProgressBar.cs

@@ -82,7 +82,8 @@ public class ProgressBar : View {
 
 	void ProgressBar_LayoutStarted (object sender, EventArgs e)
 	{
-		Bounds = new Rect (Bounds.Location, new Size (Bounds.Width, 1));
+		// TODO: use Dim.Auto
+		Height = 1 + GetAdornmentsThickness ().Vertical;
 	}
 
 	float _fraction;
@@ -182,9 +183,14 @@ public class ProgressBar : View {
 	/// </remarks>
 	public void Pulse ()
 	{
-		if (_activityPos == null) {
+		if (_activityPos == null || _activityPos.Length == 0) {
 			PopulateActivityPos ();
 		}
+
+		if (_activityPos!.Length == 0) {
+			return;
+		}
+
 		if (!_isActivity) {
 			_isActivity = true;
 			_delta = 1;
@@ -192,6 +198,7 @@ public class ProgressBar : View {
 			for (var i = 0; i < _activityPos.Length; i++) {
 				_activityPos [i] += _delta;
 			}
+
 			if (_activityPos [^1] < 0) {
 				for (var i = 0; i < _activityPos.Length; i++) {
 					_activityPos [i] = i - _activityPos.Length + 2;

+ 55 - 65
Terminal.Gui/Views/RadioGroup.cs

@@ -10,7 +10,7 @@ namespace Terminal.Gui;
 public class RadioGroup : View {
 	int _selected = -1;
 	int _cursor;
-	DisplayModeLayout _displayMode;
+	Orientation _orientation = Orientation.Vertical;
 	int _horizontalSpace = 2;
 	List<(int pos, int length)> _horizontal;
 
@@ -26,7 +26,7 @@ public class RadioGroup : View {
 	/// <param name="selected">The index of the item to be selected, the value is clamped to the number of items.</param>
 	public RadioGroup (string [] radioLabels, int selected = 0) : base ()
 	{
-		SetInitialProperties (Rect.Empty, radioLabels, selected);
+		SetInitialProperties (radioLabels, selected);
 	}
 
 	/// <summary>
@@ -37,22 +37,10 @@ public class RadioGroup : View {
 	/// <param name="selected">The index of item to be selected, the value is clamped to the number of items.</param>
 	public RadioGroup (Rect rect, string [] radioLabels, int selected = 0) : base (rect)
 	{
-		SetInitialProperties (rect, radioLabels, selected);
+		SetInitialProperties (radioLabels, selected);
 	}
 
-	/// <summary>
-	/// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Absolute"/> layout.
-	/// The <see cref="View"/> frame is computed from the provided radio labels.
-	/// </summary>
-	/// <param name="x">The x coordinate.</param>
-	/// <param name="y">The y coordinate.</param>
-	/// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
-	/// <param name="selected">The item to be selected, the value is clamped to the number of items.</param>
-	public RadioGroup (int x, int y, string [] radioLabels, int selected = 0) :
-		this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected)
-	{ }
-
-	void SetInitialProperties (Rect rect, string [] radioLabels, int selected)
+	void SetInitialProperties (string [] radioLabels, int selected)
 	{
 		HotKeySpecifier = new Rune ('_');
 
@@ -61,7 +49,6 @@ public class RadioGroup : View {
 		}
 
 		_selected = selected;
-		Frame = rect;
 		CanFocus = true;
 
 		// Things this view knows how to do
@@ -87,26 +74,42 @@ public class RadioGroup : View {
 	}
 
 	/// <summary>
-	/// Gets or sets the <see cref="DisplayModeLayout"/> for this <see cref="RadioGroup"/>.
+	/// Gets or sets the <see cref="Orientation"/> for this <see cref="RadioGroup"/>. The default is <see cref="Orientation.Vertical"/>.
 	/// </summary>
-	public DisplayModeLayout DisplayMode {
-		get { return _displayMode; }
-		set {
-			if (_displayMode != value) {
-				_displayMode = value;
-				SetWidthHeight (_radioLabels);
-				SetNeedsDisplay ();
-			}
+	public Orientation Orientation {
+		get => _orientation;
+		set => OnOrientationChanged (value);
+	}
+
+	/// <summary>
+	/// Fired when the view orientation has changed. Can be cancelled by setting
+	/// <see cref="OrientationEventArgs.Cancel"/> to true.
+	/// </summary>
+	public event EventHandler<OrientationEventArgs> OrientationChanged;
+
+	/// <summary>
+	/// Called when the view orientation has changed. Invokes the <see cref="OrientationChanged"/> event.
+	/// </summary>
+	/// <param name="newOrientation"></param>
+	/// <returns>True of the event was cancelled.</returns>
+	public virtual bool OnOrientationChanged (Orientation newOrientation)
+	{
+		var args = new OrientationEventArgs (newOrientation);
+		OrientationChanged?.Invoke (this, args);
+		if (!args.Cancel) {
+			_orientation = newOrientation;
+			SetNeedsLayout ();
 		}
+		return args.Cancel;
 	}
 
 	/// <summary>
-	/// Gets or sets the horizontal space for this <see cref="RadioGroup"/> if the <see cref="DisplayMode"/> is <see cref="DisplayModeLayout.Horizontal"/>
+	/// Gets or sets the horizontal space for this <see cref="RadioGroup"/> if the <see cref="Orientation"/> is <see cref="Orientation.Horizontal"/>
 	/// </summary>
 	public int HorizontalSpace {
 		get { return _horizontalSpace; }
 		set {
-			if (_horizontalSpace != value && _displayMode == DisplayModeLayout.Horizontal) {
+			if (_horizontalSpace != value && _orientation == Orientation.Horizontal) {
 				_horizontalSpace = value;
 				SetWidthHeight (_radioLabels);
 				UpdateTextFormatterText ();
@@ -117,24 +120,24 @@ public class RadioGroup : View {
 
 	void SetWidthHeight (List<string> radioLabels)
 	{
-		switch (_displayMode) {
-		case DisplayModeLayout.Vertical:
+		switch (_orientation) {
+		case Orientation.Vertical:
 			var r = MakeRect (0, 0, radioLabels);
-			Bounds = new Rect (Bounds.Location, new Size (r.Width, radioLabels.Count));
+			if (IsInitialized) {
+				Width = r.Width + GetAdornmentsThickness ().Horizontal;
+				Height = radioLabels.Count + GetAdornmentsThickness ().Vertical;
+			}
 			break;
 
-		case DisplayModeLayout.Horizontal:
+		case Orientation.Horizontal:
 			CalculateHorizontalPositions ();
 			var length = 0;
 			foreach (var item in _horizontal) {
 				length += item.length;
 			}
-			var hr = new Rect (0, 0, length, 1);
-			if (IsAdded && LayoutStyle == LayoutStyle.Computed) {
-				Width = hr.Width;
-				Height = 1;
-			} else {
-				Bounds = new Rect (Bounds.Location, new Size (hr.Width, radioLabels.Count));
+			if (IsInitialized) {
+				Width = length + GetAdornmentsThickness ().Vertical;
+				Height = 1 + GetAdornmentsThickness ().Horizontal;
 			}
 			break;
 		}
@@ -167,14 +170,14 @@ public class RadioGroup : View {
 		set {
 			// Remove old hot key bindings
 			foreach (var label in _radioLabels) {
-				if (TextFormatter.FindHotKey (label, HotKeySpecifier, true, out _, out var hotKey)) {
+				if (TextFormatter.FindHotKey (label, HotKeySpecifier, out _, out var hotKey)) {
 					AddKeyBindingsForHotKey (hotKey, KeyCode.Null);
 				}
 			}
 			var prevCount = _radioLabels.Count;
 			_radioLabels = value.ToList ();
 			foreach (var label in _radioLabels) {
-				if (TextFormatter.FindHotKey (label, HotKeySpecifier, true, out _, out var hotKey)) {
+				if (TextFormatter.FindHotKey (label, HotKeySpecifier, out _, out var hotKey)) {
 					AddKeyBindingsForHotKey (KeyCode.Null, hotKey);
 				}
 			}
@@ -199,7 +202,7 @@ public class RadioGroup : View {
 		if (KeyBindings.TryGet (key, out _)) {
 			// Search RadioLabels 
 			for (int i = 0; i < _radioLabels.Count; i++) {
-				if (TextFormatter.FindHotKey (_radioLabels [i], HotKeySpecifier, true, out _, out var hotKey) 
+				if (TextFormatter.FindHotKey (_radioLabels [i], HotKeySpecifier, out _, out var hotKey, true)
 					&& (key.NoAlt.NoCtrl.NoShift) == hotKey) {
 					SelectedItem = i;
 					keyEvent.Scope = KeyBindingScope.HotKey;
@@ -213,7 +216,7 @@ public class RadioGroup : View {
 
 	void CalculateHorizontalPositions ()
 	{
-		if (_displayMode == DisplayModeLayout.Horizontal) {
+		if (_orientation == Orientation.Horizontal) {
 			_horizontal = new List<(int pos, int length)> ();
 			int start = 0;
 			int length = 0;
@@ -232,18 +235,18 @@ public class RadioGroup : View {
 
 		Driver.SetAttribute (GetNormalColor ());
 		for (int i = 0; i < _radioLabels.Count; i++) {
-			switch (DisplayMode) {
-			case DisplayModeLayout.Vertical:
+			switch (Orientation) {
+			case Orientation.Vertical:
 				Move (0, i);
 				break;
-			case DisplayModeLayout.Horizontal:
+			case Orientation.Horizontal:
 				Move (_horizontal [i].pos, 0);
 				break;
 			}
 			var rl = _radioLabels [i];
 			Driver.SetAttribute (GetNormalColor ());
 			Driver.AddStr ($"{(i == _selected ? CM.Glyphs.Selected : CM.Glyphs.UnSelected)} ");
-			TextFormatter.FindHotKey (rl, HotKeySpecifier, true, out int hotPos, out var hotKey);
+			TextFormatter.FindHotKey (rl, HotKeySpecifier, out int hotPos, out var hotKey);
 			if (hotPos != -1 && (hotKey != KeyCode.Null)) {
 				var rlRunes = rl.ToRunes ();
 				for (int j = 0; j < rlRunes.Length; j++) {
@@ -276,11 +279,11 @@ public class RadioGroup : View {
 	///<inheritdoc/>
 	public override void PositionCursor ()
 	{
-		switch (DisplayMode) {
-		case DisplayModeLayout.Vertical:
+		switch (Orientation) {
+		case Orientation.Vertical:
 			Move (0, _cursor);
 			break;
-		case DisplayModeLayout.Horizontal:
+		case Orientation.Horizontal:
 			Move (_horizontal [_cursor].pos, 0);
 			break;
 		}
@@ -374,11 +377,11 @@ public class RadioGroup : View {
 		int boundsX = me.X;
 		int boundsY = me.Y;
 
-		var pos = _displayMode == DisplayModeLayout.Horizontal ? boundsX : boundsY;
-		var rCount = _displayMode == DisplayModeLayout.Horizontal ? _horizontal.Last ().pos + _horizontal.Last ().length : _radioLabels.Count;
+		var pos = _orientation == Orientation.Horizontal ? boundsX : boundsY;
+		var rCount = _orientation == Orientation.Horizontal ? _horizontal.Last ().pos + _horizontal.Last ().length : _radioLabels.Count;
 
 		if (pos < rCount) {
-			var c = _displayMode == DisplayModeLayout.Horizontal ? _horizontal.FindIndex ((x) => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX) : boundsY;
+			var c = _orientation == Orientation.Horizontal ? _horizontal.FindIndex ((x) => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX) : boundsY;
 			if (c > -1) {
 				_cursor = SelectedItem = c;
 				SetNeedsDisplay ();
@@ -396,16 +399,3 @@ public class RadioGroup : View {
 	}
 }
 
-/// <summary>
-/// Used for choose the display mode of this <see cref="RadioGroup"/>
-/// </summary>
-public enum DisplayModeLayout {
-	/// <summary>
-	/// Vertical mode display. It's the default.
-	/// </summary>
-	Vertical,
-	/// <summary>
-	/// Horizontal mode display.
-	/// </summary>
-	Horizontal
-}

+ 708 - 700
Terminal.Gui/Views/ScrollBarView.cs

@@ -8,816 +8,824 @@
 using System;
 using System.Text;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui; 
+
+/// <summary>
+/// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical
+/// </summary>
+/// <remarks>
+///         <para>
+///         The scrollbar is drawn to be a representation of the Size, assuming that the
+///         scroll position is set at Position.
+///         </para>
+///         <para>
+///         If the region to display the scrollbar is larger than three characters,
+///         arrow indicators are drawn.
+///         </para>
+/// </remarks>
+public class ScrollBarView : View {
+	bool _autoHideScrollBars = true;
+	View _contentBottomRightCorner;
+	bool _hosted;
+	bool _keepContentAlwaysInViewport = true;
+
+	int _lastLocation = -1;
+	ScrollBarView _otherScrollBarView;
+	int _posBarOffset;
+	int _posBottomTee;
+	int _posLeftTee;
+	int _posRightTee;
+
+	int _posTopTee;
+	bool _showScrollIndicator;
+	int _size, _position;
+	bool _vertical;
+
 	/// <summary>
-	/// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical
+	/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/>
+	/// layout.
 	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   The scrollbar is drawn to be a representation of the Size, assuming that the 
-	///   scroll position is set at Position.
-	/// </para>
-	/// <para>
-	///   If the region to display the scrollbar is larger than three characters, 
-	///   arrow indicators are drawn.
-	/// </para>
-	/// </remarks>
-	public class ScrollBarView : View {
-		bool _vertical;
-		int _size, _position;
-		bool _showScrollIndicator;
-		bool _keepContentAlwaysInViewport = true;
-		bool _autoHideScrollBars = true;
-		bool _hosted;
-		ScrollBarView _otherScrollBarView;
-		View _contentBottomRightCorner;
-
-		bool _showBothScrollIndicator => OtherScrollBarView?._showScrollIndicator == true && _showScrollIndicator;
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <param name="rect">Frame for the scrollbar.</param>
-		public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <param name="rect">Frame for the scrollbar.</param>
-		/// <param name="size">The size that this scrollbar represents. Sets the <see cref="Size"/> property.</param>
-		/// <param name="position">The position within this scrollbar. Sets the <see cref="Position"/> property.</param>
-		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the <see cref="IsVertical"/> property.</param>
-		public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect)
-		{
-			SetInitialProperties (size, position, isVertical);
-		}
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		public ScrollBarView () : this (0, 0, false) { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <param name="size">The size that this scrollbar represents.</param>
-		/// <param name="position">The position within this scrollbar.</param>
-		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
-		public ScrollBarView (int size, int position, bool isVertical) : base ()
-		{
-			SetInitialProperties (size, position, isVertical);
-		}
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <param name="host">The view that will host this scrollbar.</param>
-		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
-		/// <param name="showBothScrollIndicator">If set to <c>true (default)</c> will have the other scrollbar, otherwise will have only one.</param>
-		public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) : this (0, 0, isVertical)
-		{
-			if (host == null) {
-				throw new ArgumentNullException ("The host parameter can't be null.");
-			} else if (host.SuperView == null) {
-				throw new ArgumentNullException ("The host SuperView parameter can't be null.");
-			}
-			_hosted = true;
-			ColorScheme = host.ColorScheme;
-			X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host);
-			Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
-			Host = host;
-			CanFocus = false;
-			Enabled = host.Enabled;
-			Visible = host.Visible;
-			//Host.CanFocusChanged += Host_CanFocusChanged;
-			Host.EnabledChanged += Host_EnabledChanged;
-			Host.VisibleChanged += Host_VisibleChanged;
-			Host.SuperView.Add (this);
-			AutoHideScrollBars = true;
-			if (showBothScrollIndicator) {
-				OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) {
-					Id = "OtherScrollBarView",
-					ColorScheme = host.ColorScheme,
-					Host = host,
-					CanFocus = false,
-					Enabled = host.Enabled,
-					Visible = host.Visible,
-					OtherScrollBarView = this
-				};
-				OtherScrollBarView._hosted = true;
-				OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host);
-				OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
-				OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView);
-				OtherScrollBarView.ShowScrollIndicator = true;
-			}
-			ShowScrollIndicator = true;
-			CreateBottomRightCorner ();
-			ClearOnVisibleFalse = false;
-		}
-
-		private void CreateBottomRightCorner ()
-		{
-			if (Host != null && (_contentBottomRightCorner == null && OtherScrollBarView == null
-				|| (_contentBottomRightCorner == null && OtherScrollBarView != null && OtherScrollBarView._contentBottomRightCorner == null))) {
-
-				_contentBottomRightCorner = new View () {
-					Id = "contentBottomRightCorner",
-					Visible = Host.Visible,
-					ClearOnVisibleFalse = false,
-					ColorScheme = ColorScheme
-				};
-				if (_hosted) {
-					Host.SuperView.Add (_contentBottomRightCorner);
-				} else {
-					Host.Add (_contentBottomRightCorner);
-				}
-				_contentBottomRightCorner.X = Pos.Right (Host) - 1;
-				_contentBottomRightCorner.Y = Pos.Bottom (Host) - 1;
-				_contentBottomRightCorner.Width = 1;
-				_contentBottomRightCorner.Height = 1;
-				_contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick;
-				_contentBottomRightCorner.DrawContent += _contentBottomRightCorner_DrawContent;
-			}
-		}
-
-		private void _contentBottomRightCorner_DrawContent (object sender, DrawEventArgs e)
-		{
-			Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
-		}
+	/// <param name="rect">Frame for the scrollbar.</param>
+	public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { }
 
-		private void Host_VisibleChanged (object sender, EventArgs e)
-		{
-			if (!Host.Visible) {
-				Visible = Host.Visible;
-				if (_otherScrollBarView != null) {
-					_otherScrollBarView.Visible = Visible;
-				}
-				_contentBottomRightCorner.Visible = Visible;
-			} else {
-				ShowHideScrollBars ();
-			}
-		}
-
-		private void Host_EnabledChanged (object sender, EventArgs e)
-		{
-			Enabled = Host.Enabled;
-			if (_otherScrollBarView != null) {
-				_otherScrollBarView.Enabled = Enabled;
-			}
-			_contentBottomRightCorner.Enabled = Enabled;
-		}
-
-		//private void Host_CanFocusChanged ()
-		//{
-		//	CanFocus = Host.CanFocus;
-		//	if (otherScrollBarView != null) {
-		//		otherScrollBarView.CanFocus = CanFocus;
-		//	}
-		//}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/>
+	/// layout.
+	/// </summary>
+	/// <param name="rect">Frame for the scrollbar.</param>
+	/// <param name="size">The size that this scrollbar represents. Sets the <see cref="Size"/> property.</param>
+	/// <param name="position">The position within this scrollbar. Sets the <see cref="Position"/> property.</param>
+	/// <param name="isVertical">
+	/// If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the
+	/// <see cref="IsVertical"/> property.
+	/// </param>
+	public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) => SetInitialProperties (size, position, isVertical);
 
-		void ContentBottomRightCorner_MouseClick (object sender, MouseEventEventArgs me)
-		{
-			if (me.MouseEvent.Flags == MouseFlags.WheeledDown || me.MouseEvent.Flags == MouseFlags.WheeledUp
-			    || me.MouseEvent.Flags == MouseFlags.WheeledRight || me.MouseEvent.Flags == MouseFlags.WheeledLeft) {
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/>
+	/// layout.
+	/// </summary>
+	public ScrollBarView () : this (0, 0, false) { }
 
-				MouseEvent (me.MouseEvent);
-			} else if (me.MouseEvent.Flags == MouseFlags.Button1Clicked) {
-				Host.SetFocus ();
-			}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/>
+	/// layout.
+	/// </summary>
+	/// <param name="size">The size that this scrollbar represents.</param>
+	/// <param name="position">The position within this scrollbar.</param>
+	/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
+	public ScrollBarView (int size, int position, bool isVertical) => SetInitialProperties (size, position, isVertical);
 
-			me.Handled = true;
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/>
+	/// layout.
+	/// </summary>
+	/// <param name="host">The view that will host this scrollbar.</param>
+	/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
+	/// <param name="showBothScrollIndicator">
+	/// If set to <c>true (default)</c> will have the other scrollbar, otherwise will
+	/// have only one.
+	/// </param>
+	public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) : this (0, 0, isVertical)
+	{
+		if (host == null) {
+			throw new ArgumentNullException ("The host parameter can't be null.");
 		}
+		if (host.SuperView == null) {
+			throw new ArgumentNullException ("The host SuperView parameter can't be null.");
+		}
+		_hosted = true;
+		ColorScheme = host.ColorScheme;
+		X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host);
+		Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
+		Host = host;
+		CanFocus = false;
+		Enabled = host.Enabled;
+		Visible = host.Visible;
+		//Host.CanFocusChanged += Host_CanFocusChanged;
+		Host.EnabledChanged += Host_EnabledChanged;
+		Host.VisibleChanged += Host_VisibleChanged;
+		Host.SuperView.Add (this);
+		AutoHideScrollBars = true;
+		if (showBothScrollIndicator) {
+			OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) {
+				Id = "OtherScrollBarView",
+				ColorScheme = host.ColorScheme,
+				Host = host,
+				CanFocus = false,
+				Enabled = host.Enabled,
+				Visible = host.Visible,
+				OtherScrollBarView = this
+			};
+			OtherScrollBarView._hosted = true;
+			OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host);
+			OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
+			OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView);
+			OtherScrollBarView.ShowScrollIndicator = true;
+		}
+		ShowScrollIndicator = true;
+		CreateBottomRightCorner ();
+		ClearOnVisibleFalse = false;
+	}
 
-		void SetInitialProperties (int size, int position, bool isVertical)
-		{
-			Id = "ScrollBarView";
-			_vertical = isVertical;
-			this._position = position;
-			this._size = size;
-			WantContinuousButtonPressed = true;
-			ClearOnVisibleFalse = false;
-
-			Added += (s, e) => CreateBottomRightCorner ();
+	bool _showBothScrollIndicator => OtherScrollBarView?._showScrollIndicator == true && _showScrollIndicator;
 
-			Initialized += (s, e) => {
+	/// <summary>
+	/// If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.
+	/// </summary>
+	public bool IsVertical {
+		get => _vertical;
+		set {
+			_vertical = value;
+			if (IsInitialized) {
 				SetWidthHeight ();
-				SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame);
-				if (Id == "OtherScrollBarView" || OtherScrollBarView == null) {
-					// Only do this once if both scrollbars are enabled
-					ShowHideScrollBars ();
-				}
-				SetPosition (position);
-			};
+			}
 		}
+	}
 
-		/// <summary>
-		/// If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.
-		/// </summary>
-		public bool IsVertical {
-			get => _vertical;
-			set {
-				_vertical = value;
-				if (IsInitialized) {
-					SetWidthHeight ();
-				}
+	/// <summary>
+	/// The size of content the scrollbar represents.
+	/// </summary>
+	/// <value>The size.</value>
+	/// <remarks>
+	/// The <see cref="Size"/> is typically the size of the virtual content. E.g. when a Scrollbar is
+	/// part of a <see cref="View"/> the Size is set to the appropriate dimension of <see cref="Host"/>.
+	/// </remarks>
+	public int Size {
+		get => _size;
+		set {
+			_size = value;
+			if (IsInitialized) {
+				SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+				ShowHideScrollBars (false);
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// The size of content the scrollbar represents.
-		/// </summary>
-		/// <value>The size.</value>
-		/// <remarks>The <see cref="Size"/> is typically the size of the virtual content. E.g. when a Scrollbar is
-		/// part of a <see cref="View"/> the Size is set to the appropriate dimension of <see cref="Host"/>.</remarks>
-		public int Size {
-			get => _size;
-			set {
-				_size = value;
-				if (IsInitialized) {
-					SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
-					ShowHideScrollBars (false);
-					SetNeedsDisplay ();
-				}
+	/// <summary>
+	/// The position, relative to <see cref="Size"/>, to set the scrollbar at.
+	/// </summary>
+	/// <value>The position.</value>
+	public int Position {
+		get => _position;
+		set {
+			_position = value;
+			if (IsInitialized) {
+				// We're not initialized so we can't do anything fancy. Just cache value.
+				SetPosition (value);
 			}
 		}
+	}
 
-		/// <summary>
-		/// This event is raised when the position on the scrollbar has changed.
-		/// </summary>
-		public event EventHandler ChangedPosition;
+	// BUGBUG: v2 - for consistency this should be named "Parent" not "Host"
+	/// <summary>
+	/// Get or sets the view that host this <see cref="ScrollBarView"/>
+	/// </summary>
+	public View Host { get; internal set; }
 
-		/// <summary>
-		/// The position, relative to <see cref="Size"/>, to set the scrollbar at.
-		/// </summary>
-		/// <value>The position.</value>
-		public int Position {
-			get => _position;
-			set {
-				_position = value;
-				if (IsInitialized) {
-					// We're not initialized so we can't do anything fancy. Just cache value.
-					SetPosition (value);
-				}
+	/// <summary>
+	/// Represent a vertical or horizontal ScrollBarView other than this.
+	/// </summary>
+	public ScrollBarView OtherScrollBarView {
+		get => _otherScrollBarView;
+		set {
+			if (value != null && (value.IsVertical && _vertical || !value.IsVertical && !_vertical)) {
+				throw new ArgumentException ($"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView.");
 			}
+			_otherScrollBarView = value;
 		}
+	}
 
-		// Helper to assist Initialized event handler
-		private void SetPosition (int newPosition)
-		{
-			if (CanScroll (newPosition - _position, out int max, _vertical)) {
-				if (max == newPosition - _position) {
-					_position = newPosition;
+	// BUGBUG: v2 - Why can't we get rid of this and just use Visible?
+	/// <summary>
+	/// Gets or sets the visibility for the vertical or horizontal scroll indicator.
+	/// </summary>
+	/// <value><c>true</c> if show vertical or horizontal scroll indicator; otherwise, <c>false</c>.</value>
+	public bool ShowScrollIndicator {
+		get => _showScrollIndicator;
+		set {
+			//if (value == showScrollIndicator) {
+			//	return;
+			//}
+
+			_showScrollIndicator = value;
+			if (IsInitialized) {
+				SetNeedsLayout ();
+				if (value) {
+					Visible = true;
 				} else {
-					_position = Math.Max (_position + max, 0);
-				}
-			} else if (max < 0) {
-				_position = Math.Max (_position + max, 0);
-			} else {
-				_position = Math.Max (newPosition, 0);
-			}
-			OnChangedPosition ();
-			SetNeedsDisplay ();
-		}
-
-		// BUGBUG: v2 - for consistency this should be named "Parent" not "Host"
-		/// <summary>
-		/// Get or sets the view that host this <see cref="ScrollBarView"/>
-		/// </summary>
-		public View Host { get; internal set; }
-
-		/// <summary>
-		/// Represent a vertical or horizontal ScrollBarView other than this.
-		/// </summary>
-		public ScrollBarView OtherScrollBarView {
-			get => _otherScrollBarView;
-			set {
-				if (value != null && (value.IsVertical && _vertical || !value.IsVertical && !_vertical)) {
-					throw new ArgumentException ($"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView.");
-				}
-				_otherScrollBarView = value;
-			}
-		}
-
-		// BUGBUG: v2 - Why can't we get rid of this and just use Visible?
-		/// <summary>
-		/// Gets or sets the visibility for the vertical or horizontal scroll indicator.
-		/// </summary>
-		/// <value><c>true</c> if show vertical or horizontal scroll indicator; otherwise, <c>false</c>.</value>
-		public bool ShowScrollIndicator {
-			get => _showScrollIndicator;
-			set {
-				//if (value == showScrollIndicator) {
-				//	return;
-				//}
-
-				_showScrollIndicator = value;
-				if (IsInitialized) {
-					SetNeedsLayout ();
-					if (value) {
-						Visible = true;
-					} else {
-						Visible = false;
-						Position = 0;
-					}
-					SetWidthHeight ();
+					Visible = false;
+					Position = 0;
 				}
+				SetWidthHeight ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Get or sets if the view-port is kept always visible in the area of this <see cref="ScrollBarView"/>
-		/// </summary>
-		public bool KeepContentAlwaysInViewport {
-			get { return _keepContentAlwaysInViewport; }
-			set {
-				if (_keepContentAlwaysInViewport != value) {
-					_keepContentAlwaysInViewport = value;
-					int pos = 0;
-					if (value && !_vertical && _position + Host.Bounds.Width > _size) {
-						pos = _size - Host.Bounds.Width + (_showBothScrollIndicator ? 1 : 0);
-					}
-					if (value && _vertical && _position + Host.Bounds.Height > _size) {
-						pos = _size - Host.Bounds.Height + (_showBothScrollIndicator ? 1 : 0);
-					}
-					if (pos != 0) {
-						Position = pos;
-					}
-					if (OtherScrollBarView != null && OtherScrollBarView._keepContentAlwaysInViewport != value) {
-						OtherScrollBarView.KeepContentAlwaysInViewport = value;
-					}
-					if (pos == 0) {
-						Refresh ();
-					}
+	/// <summary>
+	/// Get or sets if the view-port is kept always visible in the area of this <see cref="ScrollBarView"/>
+	/// </summary>
+	public bool KeepContentAlwaysInViewport {
+		get => _keepContentAlwaysInViewport;
+		set {
+			if (_keepContentAlwaysInViewport != value) {
+				_keepContentAlwaysInViewport = value;
+				var pos = 0;
+				if (value && !_vertical && _position + Host.Bounds.Width > _size) {
+					pos = _size - Host.Bounds.Width + (_showBothScrollIndicator ? 1 : 0);
+				}
+				if (value && _vertical && _position + Host.Bounds.Height > _size) {
+					pos = _size - Host.Bounds.Height + (_showBothScrollIndicator ? 1 : 0);
+				}
+				if (pos != 0) {
+					Position = pos;
+				}
+				if (OtherScrollBarView != null && OtherScrollBarView._keepContentAlwaysInViewport != value) {
+					OtherScrollBarView.KeepContentAlwaysInViewport = value;
+				}
+				if (pos == 0) {
+					Refresh ();
 				}
 			}
 		}
+	}
 
-		/// <summary>
-		/// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
-		/// </summary>
-		public bool AutoHideScrollBars {
-			get => _autoHideScrollBars;
-			set {
-				if (_autoHideScrollBars != value) {
-					_autoHideScrollBars = value;
-					SetNeedsDisplay ();
-				}
+	/// <summary>
+	/// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
+	/// </summary>
+	public bool AutoHideScrollBars {
+		get => _autoHideScrollBars;
+		set {
+			if (_autoHideScrollBars != value) {
+				_autoHideScrollBars = value;
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Virtual method to invoke the <see cref="ChangedPosition"/> action event.
-		/// </summary>
-		public virtual void OnChangedPosition ()
-		{
-			ChangedPosition?.Invoke (this, EventArgs.Empty);
+	void CreateBottomRightCorner ()
+	{
+		if (Host != null &&
+		    (_contentBottomRightCorner == null && OtherScrollBarView == null ||
+		     _contentBottomRightCorner == null && OtherScrollBarView != null && OtherScrollBarView._contentBottomRightCorner == null)) {
+
+			_contentBottomRightCorner = new View {
+				Id = "contentBottomRightCorner",
+				Visible = Host.Visible,
+				ClearOnVisibleFalse = false,
+				ColorScheme = ColorScheme
+			};
+			if (_hosted) {
+				Host.SuperView.Add (_contentBottomRightCorner);
+			} else {
+				Host.Add (_contentBottomRightCorner);
+			}
+			_contentBottomRightCorner.X = Pos.Right (Host) - 1;
+			_contentBottomRightCorner.Y = Pos.Bottom (Host) - 1;
+			_contentBottomRightCorner.Width = 1;
+			_contentBottomRightCorner.Height = 1;
+			_contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick;
+			_contentBottomRightCorner.DrawContent += _contentBottomRightCorner_DrawContent;
 		}
+	}
 
-		/// <summary>
-		/// Only used for a hosted view that will update and redraw the scrollbars.
-		/// </summary>
-		public virtual void Refresh ()
-		{
+	void _contentBottomRightCorner_DrawContent (object sender, DrawEventArgs e) => Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
+
+	void Host_VisibleChanged (object sender, EventArgs e)
+	{
+		if (!Host.Visible) {
+			Visible = Host.Visible;
+			if (_otherScrollBarView != null) {
+				_otherScrollBarView.Visible = Visible;
+			}
+			_contentBottomRightCorner.Visible = Visible;
+		} else {
 			ShowHideScrollBars ();
 		}
+	}
 
-		void ShowHideScrollBars (bool redraw = true)
-		{
-			if (!_hosted || (_hosted && !_autoHideScrollBars)) {
-				if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) {
-					_contentBottomRightCorner.Visible = false;
-				} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) {
-					_otherScrollBarView._contentBottomRightCorner.Visible = false;
-				}
-				return;
-			}
+	void Host_EnabledChanged (object sender, EventArgs e)
+	{
+		Enabled = Host.Enabled;
+		if (_otherScrollBarView != null) {
+			_otherScrollBarView.Enabled = Enabled;
+		}
+		_contentBottomRightCorner.Enabled = Enabled;
+	}
 
-			var pending = CheckBothScrollBars (this);
-			if (_otherScrollBarView != null) {
-				CheckBothScrollBars (_otherScrollBarView, pending);
-			}
+	//private void Host_CanFocusChanged ()
+	//{
+	//	CanFocus = Host.CanFocus;
+	//	if (otherScrollBarView != null) {
+	//		otherScrollBarView.CanFocus = CanFocus;
+	//	}
+	//}
+
+	void ContentBottomRightCorner_MouseClick (object sender, MouseEventEventArgs me)
+	{
+		if (me.MouseEvent.Flags == MouseFlags.WheeledDown ||
+		    me.MouseEvent.Flags == MouseFlags.WheeledUp ||
+		    me.MouseEvent.Flags == MouseFlags.WheeledRight ||
+		    me.MouseEvent.Flags == MouseFlags.WheeledLeft) {
+
+			MouseEvent (me.MouseEvent);
+		} else if (me.MouseEvent.Flags == MouseFlags.Button1Clicked) {
+			Host.SetFocus ();
+		}
 
+		me.Handled = true;
+	}
+
+	void SetInitialProperties (int size, int position, bool isVertical)
+	{
+		Id = "ScrollBarView";
+		_vertical = isVertical;
+		_position = position;
+		_size = size;
+		WantContinuousButtonPressed = true;
+		ClearOnVisibleFalse = false;
+
+		Added += (s, e) => CreateBottomRightCorner ();
+
+		Initialized += (s, e) => {
 			SetWidthHeight ();
-			SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
-			if (_otherScrollBarView != null) {
-				OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+			SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame);
+			// BUGBUG: We're not supposed to use Id internally!
+			if (Id == "OtherScrollBarView" || OtherScrollBarView == null) {
+				// Only do this once if both scrollbars are enabled
+				ShowHideScrollBars ();
 			}
+			SetPosition (position);
+		};
+	}
 
-			if (_showBothScrollIndicator) {
-				if (_contentBottomRightCorner != null) {
-					_contentBottomRightCorner.Visible = true;
-				} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
-					_otherScrollBarView._contentBottomRightCorner.Visible = true;
-				}
-			} else if (!_showScrollIndicator) {
-				if (_contentBottomRightCorner != null) {
-					_contentBottomRightCorner.Visible = false;
-				} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
-					_otherScrollBarView._contentBottomRightCorner.Visible = false;
-				}
-				if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
-					Application.UngrabMouse ();
-				}
-			} else if (_contentBottomRightCorner != null) {
+	/// <summary>
+	/// This event is raised when the position on the scrollbar has changed.
+	/// </summary>
+	public event EventHandler ChangedPosition;
+
+	// Helper to assist Initialized event handler
+	void SetPosition (int newPosition)
+	{
+		if (CanScroll (newPosition - _position, out var max, _vertical)) {
+			if (max == newPosition - _position) {
+				_position = newPosition;
+			} else {
+				_position = Math.Max (_position + max, 0);
+			}
+		} else if (max < 0) {
+			_position = Math.Max (_position + max, 0);
+		} else {
+			_position = Math.Max (newPosition, 0);
+		}
+		OnChangedPosition ();
+		SetNeedsDisplay ();
+	}
+
+	/// <summary>
+	/// Virtual method to invoke the <see cref="ChangedPosition"/> action event.
+	/// </summary>
+	public virtual void OnChangedPosition () => ChangedPosition?.Invoke (this, EventArgs.Empty);
+
+	/// <summary>
+	/// Only used for a hosted view that will update and redraw the scrollbars.
+	/// </summary>
+	public virtual void Refresh () => ShowHideScrollBars ();
+
+	void ShowHideScrollBars (bool redraw = true)
+	{
+		if (!_hosted || _hosted && !_autoHideScrollBars) {
+			if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) {
 				_contentBottomRightCorner.Visible = false;
-			} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
+			} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) {
 				_otherScrollBarView._contentBottomRightCorner.Visible = false;
 			}
-			if (Host?.Visible == true && _showScrollIndicator && !Visible) {
-				Visible = true;
-			}
-			if (Host?.Visible == true && _otherScrollBarView?._showScrollIndicator == true && !_otherScrollBarView.Visible) {
-				_otherScrollBarView.Visible = true;
-			}
+			return;
+		}
 
-			if (!redraw) {
-				return;
-			}
+		var pending = CheckBothScrollBars (this);
+		if (_otherScrollBarView != null) {
+			CheckBothScrollBars (_otherScrollBarView, pending);
+		}
+
+		SetWidthHeight ();
+		SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+		if (_otherScrollBarView != null) {
+			OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+		}
 
-			if (_showScrollIndicator) {
-				Draw ();
+		if (_showBothScrollIndicator) {
+			if (_contentBottomRightCorner != null) {
+				_contentBottomRightCorner.Visible = true;
+			} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
+				_otherScrollBarView._contentBottomRightCorner.Visible = true;
 			}
-			if (_otherScrollBarView != null && _otherScrollBarView._showScrollIndicator) {
-				_otherScrollBarView.Draw ();
+		} else if (!_showScrollIndicator) {
+			if (_contentBottomRightCorner != null) {
+				_contentBottomRightCorner.Visible = false;
+			} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
+				_otherScrollBarView._contentBottomRightCorner.Visible = false;
 			}
-			if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) {
-				_contentBottomRightCorner.Draw ();
-			} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) {
-				_otherScrollBarView._contentBottomRightCorner.Draw ();
+			if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
+				Application.UngrabMouse ();
 			}
+		} else if (_contentBottomRightCorner != null) {
+			_contentBottomRightCorner.Visible = false;
+		} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
+			_otherScrollBarView._contentBottomRightCorner.Visible = false;
+		}
+		if (Host?.Visible == true && _showScrollIndicator && !Visible) {
+			Visible = true;
+		}
+		if (Host?.Visible == true && _otherScrollBarView?._showScrollIndicator == true && !_otherScrollBarView.Visible) {
+			_otherScrollBarView.Visible = true;
 		}
 
-		bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false)
-		{
-			int barsize = scrollBarView._vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width;
+		if (!redraw) {
+			return;
+		}
 
-			if (barsize == 0 || barsize >= scrollBarView._size) {
-				if (scrollBarView._showScrollIndicator) {
-					scrollBarView.ShowScrollIndicator = false;
-				}
-				if (scrollBarView.Visible) {
-					scrollBarView.Visible = false;
-				}
-			} else if (barsize > 0 && barsize == scrollBarView._size && scrollBarView.OtherScrollBarView != null && pending) {
-				if (scrollBarView._showScrollIndicator) {
-					scrollBarView.ShowScrollIndicator = false;
-				}
-				if (scrollBarView.Visible) {
-					scrollBarView.Visible = false;
-				}
-				if (scrollBarView.OtherScrollBarView != null && scrollBarView._showBothScrollIndicator) {
-					scrollBarView.OtherScrollBarView.ShowScrollIndicator = false;
-				}
-				if (scrollBarView.OtherScrollBarView.Visible) {
-					scrollBarView.OtherScrollBarView.Visible = false;
-				}
-			} else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView != null && !pending) {
-				pending = true;
-			} else {
-				if (scrollBarView.OtherScrollBarView != null && pending) {
-					if (!scrollBarView._showBothScrollIndicator) {
-						scrollBarView.OtherScrollBarView.ShowScrollIndicator = true;
-					}
-					if (!scrollBarView.OtherScrollBarView.Visible) {
-						scrollBarView.OtherScrollBarView.Visible = true;
-					}
-				}
-				if (!scrollBarView._showScrollIndicator) {
-					scrollBarView.ShowScrollIndicator = true;
+		if (_showScrollIndicator) {
+			Draw ();
+		}
+		if (_otherScrollBarView != null && _otherScrollBarView._showScrollIndicator) {
+			_otherScrollBarView.Draw ();
+		}
+		if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) {
+			_contentBottomRightCorner.Draw ();
+		} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) {
+			_otherScrollBarView._contentBottomRightCorner.Draw ();
+		}
+	}
+
+	bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false)
+	{
+		var barsize = scrollBarView._vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width;
+
+		if (barsize == 0 || barsize >= scrollBarView._size) {
+			if (scrollBarView._showScrollIndicator) {
+				scrollBarView.ShowScrollIndicator = false;
+			}
+			if (scrollBarView.Visible) {
+				scrollBarView.Visible = false;
+			}
+		} else if (barsize > 0 && barsize == scrollBarView._size && scrollBarView.OtherScrollBarView != null && pending) {
+			if (scrollBarView._showScrollIndicator) {
+				scrollBarView.ShowScrollIndicator = false;
+			}
+			if (scrollBarView.Visible) {
+				scrollBarView.Visible = false;
+			}
+			if (scrollBarView.OtherScrollBarView != null && scrollBarView._showBothScrollIndicator) {
+				scrollBarView.OtherScrollBarView.ShowScrollIndicator = false;
+			}
+			if (scrollBarView.OtherScrollBarView.Visible) {
+				scrollBarView.OtherScrollBarView.Visible = false;
+			}
+		} else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView != null && !pending) {
+			pending = true;
+		} else {
+			if (scrollBarView.OtherScrollBarView != null && pending) {
+				if (!scrollBarView._showBothScrollIndicator) {
+					scrollBarView.OtherScrollBarView.ShowScrollIndicator = true;
 				}
-				if (!scrollBarView.Visible) {
-					scrollBarView.Visible = true;
+				if (!scrollBarView.OtherScrollBarView.Visible) {
+					scrollBarView.OtherScrollBarView.Visible = true;
 				}
 			}
-
-			return pending;
+			if (!scrollBarView._showScrollIndicator) {
+				scrollBarView.ShowScrollIndicator = true;
+			}
+			if (!scrollBarView.Visible) {
+				scrollBarView.Visible = true;
+			}
 		}
 
-		// BUGBUG: v2 - rationalize this with View.SetMinWidthHeight
-		void SetWidthHeight ()
-		{
-			// BUGBUG: v2 - If Host is also the ScrollBarView's superview, this is all bogus because it's not
-			// supported that a view can reference it's superview's Dims. This code also assumes the host does 
-			//  not have a margin/borderframe/padding.
-			if (!IsInitialized) {
-				return;
-			}
+		return pending;
+	}
 
-			if (_showBothScrollIndicator) {
-				Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1;
-				Height = _vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1;
+	// BUGBUG: v2 - rationalize this with View.SetMinWidthHeight
+	void SetWidthHeight ()
+	{
+		// BUGBUG: v2 - If Host is also the ScrollBarView's superview, this is all bogus because it's not
+		// supported that a view can reference it's superview's Dims. This code also assumes the host does 
+		//  not have a margin/borderframe/padding.
+		if (!IsInitialized) {
+			return;
+		}
+
+		if (_showBothScrollIndicator) {
+			Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1;
+			Height = _vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1;
+
+			_otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1;
+			_otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1;
+		} else if (_showScrollIndicator) {
+			Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill ();
+			Height = _vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () : 1;
+		} else if (_otherScrollBarView?._showScrollIndicator == true) {
+			_otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill () - 0;
+			_otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () - 0 : 1;
+		}
+	}
 
-				_otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1;
-				_otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1;
-			} else if (_showScrollIndicator) {
-				Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill ();
-				Height = _vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () : 1;
-			} else if (_otherScrollBarView?._showScrollIndicator == true) {
-				_otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill () - 0;
-				_otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () - 0 : 1;
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		if (ColorScheme == null || (!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) {
+			if ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) {
+				ShowHideScrollBars (false);
 			}
+			return;
 		}
 
-		int _posTopTee;
-		int _posLeftTee;
-		int _posBottomTee;
-		int _posRightTee;
+		if (Size == 0 || _vertical && Bounds.Height == 0 || !_vertical && Bounds.Width == 0) {
+			return;
+		}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			if (ColorScheme == null || ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible)) {
-				if ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) {
-					ShowHideScrollBars (false);
-				}
-				return;
-			}
+		Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
 
-			if (Size == 0 || (_vertical && Bounds.Height == 0) || (!_vertical && Bounds.Width == 0)) {
+		if (_vertical) {
+			if (Bounds.Right < Bounds.Width - 1) {
 				return;
 			}
 
-			Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
+			var col = Bounds.Width - 1;
+			var bh = Bounds.Height;
+			Rune special;
 
-			if (_vertical) {
-				if (Bounds.Right < Bounds.Width - 1) {
-					return;
-				}
-
-				var col = Bounds.Width - 1;
-				var bh = Bounds.Height;
-				Rune special;
+			if (bh < 4) {
+				var by1 = _position * bh / Size;
+				var by2 = (_position + bh) * bh / Size;
 
-				if (bh < 4) {
-					var by1 = _position * bh / Size;
-					var by2 = (_position + bh) * bh / Size;
-
-					Move (col, 0);
-					if (Bounds.Height == 1) {
-						Driver.AddRune (CM.Glyphs.Diamond);
-					} else {
-						Driver.AddRune (CM.Glyphs.UpArrow);
-					}
-					if (Bounds.Height == 3) {
-						Move (col, 1);
-						Driver.AddRune (CM.Glyphs.Diamond);
-					}
-					if (Bounds.Height > 1) {
-						Move (col, Bounds.Height - 1);
-						Driver.AddRune (CM.Glyphs.DownArrow);
-					}
+				Move (col, 0);
+				if (Bounds.Height == 1) {
+					Driver.AddRune (Glyphs.Diamond);
 				} else {
-					bh -= 2;
-					var by1 = KeepContentAlwaysInViewport ? _position * bh / Size : _position * bh / (Size + bh);
-					var by2 = KeepContentAlwaysInViewport ? Math.Min (((_position + bh) * bh / Size) + 1, bh - 1) : (_position + bh) * bh / (Size + bh);
-					if (KeepContentAlwaysInViewport && by1 == by2) {
-						by1 = Math.Max (by1 - 1, 0);
-					}
-
-					Move (col, 0);
-					Driver.AddRune (CM.Glyphs.UpArrow);
-
-					bool hasTopTee = false;
-					bool hasDiamond = false;
-					bool hasBottomTee = false;
-					for (int y = 0; y < bh; y++) {
-						Move (col, y + 1);
-						if ((y < by1 || y > by2) && ((_position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) {
-							special = CM.Glyphs.Stipple;
+					Driver.AddRune (Glyphs.UpArrow);
+				}
+				if (Bounds.Height == 3) {
+					Move (col, 1);
+					Driver.AddRune (Glyphs.Diamond);
+				}
+				if (Bounds.Height > 1) {
+					Move (col, Bounds.Height - 1);
+					Driver.AddRune (Glyphs.DownArrow);
+				}
+			} else {
+				bh -= 2;
+				var by1 = KeepContentAlwaysInViewport ? _position * bh / Size : _position * bh / (Size + bh);
+				var by2 = KeepContentAlwaysInViewport ? Math.Min ((_position + bh) * bh / Size + 1, bh - 1) : (_position + bh) * bh / (Size + bh);
+				if (KeepContentAlwaysInViewport && by1 == by2) {
+					by1 = Math.Max (by1 - 1, 0);
+				}
+
+				Move (col, 0);
+				Driver.AddRune (Glyphs.UpArrow);
+
+				var hasTopTee = false;
+				var hasDiamond = false;
+				var hasBottomTee = false;
+				for (var y = 0; y < bh; y++) {
+					Move (col, y + 1);
+					if ((y < by1 || y > by2) && (_position > 0 && !hasTopTee || hasTopTee && hasBottomTee)) {
+						special = Glyphs.Stipple;
+					} else {
+						if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) {
+							hasDiamond = true;
+							special = Glyphs.Diamond;
 						} else {
-							if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) {
-								hasDiamond = true;
-								special = CM.Glyphs.Diamond;
+							if (y == by1 && !hasTopTee) {
+								hasTopTee = true;
+								_posTopTee = y;
+								special = Glyphs.TopTee;
+							} else if ((_position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) {
+								hasBottomTee = true;
+								_posBottomTee = y;
+								special = Glyphs.BottomTee;
 							} else {
-								if (y == by1 && !hasTopTee) {
-									hasTopTee = true;
-									_posTopTee = y;
-									special = CM.Glyphs.TopTee;
-								} else if ((_position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) {
-									hasBottomTee = true;
-									_posBottomTee = y;
-									special = CM.Glyphs.BottomTee;
-								} else {
-									special = CM.Glyphs.VLine;
-								}
+								special = Glyphs.VLine;
 							}
 						}
-						Driver.AddRune (special);
 					}
-					if (!hasTopTee) {
-						Move (col, Bounds.Height - 2);
-						Driver.AddRune (CM.Glyphs.TopTee);
-					}
-					Move (col, Bounds.Height - 1);
-					Driver.AddRune (CM.Glyphs.DownArrow);
+					Driver.AddRune (special);
 				}
-			} else {
-				if (Bounds.Bottom < Bounds.Height - 1) {
-					return;
+				if (!hasTopTee) {
+					Move (col, Bounds.Height - 2);
+					Driver.AddRune (Glyphs.TopTee);
 				}
+				Move (col, Bounds.Height - 1);
+				Driver.AddRune (Glyphs.DownArrow);
+			}
+		} else {
+			if (Bounds.Bottom < Bounds.Height - 1) {
+				return;
+			}
 
-				var row = Bounds.Height - 1;
-				var bw = Bounds.Width;
-				Rune special;
-
-				if (bw < 4) {
-					var bx1 = _position * bw / Size;
-					var bx2 = (_position + bw) * bw / Size;
-
-					Move (0, row);
-					Driver.AddRune (CM.Glyphs.LeftArrow);
-					Driver.AddRune (CM.Glyphs.RightArrow);
-				} else {
-					bw -= 2;
-					var bx1 = KeepContentAlwaysInViewport ? _position * bw / Size : _position * bw / (Size + bw);
-					var bx2 = KeepContentAlwaysInViewport ? Math.Min (((_position + bw) * bw / Size) + 1, bw - 1) : (_position + bw) * bw / (Size + bw);
-					if (KeepContentAlwaysInViewport && bx1 == bx2) {
-						bx1 = Math.Max (bx1 - 1, 0);
-					}
+			var row = Bounds.Height - 1;
+			var bw = Bounds.Width;
+			Rune special;
 
-					Move (0, row);
-					Driver.AddRune (CM.Glyphs.LeftArrow);
+			if (bw < 4) {
+				var bx1 = _position * bw / Size;
+				var bx2 = (_position + bw) * bw / Size;
 
-					bool hasLeftTee = false;
-					bool hasDiamond = false;
-					bool hasRightTee = false;
-					for (int x = 0; x < bw; x++) {
-						if ((x < bx1 || x >= bx2 + 1) && ((_position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) {
-							special = CM.Glyphs.Stipple;
+				Move (0, row);
+				Driver.AddRune (Glyphs.LeftArrow);
+				Driver.AddRune (Glyphs.RightArrow);
+			} else {
+				bw -= 2;
+				var bx1 = KeepContentAlwaysInViewport ? _position * bw / Size : _position * bw / (Size + bw);
+				var bx2 = KeepContentAlwaysInViewport ? Math.Min ((_position + bw) * bw / Size + 1, bw - 1) : (_position + bw) * bw / (Size + bw);
+				if (KeepContentAlwaysInViewport && bx1 == bx2) {
+					bx1 = Math.Max (bx1 - 1, 0);
+				}
+
+				Move (0, row);
+				Driver.AddRune (Glyphs.LeftArrow);
+
+				var hasLeftTee = false;
+				var hasDiamond = false;
+				var hasRightTee = false;
+				for (var x = 0; x < bw; x++) {
+					if ((x < bx1 || x >= bx2 + 1) && (_position > 0 && !hasLeftTee || hasLeftTee && hasRightTee)) {
+						special = Glyphs.Stipple;
+					} else {
+						if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) {
+							hasDiamond = true;
+							special = Glyphs.Diamond;
 						} else {
-							if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) {
-								hasDiamond = true;
-								special = CM.Glyphs.Diamond;
+							if (x == bx1 && !hasLeftTee) {
+								hasLeftTee = true;
+								_posLeftTee = x;
+								special = Glyphs.LeftTee;
+							} else if ((_position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) {
+								hasRightTee = true;
+								_posRightTee = x;
+								special = Glyphs.RightTee;
 							} else {
-								if (x == bx1 && !hasLeftTee) {
-									hasLeftTee = true;
-									_posLeftTee = x;
-									special = CM.Glyphs.LeftTee;
-								} else if ((_position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) {
-									hasRightTee = true;
-									_posRightTee = x;
-									special = CM.Glyphs.RightTee;
-								} else {
-									special = CM.Glyphs.HLine;
-								}
+								special = Glyphs.HLine;
 							}
 						}
-						Driver.AddRune (special);
 					}
-					if (!hasLeftTee) {
-						Move (Bounds.Width - 2, row);
-						Driver.AddRune (CM.Glyphs.LeftTee);
-					}
-
-					Driver.AddRune (CM.Glyphs.RightArrow);
+					Driver.AddRune (special);
+				}
+				if (!hasLeftTee) {
+					Move (Bounds.Width - 2, row);
+					Driver.AddRune (Glyphs.LeftTee);
 				}
-			}
-		}
-
-		int _lastLocation = -1;
-		int _posBarOffset;
-
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent mouseEvent)
-		{
-			if (mouseEvent.Flags != MouseFlags.Button1Pressed && mouseEvent.Flags != MouseFlags.Button1DoubleClicked &&
-				!mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) &&
-				mouseEvent.Flags != MouseFlags.Button1Released && mouseEvent.Flags != MouseFlags.WheeledDown &&
-				mouseEvent.Flags != MouseFlags.WheeledUp && mouseEvent.Flags != MouseFlags.WheeledRight &&
-				mouseEvent.Flags != MouseFlags.WheeledLeft && mouseEvent.Flags != MouseFlags.Button1TripleClicked) {
 
-				return false;
+				Driver.AddRune (Glyphs.RightArrow);
 			}
+		}
+	}
 
-			if (!Host.CanFocus) {
-				return true;
-			}
-			if (Host?.HasFocus == false) {
-				Host.SetFocus ();
-			}
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent mouseEvent)
+	{
+		if (mouseEvent.Flags != MouseFlags.Button1Pressed &&
+		    mouseEvent.Flags != MouseFlags.Button1DoubleClicked &&
+		    !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) &&
+		    mouseEvent.Flags != MouseFlags.Button1Released &&
+		    mouseEvent.Flags != MouseFlags.WheeledDown &&
+		    mouseEvent.Flags != MouseFlags.WheeledUp &&
+		    mouseEvent.Flags != MouseFlags.WheeledRight &&
+		    mouseEvent.Flags != MouseFlags.WheeledLeft &&
+		    mouseEvent.Flags != MouseFlags.Button1TripleClicked) {
 
-			int location = _vertical ? mouseEvent.Y : mouseEvent.X;
-			int barsize = _vertical ? Bounds.Height : Bounds.Width;
-			int posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1;
-			int posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1;
-			barsize -= 2;
-			var pos = Position;
+			return false;
+		}
 
-			if (mouseEvent.Flags != MouseFlags.Button1Released
-				&& (Application.MouseGrabView == null || Application.MouseGrabView != this)) {
-				Application.GrabMouse (this);
-			} else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) {
-				_lastLocation = -1;
-				Application.UngrabMouse ();
-				return true;
-			}
-			if (_showScrollIndicator && (mouseEvent.Flags == MouseFlags.WheeledDown || mouseEvent.Flags == MouseFlags.WheeledUp ||
-				mouseEvent.Flags == MouseFlags.WheeledRight || mouseEvent.Flags == MouseFlags.WheeledLeft)) {
+		if (!Host.CanFocus) {
+			return true;
+		}
+		if (Host?.HasFocus == false) {
+			Host.SetFocus ();
+		}
 
-				return Host.MouseEvent (mouseEvent);
-			}
+		var location = _vertical ? mouseEvent.Y : mouseEvent.X;
+		var barsize = _vertical ? Bounds.Height : Bounds.Width;
+		var posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1;
+		var posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1;
+		barsize -= 2;
+		var pos = Position;
+
+		if (mouseEvent.Flags != MouseFlags.Button1Released && (Application.MouseGrabView == null || Application.MouseGrabView != this)) {
+			Application.GrabMouse (this);
+		} else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) {
+			_lastLocation = -1;
+			Application.UngrabMouse ();
+			return true;
+		}
+		if (_showScrollIndicator &&
+		    (mouseEvent.Flags == MouseFlags.WheeledDown ||
+		     mouseEvent.Flags == MouseFlags.WheeledUp ||
+		     mouseEvent.Flags == MouseFlags.WheeledRight ||
+		     mouseEvent.Flags == MouseFlags.WheeledLeft)) {
 
-			if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0) {
-				if (pos > 0) {
-					Position = pos - 1;
-				}
-			} else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1) {
-				if (CanScroll (1, out _, _vertical)) {
-					Position = pos + 1;
-				}
-			} else if (location > 0 && location < barsize + 1) {
-				//var b1 = pos * (Size > 0 ? barsize / Size : 0);
-				//var b2 = Size > 0
-				//	? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size)
-				//	: 0;
-				//if (KeepContentAlwaysInViewport && b1 == b2) {
-				//	b1 = Math.Max (b1 - 1, 0);
-				//}
-
-				if (_lastLocation > -1 || (location >= posTopLeftTee && location <= posBottomRightTee
-				&& mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
-					if (_lastLocation == -1) {
-						_lastLocation = location;
-						_posBarOffset = _keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0;
-						return true;
-					}
+			return Host.MouseEvent (mouseEvent);
+		}
 
-					if (location > _lastLocation) {
-						if (location - _posBarOffset < barsize) {
-							var np = ((location - _posBarOffset) * Size / barsize) + (Size / barsize);
-							if (CanScroll (np - pos, out int nv, _vertical)) {
-								Position = pos + nv;
-							}
-						} else if (CanScroll (Size - pos, out int nv, _vertical)) {
-							Position = Math.Min (pos + nv, Size);
-						}
-					} else if (location < _lastLocation) {
-						if (location - _posBarOffset > 0) {
-							var np = ((location - _posBarOffset) * Size / barsize) - (Size / barsize);
-							if (CanScroll (np - pos, out int nv, _vertical)) {
-								Position = pos + nv;
-							}
-						} else {
-							Position = 0;
+		if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0) {
+			if (pos > 0) {
+				Position = pos - 1;
+			}
+		} else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1) {
+			if (CanScroll (1, out _, _vertical)) {
+				Position = pos + 1;
+			}
+		} else if (location > 0 && location < barsize + 1) {
+			//var b1 = pos * (Size > 0 ? barsize / Size : 0);
+			//var b2 = Size > 0
+			//	? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size)
+			//	: 0;
+			//if (KeepContentAlwaysInViewport && b1 == b2) {
+			//	b1 = Math.Max (b1 - 1, 0);
+			//}
+
+			if (_lastLocation > -1 || location >= posTopLeftTee && location <= posBottomRightTee && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
+				if (_lastLocation == -1) {
+					_lastLocation = location;
+					_posBarOffset = _keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0;
+					return true;
+				}
+
+				if (location > _lastLocation) {
+					if (location - _posBarOffset < barsize) {
+						var np = (location - _posBarOffset) * Size / barsize + Size / barsize;
+						if (CanScroll (np - pos, out var nv, _vertical)) {
+							Position = pos + nv;
 						}
-					} else if (location - _posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out int nv, _vertical)) {
-						Position = Math.Min (pos + nv, Size);
-					} else if (location - _posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, _vertical)) {
+					} else if (CanScroll (Size - pos, out var nv, _vertical)) {
 						Position = Math.Min (pos + nv, Size);
-					} else if (location - _posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) {
-						Position = 0;
-					}
-				} else if (location > posBottomRightTee) {
-					if (CanScroll (barsize, out int nv, _vertical)) {
-						Position = pos + nv;
 					}
-				} else if (location < posTopLeftTee) {
-					if (CanScroll (-barsize, out int nv, _vertical)) {
-						Position = pos + nv;
+				} else if (location < _lastLocation) {
+					if (location - _posBarOffset > 0) {
+						var np = (location - _posBarOffset) * Size / barsize - Size / barsize;
+						if (CanScroll (np - pos, out var nv, _vertical)) {
+							Position = pos + nv;
+						}
+					} else {
+						Position = 0;
 					}
-				} else if (location == 1 && posTopLeftTee <= 3) {
+				} else if (location - _posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out var nv, _vertical)) {
+					Position = Math.Min (pos + nv, Size);
+				} else if (location - _posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, _vertical)) {
+					Position = Math.Min (pos + nv, Size);
+				} else if (location - _posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) {
 					Position = 0;
-				} else if (location == barsize) {
-					if (CanScroll (Size - pos, out int nv, _vertical)) {
-						Position = Math.Min (pos + nv, Size);
-					}
+				}
+			} else if (location > posBottomRightTee) {
+				if (CanScroll (barsize, out var nv, _vertical)) {
+					Position = pos + nv;
+				}
+			} else if (location < posTopLeftTee) {
+				if (CanScroll (-barsize, out var nv, _vertical)) {
+					Position = pos + nv;
+				}
+			} else if (location == 1 && posTopLeftTee <= 3) {
+				Position = 0;
+			} else if (location == barsize) {
+				if (CanScroll (Size - pos, out var nv, _vertical)) {
+					Position = Math.Min (pos + nv, Size);
 				}
 			}
-
-			return true;
 		}
 
-		internal bool CanScroll (int n, out int max, bool isVertical = false)
-		{
-			if (Host?.Bounds.IsEmpty != false) {
-				max = 0;
-				return false;
-			}
-			int s = GetBarsize (isVertical);
-			var newSize = Math.Max (Math.Min (_size - s, _position + n), 0);
-			max = _size > s + newSize ? (newSize == 0 ? -_position : n) : _size - (s + _position) - 1;
-			if (_size >= s + newSize && max != 0) {
-				return true;
-			}
+		return true;
+	}
+
+	internal bool CanScroll (int n, out int max, bool isVertical = false)
+	{
+		if (Host?.Bounds.IsEmpty != false) {
+			max = 0;
 			return false;
 		}
+		var s = GetBarsize (isVertical);
+		var newSize = Math.Max (Math.Min (_size - s, _position + n), 0);
+		max = _size > s + newSize ? newSize == 0 ? -_position : n : _size - (s + _position) - 1;
+		if (_size >= s + newSize && max != 0) {
+			return true;
+		}
+		return false;
+	}
 
-		int GetBarsize (bool isVertical)
-		{
-			if (Host?.Bounds.IsEmpty != false) {
-				return 0;
-			}
-			return isVertical ?
-				(KeepContentAlwaysInViewport ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1) : 0) :
-				(KeepContentAlwaysInViewport ? Host.Bounds.Width + (_showBothScrollIndicator ? -2 : -1) : 0);
+	int GetBarsize (bool isVertical)
+	{
+		if (Host?.Bounds.IsEmpty != false) {
+			return 0;
 		}
+		return isVertical ?
+			KeepContentAlwaysInViewport ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1) : 0 :
+			KeepContentAlwaysInViewport ? Host.Bounds.Width + (_showBothScrollIndicator ? -2 : -1) : 0;
+	}
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
-			return base.OnEnter (view);
-		}
+		return base.OnEnter (view);
 	}
-}
+}

+ 16 - 19
Terminal.Gui/Views/Slider.cs

@@ -804,20 +804,17 @@ public class Slider<T> : View {
 		if (!IsInitialized || AutoSize == false) {
 			return;
 		}
-		// Hack???  Otherwise we can't go back to Dim.Absolute.
-		LayoutStyle = LayoutStyle.Absolute;
 		Width = 0;
 		Height = 0;
 		if (_config._sliderOrientation == Orientation.Horizontal) {
 			Bounds = new Rect (Bounds.Location,
-				new Size (int.Min (SuperView.Bounds.Width - GetFramesThickness ().Horizontal, CalcBestLength ()),
-					int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical,    CalcThickness ())));
+				new Size (int.Min (SuperView.Bounds.Width - GetAdornmentsThickness ().Horizontal, CalcBestLength ()),
+					int.Min (SuperView.Bounds.Height - GetAdornmentsThickness ().Vertical, CalcThickness ())));
 		} else {
 			Bounds = new Rect (Bounds.Location,
-				new Size (int.Min (SuperView.Bounds.Width - GetFramesThickness ().Horizontal, CalcThickness ()),
-					int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical,    CalcBestLength ())));
+				new Size (int.Min (SuperView.Bounds.Width - GetAdornmentsThickness ().Horizontal, CalcThickness ()),
+					int.Min (SuperView.Bounds.Height - GetAdornmentsThickness ().Vertical, CalcBestLength ())));
 		}
-		LayoutStyle = LayoutStyle.Computed;
 	}
 
 	/// <summary>
@@ -1050,7 +1047,7 @@ public class Slider<T> : View {
 		// Attributes
 
 		var normalAttr = new Attribute (Color.White, Color.Black);
-		var setAtrr = new Attribute (Color.Black,    Color.White);
+		var setAtrr = new Attribute (Color.Black, Color.White);
 		if (IsInitialized) {
 			normalAttr = ColorScheme?.Normal ?? Application.Current.ColorScheme.Normal;
 			setAtrr = Style.SetChar.Attribute ?? ColorScheme.HotNormal;
@@ -1190,7 +1187,7 @@ public class Slider<T> : View {
 	{
 		// Attributes
 		var normalAttr = new Attribute (Color.White, Color.Black);
-		var setAttr = new Attribute (Color.Black,    Color.White);
+		var setAttr = new Attribute (Color.Black, Color.White);
 		var spaceAttr = normalAttr;
 		if (IsInitialized) {
 			normalAttr = Style.LegendAttributes.NormalAttribute ?? ColorScheme?.Normal ?? ColorScheme.Disabled;
@@ -1462,15 +1459,15 @@ public class Slider<T> : View {
 
 	void SetCommands ()
 	{
-		AddCommand (Command.Right,       () => MovePlus ());
-		AddCommand (Command.LineDown,    () => MovePlus ());
-		AddCommand (Command.Left,        () => MoveMinus ());
-		AddCommand (Command.LineUp,      () => MoveMinus ());
-		AddCommand (Command.LeftHome,    () => MoveStart ());
-		AddCommand (Command.RightEnd,    () => MoveEnd ());
+		AddCommand (Command.Right, () => MovePlus ());
+		AddCommand (Command.LineDown, () => MovePlus ());
+		AddCommand (Command.Left, () => MoveMinus ());
+		AddCommand (Command.LineUp, () => MoveMinus ());
+		AddCommand (Command.LeftHome, () => MoveStart ());
+		AddCommand (Command.RightEnd, () => MoveEnd ());
 		AddCommand (Command.RightExtend, () => ExtendPlus ());
-		AddCommand (Command.LeftExtend,  () => ExtendMinus ());
-		AddCommand (Command.Accept,      () => Set ());
+		AddCommand (Command.LeftExtend, () => ExtendMinus ());
+		AddCommand (Command.Accept, () => Set ());
 
 		SetKeyBindings ();
 	}
@@ -1500,8 +1497,8 @@ public class Slider<T> : View {
 			KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.LeftExtend);
 
 		}
-		KeyBindings.Add (KeyCode.Home,  Command.LeftHome);
-		KeyBindings.Add (KeyCode.End,   Command.RightEnd);
+		KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+		KeyBindings.Add (KeyCode.End, Command.RightEnd);
 		KeyBindings.Add (KeyCode.Enter, Command.Accept);
 		KeyBindings.Add (KeyCode.Space, Command.Accept);
 

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

@@ -111,7 +111,7 @@ public class StatusBar : View {
 			Items = items;
 		}
 		CanFocus = false;
-		ColorScheme = Colors.Menu;
+		ColorScheme = Colors.ColorSchemes ["Menu"];
 		X = 0;
 		Y = Pos.AnchorEnd (1);
 		Width = Dim.Fill ();

+ 9 - 19
Terminal.Gui/Views/Tab.cs

@@ -1,39 +1,29 @@
 namespace Terminal.Gui; 
 
 /// <summary>
-/// A single tab in a <see cref="TabView"/>
+/// A single tab in a <see cref="TabView"/>.
 /// </summary>
-public class Tab {
-	private string text;
+public class Tab : View {
+	private string _displayText;
 
 	/// <summary>
-	/// The text to display in a <see cref="TabView"/>
+	/// The text to display in a <see cref="TabView"/>.
 	/// </summary>
 	/// <value></value>
-	public string Text { get => text ?? "Unamed"; set => text = value; }
+	public string DisplayText { get => _displayText ?? "Unamed"; set => _displayText = value; }
 
 	/// <summary>
-	/// The control to display when the tab is selected
+	/// The control to display when the tab is selected.
 	/// </summary>
 	/// <value></value>
 	public View View { get; set; }
 
 	/// <summary>
-	/// Creates a new unamed tab with no controls inside
+	/// Creates a new unamed tab with no controls inside.
 	/// </summary>
 	public Tab ()
 	{
-
-	}
-
-	/// <summary>
-	/// Creates a new tab with the given text hosting a view
-	/// </summary>
-	/// <param name="text"></param>
-	/// <param name="view"></param>
-	public Tab (string text, View view)
-	{
-		this.Text = text;
-		this.View = view;
+		BorderStyle = LineStyle.Rounded;
+		CanFocus = true;
 	}
 }

+ 737 - 545
Terminal.Gui/Views/TabView.cs

@@ -1,737 +1,929 @@
-using System.Text;
 using System;
 using System.Collections.Generic;
-using System.Data;
 using System.Linq;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+
+/// <summary>
+/// Control that hosts multiple sub views, presenting a single one at once.
+/// </summary>
+public class TabView : View {
+	private Tab _selectedTab;
 
 	/// <summary>
-	/// Control that hosts multiple sub views, presenting a single one at once
+	/// The default <see cref="MaxTabTextWidth"/> to set on new <see cref="TabView"/> controls.
 	/// </summary>
-	public class TabView : View {
-		private Tab selectedTab;
-
-		/// <summary>
-		/// The default <see cref="MaxTabTextWidth"/> to set on new <see cref="TabView"/> controls
-		/// </summary>
-		public const uint DefaultMaxTabTextWidth = 30;
-
-		/// <summary>
-		/// This sub view is the 2 or 3 line control that represents the actual tabs themselves
-		/// </summary>
-		TabRowView tabsBar;
-
-		/// <summary>
-		/// This sub view is the main client area of the current tab.  It hosts the <see cref="Tab.View"/> 
-		/// of the tab, the <see cref="SelectedTab"/>
-		/// </summary>
-		View contentView;
-		private List<Tab> tabs = new List<Tab> ();
+	public const uint DefaultMaxTabTextWidth = 30;
 
-		/// <summary>
-		/// All tabs currently hosted by the control
-		/// </summary>
-		/// <value></value>
-		public IReadOnlyCollection<Tab> Tabs { get => tabs.AsReadOnly (); }
-
-		/// <summary>
-		/// When there are too many tabs to render, this indicates the first
-		/// tab to render on the screen.
-		/// </summary>
-		/// <value></value>
-		public int TabScrollOffset { get; set; }
+	/// <summary>
+	/// This sub view is the 2 or 3 line control that represents the actual tabs themselves.
+	/// </summary>
+	TabRowView _tabsBar;
 
-		/// <summary>
-		/// The maximum number of characters to render in a Tab header.  This prevents one long tab 
-		/// from pushing out all the others.
-		/// </summary>
-		public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth;
+	/// <summary>
+	/// This sub view is the main client area of the current tab.  It hosts the <see cref="Tab.View"/> 
+	/// of the tab, the <see cref="SelectedTab"/>.
+	/// </summary>
+	View _contentView;
+	private List<Tab> _tabs = new List<Tab> ();
 
-		/// <summary>
-		/// Event for when <see cref="SelectedTab"/> changes
-		/// </summary>
-		public event EventHandler<TabChangedEventArgs> SelectedTabChanged;
+	/// <summary>
+	/// All tabs currently hosted by the control.
+	/// </summary>
+	/// <value></value>
+	public IReadOnlyCollection<Tab> Tabs { get => _tabs.AsReadOnly (); }
 
-		/// <summary>
-		/// Event fired when a <see cref="Tab"/> is clicked.  Can be used to cancel navigation,
-		/// show context menu (e.g. on right click) etc.
-		/// </summary>
-		public event EventHandler<TabMouseEventArgs> TabClicked;
+	/// <summary>
+	/// When there are too many tabs to render, this indicates the first
+	/// tab to render on the screen.
+	/// </summary>
+	/// <value></value>
+	public int TabScrollOffset {
+		get => _tabScrollOffset;
+		set {
+			_tabScrollOffset = EnsureValidScrollOffsets (value);
+		}
+	}
 
-		/// <summary>
-		/// The currently selected member of <see cref="Tabs"/> chosen by the user
-		/// </summary>
-		/// <value></value>
-		public Tab SelectedTab {
-			get => selectedTab;
-			set {
+	/// <summary>
+	/// The maximum number of characters to render in a Tab header.  This prevents one long tab 
+	/// from pushing out all the others.
+	/// </summary>
+	public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth;
 
-				var old = selectedTab;
+	/// <summary>
+	/// Event for when <see cref="SelectedTab"/> changes.
+	/// </summary>
+	public event EventHandler<TabChangedEventArgs> SelectedTabChanged;
 
-				if (selectedTab != null) {
+	/// <summary>
+	/// Event fired when a <see cref="Tab"/> is clicked.  Can be used to cancel navigation,
+	/// show context menu (e.g. on right click) etc.
+	/// </summary>
+	public event EventHandler<TabMouseEventArgs> TabClicked;
 
-					if (selectedTab.View != null) {
-						// remove old content
-						contentView.Remove (selectedTab.View);
-					}
+	/// <summary>
+	/// The currently selected member of <see cref="Tabs"/> chosen by the user.
+	/// </summary>
+	/// <value></value>
+	public Tab SelectedTab {
+		get => _selectedTab;
+		set {
+			UnSetCurrentTabs ();
+
+			var old = _selectedTab;
+
+			if (_selectedTab != null) {
+				if (_selectedTab.View != null) {
+					// remove old content
+					_contentView.Remove (_selectedTab.View);
 				}
+			}
 
-				selectedTab = value;
-
-				if (value != null) {
+			_selectedTab = value;
 
-					// add new content
-					if (selectedTab.View != null) {
-						contentView.Add (selectedTab.View);
-					}
+			if (value != null) {
+				// add new content
+				if (_selectedTab.View != null) {
+					_contentView.Add (_selectedTab.View);
 				}
+			}
 
-				EnsureSelectedTabIsVisible ();
+			EnsureSelectedTabIsVisible ();
 
-				if (old != value) {
-					OnSelectedTabChanged (old, value);
+			if (old != value) {
+				if (old?.HasFocus == true) {
+					SelectedTab.SetFocus ();
 				}
-
+				OnSelectedTabChanged (old, value);
 			}
 		}
+	}
 
-		/// <summary>
-		/// Render choices for how to display tabs.  After making changes, call <see cref="ApplyStyleChanges()"/>
-		/// </summary>
-		/// <value></value>
-		public TabStyle Style { get; set; } = new TabStyle ();
-
-		/// <summary>
-		/// Initializes a <see cref="TabView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		public TabView () : base ()
-		{
-			CanFocus = true;
-			contentView = new View ();
-			tabsBar = new TabRowView (this);
+	/// <summary>
+	/// Render choices for how to display tabs.  After making changes, call <see cref="ApplyStyleChanges()"/>.
+	/// </summary>
+	/// <value></value>
+	public TabStyle Style { get; set; } = new TabStyle ();
 
-			ApplyStyleChanges ();
+	/// <summary>
+	/// Initializes a <see cref="TabView"/> class using <see cref="LayoutStyle.Computed"/> layout.
+	/// </summary>
+	public TabView () : base ()
+	{
+		CanFocus = true;
+		_tabsBar = new TabRowView (this);
+		_contentView = new View ();
+
+		ApplyStyleChanges ();
+
+		base.Add (_tabsBar);
+		base.Add (_contentView);
+
+		// Things this view knows how to do
+		AddCommand (Command.Left, () => { SwitchTabBy (-1); return true; });
+		AddCommand (Command.Right, () => { SwitchTabBy (1); return true; });
+		AddCommand (Command.LeftHome, () => { TabScrollOffset = 0; SelectedTab = Tabs.FirstOrDefault (); return true; });
+		AddCommand (Command.RightEnd, () => { TabScrollOffset = Tabs.Count - 1; SelectedTab = Tabs.LastOrDefault (); return true; });
+		AddCommand (Command.NextView, () => { _contentView.SetFocus (); return true; });
+		AddCommand (Command.PreviousView, () => { SuperView?.FocusPrev (); return true; });
+		AddCommand (Command.PageDown, () => { TabScrollOffset += _tabLocations.Length; SelectedTab = Tabs.ElementAt (TabScrollOffset); return true; });
+		AddCommand (Command.PageUp, () => { TabScrollOffset -= _tabLocations.Length; SelectedTab = Tabs.ElementAt (TabScrollOffset); return true; });
+
+		// Default keybindings for this view
+		KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+		KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+		KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+		KeyBindings.Add (KeyCode.End, Command.RightEnd);
+		KeyBindings.Add (KeyCode.CursorDown, Command.NextView);
+		KeyBindings.Add (KeyCode.CursorUp, Command.PreviousView);
+		KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+		KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+	}
 
-			base.Add (tabsBar);
-			base.Add (contentView);
+	/// <summary>
+	/// Updates the control to use the latest state settings in <see cref="Style"/>.
+	/// This can change the size of the client area of the tab (for rendering the 
+	/// selected tab's content).  This method includes a call 
+	/// to <see cref="View.SetNeedsDisplay()"/>.
+	/// </summary>
+	public void ApplyStyleChanges ()
+	{
+		_contentView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None;
+		_contentView.Width = Dim.Fill ();
 
-			// Things this view knows how to do
-			AddCommand (Command.Left, () => { SwitchTabBy (-1); return true; });
-			AddCommand (Command.Right, () => { SwitchTabBy (1); return true; });
-			AddCommand (Command.LeftHome, () => { SelectedTab = Tabs.FirstOrDefault (); return true; });
-			AddCommand (Command.RightEnd, () => { SelectedTab = Tabs.LastOrDefault (); return true; });
+		if (Style.TabsOnBottom) {
+			// Tabs are along the bottom so just dodge the border
+			if (Style.ShowBorder) {
+				_contentView.Border.Thickness = new Thickness (1, 1, 1, 0);
+			}
 
-			// Default keybindings for this view
-			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
-			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
-			KeyBindings.Add (KeyCode.Home, Command.LeftHome);
-			KeyBindings.Add (KeyCode.End, Command.RightEnd);
-		}
+			_contentView.Y = 0;
 
-		/// <summary>
-		/// Updates the control to use the latest state settings in <see cref="Style"/>.
-		/// This can change the size of the client area of the tab (for rendering the 
-		/// selected tab's content).  This method includes a call 
-		/// to <see cref="View.SetNeedsDisplay()"/>
-		/// </summary>
-		public void ApplyStyleChanges ()
-		{
-			contentView.X = Style.ShowBorder ? 1 : 0;
-			contentView.Width = Dim.Fill (Style.ShowBorder ? 1 : 0);
+			var tabHeight = GetTabHeight (false);
 
-			if (Style.TabsOnBottom) {
-				// Tabs are along the bottom so just dodge the border
-				contentView.Y = Style.ShowBorder ? 1 : 0;
+			// Fill client area leaving space at bottom for tabs
+			_contentView.Height = Dim.Fill (tabHeight);
 
-				// Fill client area leaving space at bottom for tabs
-				contentView.Height = Dim.Fill (GetTabHeight (false));
+			_tabsBar.Height = tabHeight;
 
-				var tabHeight = GetTabHeight (false);
-				tabsBar.Height = tabHeight;
+			_tabsBar.Y = Pos.Bottom (_contentView);
 
-				tabsBar.Y = Pos.Percent (100) - tabHeight;
+		} else {
 
-			} else {
+			// Tabs are along the top
+			if (Style.ShowBorder) {
+				_contentView.Border.Thickness = new Thickness (1, 0, 1, 1);
+			}
 
-				// Tabs are along the top
+			_tabsBar.Y = 0;
 
-				var tabHeight = GetTabHeight (true);
+			var tabHeight = GetTabHeight (true);
 
-				//move content down to make space for tabs
-				contentView.Y = tabHeight;
+			//move content down to make space for tabs
+			_contentView.Y = Pos.Bottom (_tabsBar);
 
-				// Fill client area leaving space at bottom for border
-				contentView.Height = Dim.Fill (Style.ShowBorder ? 1 : 0);
+			// Fill client area leaving space at bottom for border
+			_contentView.Height = Dim.Fill ();
 
-				// The top tab should be 2 or 3 rows high and on the top
+			// The top tab should be 2 or 3 rows high and on the top
 
-				tabsBar.Height = tabHeight;
+			_tabsBar.Height = tabHeight;
 
-				// Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0
-				tabsBar.Y = Pos.Percent (0);
-			}
-			if (IsInitialized) {
-				LayoutSubviews ();
-			}
-			SetNeedsDisplay ();
+			// Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0
 		}
+		if (IsInitialized) {
+			LayoutSubviews ();
+		}
+		SetNeedsDisplay ();
+	}
 
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		Driver.SetAttribute (GetNormalColor ());
+
+		if (Tabs.Any ()) {
+			var savedClip = ClipToBounds ();
+			_tabsBar.OnDrawContent (contentArea);
+			_contentView.SetNeedsDisplay ();
+			_contentView.Draw ();
+			Driver.Clip = savedClip;
+		}
+	}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			Move (0, 0);
-			Driver.SetAttribute (GetNormalColor ());
-
-			if (Style.ShowBorder) {
+	///<inheritdoc/>
+	public override void OnDrawContentComplete (Rect contentArea)
+	{
+		_tabsBar.OnDrawContentComplete (contentArea);
+	}
 
-				// How much space do we need to leave at the bottom to show the tabs
-				int spaceAtBottom = Math.Max (0, GetTabHeight (false) - 1);
-				int startAtY = Math.Max (0, GetTabHeight (true) - 1);
+	/// <summary>
+	/// Disposes the control and all <see cref="Tabs"/>.
+	/// </summary>
+	/// <param name="disposing"></param>
+	protected override void Dispose (bool disposing)
+	{
+		base.Dispose (disposing);
 
-				Border.DrawFrame (new Rect (0, startAtY, Bounds.Width,
-				Math.Max (Bounds.Height - spaceAtBottom - startAtY, 0)), false);
-			}
+		// The selected tab will automatically be disposed but
+		// any tabs not visible will need to be manually disposed
 
-			if (Tabs.Any ()) {
-				tabsBar.OnDrawContent (contentArea);
-				contentView.SetNeedsDisplay ();
-				var savedClip = contentView.ClipToBounds ();
-				contentView.Draw ();
-				Driver.Clip = savedClip;
+		foreach (var tab in Tabs) {
+			if (!Equals (SelectedTab, tab)) {
+				tab.View?.Dispose ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Disposes the control and all <see cref="Tabs"/>
-		/// </summary>
-		/// <param name="disposing"></param>
-		protected override void Dispose (bool disposing)
-		{
-			base.Dispose (disposing);
+	/// <summary>
+	/// Raises the <see cref="SelectedTabChanged"/> event.
+	/// </summary>
+	protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab)
+	{
 
-			// The selected tab will automatically be disposed but
-			// any tabs not visible will need to be manually disposed
+		SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab));
+	}
 
-			foreach (var tab in Tabs) {
-				if (!Equals (SelectedTab, tab)) {
-					tab.View?.Dispose ();
-				}
+	/// <summary>
+	/// Changes the <see cref="SelectedTab"/> by the given <paramref name="amount"/>. 
+	/// Positive for right, negative for left.  If no tab is currently selected then
+	/// the first tab will become selected.
+	/// </summary>
+	/// <param name="amount"></param>
+	public void SwitchTabBy (int amount)
+	{
+		if (Tabs.Count == 0) {
+			return;
+		}
 
-			}
+		// if there is only one tab anyway or nothing is selected
+		if (Tabs.Count == 1 || SelectedTab == null) {
+			SelectedTab = Tabs.ElementAt (0);
+			SetNeedsDisplay ();
+			return;
 		}
 
-		/// <summary>
-		/// Raises the <see cref="SelectedTabChanged"/> event
-		/// </summary>
-		protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab)
-		{
+		var currentIdx = Tabs.IndexOf (SelectedTab);
 
-			SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab));
+		// Currently selected tab has vanished!
+		if (currentIdx == -1) {
+			SelectedTab = Tabs.ElementAt (0);
+			SetNeedsDisplay ();
+			return;
 		}
 
-		/// <summary>
-		/// Changes the <see cref="SelectedTab"/> by the given <paramref name="amount"/>.  
-		/// Positive for right, negative for left.  If no tab is currently selected then
-		/// the first tab will become selected
-		/// </summary>
-		/// <param name="amount"></param>
-		public void SwitchTabBy (int amount)
-		{
-			if (Tabs.Count == 0) {
-				return;
-			}
+		var newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1));
 
-			// if there is only one tab anyway or nothing is selected
-			if (Tabs.Count == 1 || SelectedTab == null) {
-				SelectedTab = Tabs.ElementAt (0);
-				SetNeedsDisplay ();
-				return;
-			}
+		SelectedTab = _tabs [newIdx];
+		SetNeedsDisplay ();
 
-			var currentIdx = Tabs.IndexOf (SelectedTab);
+		EnsureSelectedTabIsVisible ();
+	}
 
-			// Currently selected tab has vanished!
-			if (currentIdx == -1) {
-				SelectedTab = Tabs.ElementAt (0);
-				SetNeedsDisplay ();
-				return;
-			}
+	/// <summary>
+	/// Updates <see cref="TabScrollOffset"/> to be a valid index of <see cref="Tabs"/>.
+	/// </summary>
+	/// <param name="value">The value to validate.</param>
+	/// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDisplay()"/>.</remarks>
+	/// <returns>The valid <see cref="TabScrollOffset"/> for the given value.</returns>
+	public int EnsureValidScrollOffsets (int value)
+	{
+		return Math.Max (Math.Min (value, Tabs.Count - 1), 0);
+	}
 
-			var newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1));
+	/// <summary>
+	/// Updates <see cref="TabScrollOffset"/> to ensure that <see cref="SelectedTab"/> is visible.
+	/// </summary>
+	public void EnsureSelectedTabIsVisible ()
+	{
+		if (!IsInitialized || SelectedTab == null) {
+			return;
+		}
 
-			SelectedTab = tabs [newIdx];
-			SetNeedsDisplay ();
+		// if current viewport does not include the selected tab
+		if (!CalculateViewport (Bounds).Any (r => Equals (SelectedTab, r.Tab))) {
 
-			EnsureSelectedTabIsVisible ();
+			// Set scroll offset so the first tab rendered is the
+			TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab));
 		}
+	}
 
-		/// <summary>
-		/// Updates <see cref="TabScrollOffset"/> to be a valid index of <see cref="Tabs"/>
-		/// </summary>
-		/// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDisplay()"/></remarks>
-		public void EnsureValidScrollOffsets ()
-		{
-			TabScrollOffset = Math.Max (Math.Min (TabScrollOffset, Tabs.Count - 1), 0);
+	/// <summary>
+	/// Returns the number of rows occupied by rendering the tabs, this depends 
+	/// on <see cref="TabStyle.ShowTopLine"/> and can be 0 (e.g. if 
+	/// <see cref="TabStyle.TabsOnBottom"/> and you ask for <paramref name="top"/>).
+	/// </summary>
+	/// <param name="top">True to measure the space required at the top of the control,
+	/// false to measure space at the bottom.</param>.
+	/// <returns></returns>
+	private int GetTabHeight (bool top)
+	{
+		if (top && Style.TabsOnBottom) {
+			return 0;
 		}
 
-		/// <summary>
-		/// Updates <see cref="TabScrollOffset"/> to ensure that <see cref="SelectedTab"/> is visible
-		/// </summary>
-		public void EnsureSelectedTabIsVisible ()
-		{
-			if (!IsInitialized || SelectedTab == null) {
-				return;
-			}
-
-			// if current viewport does not include the selected tab
-			if (!CalculateViewport (Bounds).Any (r => Equals (SelectedTab, r.Tab))) {
-
-				// Set scroll offset so the first tab rendered is the
-				TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab));
-			}
+		if (!top && !Style.TabsOnBottom) {
+			return 0;
 		}
 
-		/// <summary>
-		/// Returns the number of rows occupied by rendering the tabs, this depends 
-		/// on <see cref="TabStyle.ShowTopLine"/> and can be 0 (e.g. if 
-		/// <see cref="TabStyle.TabsOnBottom"/> and you ask for <paramref name="top"/>).
-		/// </summary>
-		/// <param name="top">True to measure the space required at the top of the control,
-		/// false to measure space at the bottom</param>
-		/// <returns></returns>
-		private int GetTabHeight (bool top)
-		{
-			if (top && Style.TabsOnBottom) {
-				return 0;
-			}
+		return Style.ShowTopLine ? 3 : 2;
+	}
 
-			if (!top && !Style.TabsOnBottom) {
-				return 0;
-			}
+	private TabToRender [] _tabLocations;
+	private int _tabScrollOffset;
 
-			return Style.ShowTopLine ? 3 : 2;
-		}
+	/// <summary>
+	/// Returns which tabs to render at each x location.
+	/// </summary>
+	/// <returns></returns>
+	private IEnumerable<TabToRender> CalculateViewport (Rect bounds)
+	{
+		UnSetCurrentTabs ();
 
-		/// <summary>
-		/// Returns which tabs to render at each x location
-		/// </summary>
-		/// <returns></returns>
-		private IEnumerable<TabToRender> CalculateViewport (Rect bounds)
-		{
-			int i = 1;
+		int i = 1;
+		View prevTab = null;
 
-			// Starting at the first or scrolled to tab
-			foreach (var tab in Tabs.Skip (TabScrollOffset)) {
+		// Starting at the first or scrolled to tab
+		foreach (var tab in Tabs.Skip (TabScrollOffset)) {
 
-				// while there is space for the tab
-				var tabTextWidth = tab.Text.EnumerateRunes ().Sum (c => c.GetColumns ());
+			if (prevTab != null) {
+				tab.X = Pos.Right (prevTab);
+			} else {
+				tab.X = 0;
+			}
+			tab.Y = 0;
 
-				string text = tab.Text;
+			// while there is space for the tab
+			var tabTextWidth = tab.DisplayText.EnumerateRunes ().Sum (c => c.GetColumns ());
 
-				// The maximum number of characters to use for the tab name as specified
-				// by the user (MaxTabTextWidth).  But not more than the width of the view
-				// or we won't even be able to render a single tab!
-				var maxWidth = Math.Max (0, Math.Min (bounds.Width - 3, MaxTabTextWidth));
+			string text = tab.DisplayText;
 
-				// if tab view is width <= 3 don't render any tabs
-				if (maxWidth == 0) {
-					yield return new TabToRender (i, tab, string.Empty, Equals (SelectedTab, tab), 0);
-					break;
-				}
+			// The maximum number of characters to use for the tab name as specified
+			// by the user (MaxTabTextWidth).  But not more than the width of the view
+			// or we won't even be able to render a single tab!
+			var maxWidth = Math.Max (0, Math.Min (bounds.Width - 3, MaxTabTextWidth));
 
-				if (tabTextWidth > maxWidth) {
-					text = tab.Text.Substring (0, (int)maxWidth);
-					tabTextWidth = (int)maxWidth;
-				}
+			prevTab = tab;
 
-				// if there is not enough space for this tab
-				if (i + tabTextWidth >= bounds.Width) {
-					break;
-				}
+			tab.Width = 2;
+			tab.Height = Style.ShowTopLine ? 3 : 2;
 
-				// there is enough space!
-				yield return new TabToRender (i, tab, text, Equals (SelectedTab, tab), tabTextWidth);
-				i += tabTextWidth + 1;
+			// if tab view is width <= 3 don't render any tabs
+			if (maxWidth == 0) {
+				tab.Visible = true;
+				tab.MouseClick += Tab_MouseClick;
+				yield return new TabToRender (i, tab, string.Empty, Equals (SelectedTab, tab), 0);
+				break;
 			}
-		}
 
-		/// <summary>
-		/// Adds the given <paramref name="tab"/> to <see cref="Tabs"/>
-		/// </summary>
-		/// <param name="tab"></param>
-		/// <param name="andSelect">True to make the newly added Tab the <see cref="SelectedTab"/></param>
-		public void AddTab (Tab tab, bool andSelect)
-		{
-			if (tabs.Contains (tab)) {
-				return;
+			if (tabTextWidth > maxWidth) {
+				text = tab.DisplayText.Substring (0, (int)maxWidth);
+				tabTextWidth = (int)maxWidth;
 			}
 
-			tabs.Add (tab);
-
-			if (SelectedTab == null || andSelect) {
-				SelectedTab = tab;
+			tab.Width = Math.Max (tabTextWidth + 2, 1);
+			tab.Height = Style.ShowTopLine ? 3 : 2;
 
-				EnsureSelectedTabIsVisible ();
-
-				tab.View?.SetFocus ();
+			// if there is not enough space for this tab
+			if (i + tabTextWidth >= bounds.Width) {
+				tab.Visible = false;
+				break;
 			}
 
-			SetNeedsDisplay ();
+			// there is enough space!
+			tab.Visible = true;
+			tab.MouseClick += Tab_MouseClick;
+			yield return new TabToRender (i, tab, text, Equals (SelectedTab, tab), tabTextWidth);
+			i += tabTextWidth + 1;
 		}
+	}
 
-		/// <summary>
-		/// Removes the given <paramref name="tab"/> from <see cref="Tabs"/>.
-		/// Caller is responsible for disposing the tab's hosted <see cref="Tab.View"/>
-		/// if appropriate.
-		/// </summary>
-		/// <param name="tab"></param>
-		public void RemoveTab (Tab tab)
-		{
-			if (tab == null || !tabs.Contains (tab)) {
-				return;
+	private void UnSetCurrentTabs ()
+	{
+		if (_tabLocations != null) {
+			foreach (var tabToRender in _tabLocations) {
+				tabToRender.Tab.MouseClick -= Tab_MouseClick;
+				tabToRender.Tab.Visible = false;
 			}
+			_tabLocations = null;
+		}
+	}
 
-			// what tab was selected before closing
-			var idx = tabs.IndexOf (tab);
+	private void Tab_MouseClick (object sender, MouseEventEventArgs e)
+	{
+		e.Handled = _tabsBar.MouseEvent (e.MouseEvent);
+	}
 
-			tabs.Remove (tab);
+	/// <summary>
+	/// Adds the given <paramref name="tab"/> to <see cref="Tabs"/>.
+	/// </summary>
+	/// <param name="tab"></param>
+	/// <param name="andSelect">True to make the newly added Tab the <see cref="SelectedTab"/>.</param>
+	public void AddTab (Tab tab, bool andSelect)
+	{
+		if (_tabs.Contains (tab)) {
+			return;
+		}
 
-			// if the currently selected tab is no longer a member of Tabs
-			if (SelectedTab == null || !Tabs.Contains (SelectedTab)) {
-				// select the tab closest to the one that disappeared
-				var toSelect = Math.Max (idx - 1, 0);
+		_tabs.Add (tab);
+		_tabsBar.Add (tab);
 
-				if (toSelect < Tabs.Count) {
-					SelectedTab = Tabs.ElementAt (toSelect);
-				} else {
-					SelectedTab = Tabs.LastOrDefault ();
-				}
-
-			}
+		if (SelectedTab == null || andSelect) {
+			SelectedTab = tab;
 
 			EnsureSelectedTabIsVisible ();
-			SetNeedsDisplay ();
+
+			tab.View?.SetFocus ();
 		}
 
-		private class TabToRender {
-			public int X { get; set; }
-			public Tab Tab { get; set; }
-
-			/// <summary>
-			/// True if the tab that is being rendered is the selected one
-			/// </summary>
-			/// <value></value>
-			public bool IsSelected { get; set; }
-			public int Width { get; }
-			public string TextToRender { get; }
-
-			public TabToRender (int x, Tab tab, string textToRender, bool isSelected, int width)
-			{
-				X = x;
-				Tab = tab;
-				IsSelected = isSelected;
-				Width = width;
-				TextToRender = textToRender;
-			}
+		SetNeedsDisplay ();
+	}
+
+	/// <summary>
+	/// Removes the given <paramref name="tab"/> from <see cref="Tabs"/>.
+	/// Caller is responsible for disposing the tab's hosted <see cref="Tab.View"/>
+	/// if appropriate.
+	/// </summary>
+	/// <param name="tab"></param>
+	public void RemoveTab (Tab tab)
+	{
+		if (tab == null || !_tabs.Contains (tab)) {
+			return;
 		}
 
-		private class TabRowView : View {
+		// what tab was selected before closing
+		var idx = _tabs.IndexOf (tab);
 
-			readonly TabView host;
+		_tabs.Remove (tab);
 
-			public TabRowView (TabView host)
-			{
-				this.host = host;
+		// if the currently selected tab is no longer a member of Tabs
+		if (SelectedTab == null || !Tabs.Contains (SelectedTab)) {
+			// select the tab closest to the one that disappeared
+			var toSelect = Math.Max (idx - 1, 0);
 
-				CanFocus = true;
-				Height = 1;
-				Width = Dim.Fill ();
+			if (toSelect < Tabs.Count) {
+				SelectedTab = Tabs.ElementAt (toSelect);
+			} else {
+				SelectedTab = Tabs.LastOrDefault ();
 			}
 
-			public override bool OnEnter (View view)
-			{
-				Driver.SetCursorVisibility (CursorVisibility.Invisible);
-				return base.OnEnter (view);
-			}
+		}
 
-			public override void OnDrawContent (Rect contentArea)
-			{
-				var tabLocations = host.CalculateViewport (Bounds).ToArray ();
-				var width = Bounds.Width;
-				Driver.SetAttribute (GetNormalColor ());
+		EnsureSelectedTabIsVisible ();
+		SetNeedsDisplay ();
+	}
 
-				if (host.Style.ShowTopLine) {
-					RenderOverline (tabLocations, width);
-				}
+	private class TabToRender {
+		public int X { get; set; }
+		public Tab Tab { get; set; }
 
-				RenderTabLine (tabLocations, width);
+		/// <summary>
+		/// True if the tab that is being rendered is the selected one.
+		/// </summary>
+		/// <value></value>
+		public bool IsSelected { get; set; }
+		public int Width { get; }
+		public string TextToRender { get; }
 
-				RenderUnderline (tabLocations, width);
-				Driver.SetAttribute (GetNormalColor ());
-			}
+		public TabToRender (int x, Tab tab, string textToRender, bool isSelected, int width)
+		{
+			X = x;
+			Tab = tab;
+			IsSelected = isSelected;
+			Width = width;
+			TextToRender = textToRender;
+		}
+	}
 
-			/// <summary>
-			/// Renders the line of the tabs that does not adjoin the content
-			/// </summary>
-			/// <param name="tabLocations"></param>
-			/// <param name="width"></param>
-			private void RenderOverline (TabToRender [] tabLocations, int width)
-			{
-				// if tabs are on the bottom draw the side of the tab that doesn't border the content area at the bottom otherwise the top
-				int y = host.Style.TabsOnBottom ? 2 : 0;
+	private class TabRowView : View {
+		readonly TabView _host;
+		View _rightScrollIndicator;
+		View _leftScrollIndicator;
 
-				Move (0, y);
+		public TabRowView (TabView host)
+		{
+			this._host = host;
 
-				var selected = tabLocations.FirstOrDefault (t => t.IsSelected);
+			CanFocus = true;
+			Height = 1;
+			Width = Dim.Fill ();
 
-				// Clear out everything
-				Driver.AddStr (new string (' ', width));
+			_rightScrollIndicator = new View () { Id = "rightScrollIndicator", Width = 1, Height = 1, Visible = false, Text = CM.Glyphs.RightArrow.ToString () };
+			_rightScrollIndicator.MouseClick += _host.Tab_MouseClick;
+			_leftScrollIndicator = new View () { Id = "leftScrollIndicator", Width = 1, Height = 1, Visible = false, Text = CM.Glyphs.LeftArrow.ToString () };
+			_leftScrollIndicator.MouseClick += _host.Tab_MouseClick;
 
-				// Nothing is selected... odd but we are done
-				if (selected == null) {
-					return;
-				}
+			Add (_rightScrollIndicator, _leftScrollIndicator);
+		}
 
-				Move (selected.X - 1, y);
-				Driver.AddRune (host.Style.TabsOnBottom ? CM.Glyphs.LLCorner : CM.Glyphs.ULCorner);
+		public override bool OnEnter (View view)
+		{
+			Driver.SetCursorVisibility (CursorVisibility.Invisible);
+			return base.OnEnter (view);
+		}
 
-				for (int i = 0; i < selected.Width; i++) {
+		public override void OnDrawContent (Rect contentArea)
+		{
+			_host._tabLocations = _host.CalculateViewport (Bounds).ToArray ();
 
-					if (selected.X + i > width) {
-						// we ran out of space horizontally
-						return;
-					}
+			// clear any old text
+			Clear ();
 
-					Driver.AddRune (CM.Glyphs.HLine);
-				}
+			RenderTabLine ();
 
-				// Add the end of the selected tab
-				Driver.AddRune (host.Style.TabsOnBottom ? CM.Glyphs.LRCorner : CM.Glyphs.URCorner);
+			RenderUnderline ();
+			Driver.SetAttribute (GetNormalColor ());
+		}
 
+		public override void OnDrawContentComplete (Rect contentArea)
+		{
+			if (_host._tabLocations == null) {
+				return;
 			}
 
-			/// <summary>
-			/// Renders the line with the tab names in it
-			/// </summary>
-			/// <param name="tabLocations"></param>
-			/// <param name="width"></param>
-			private void RenderTabLine (TabToRender [] tabLocations, int width)
-			{
-				int y;
+			var tabLocations = _host._tabLocations;
+			var selectedTab = -1;
 
-				if (host.Style.TabsOnBottom) {
-
-					y = 1;
-				} else {
-					y = host.Style.ShowTopLine ? 1 : 0;
-				}
+			for (int i = 0; i < tabLocations.Length; i++) {
+				View tab = tabLocations [i].Tab;
+				var vts = tab.BoundsToScreen (tab.Bounds);
+				LineCanvas lc = new LineCanvas ();
+				var selectedOffset = _host.Style.ShowTopLine && tabLocations [i].IsSelected ? 0 : 1;
 
+				if (tabLocations [i].IsSelected) {
+					selectedTab = i;
 
-				// clear any old text
-				Move (0, y);
-				Driver.AddStr (new string (' ', width));
+					if (i == 0 && _host.TabScrollOffset == 0) {
+						if (_host.Style.TabsOnBottom) {
+							// Upper left vertical line
+							lc.AddLine (new Point (vts.X - 1, vts.Y - 1), -1, Orientation.Vertical, tab.BorderStyle);
+						} else {
+							// Lower left vertical line
+							lc.AddLine (new Point (vts.X - 1, vts.Bottom - selectedOffset), -1, Orientation.Vertical, tab.BorderStyle);
+						}
+					} else if (i > 0 && i <= tabLocations.Length - 1) {
+						if (_host.Style.TabsOnBottom) {
+							// URCorner
+							lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle);
+							lc.AddLine (new Point (vts.X - 1, vts.Y - 1), -1, Orientation.Horizontal, tab.BorderStyle);
+						} else {
+							// LRCorner
+							lc.AddLine (new Point (vts.X - 1, vts.Bottom - selectedOffset), -1, Orientation.Vertical, tab.BorderStyle);
+							lc.AddLine (new Point (vts.X - 1, vts.Bottom - selectedOffset), -1, Orientation.Horizontal, tab.BorderStyle);
+						}
 
-				foreach (var toRender in tabLocations) {
+						if (_host.Style.ShowTopLine) {
+							if (_host.Style.TabsOnBottom) {
+								// Lower left tee
+								lc.AddLine (new Point (vts.X - 1, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle);
+								lc.AddLine (new Point (vts.X - 1, vts.Bottom), 0, Orientation.Horizontal, tab.BorderStyle);
+							} else {
+								// Upper left tee
+								lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle);
+								lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle);
+							}
+						}
+					}
 
-					if (toRender.IsSelected) {
-						Move (toRender.X - 1, y);
-						Driver.AddRune (CM.Glyphs.VLine);
+					if (i < tabLocations.Length - 1) {
+						if (_host.Style.ShowTopLine) {
+							if (_host.Style.TabsOnBottom) {
+								// Lower right tee
+								lc.AddLine (new Point (vts.Right, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle);
+								lc.AddLine (new Point (vts.Right, vts.Bottom), 0, Orientation.Horizontal, tab.BorderStyle);
+							} else {
+								// Upper right tee
+								lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle);
+								lc.AddLine (new Point (vts.Right, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle);
+							}
+						}
 					}
 
-					Move (toRender.X, y);
+					if (_host.Style.TabsOnBottom) {
+						//URCorner
+						lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle);
+						lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Horizontal, tab.BorderStyle);
+					} else {
+						//LLCorner
+						lc.AddLine (new Point (vts.Right, vts.Bottom - selectedOffset), -1, Orientation.Vertical, tab.BorderStyle);
+						lc.AddLine (new Point (vts.Right, vts.Bottom - selectedOffset), 1, Orientation.Horizontal, tab.BorderStyle);
+					}
 
-					// if tab is the selected one and focus is inside this control
-					if (toRender.IsSelected && host.HasFocus) {
+				} else if (selectedTab == -1) {
+					if (i == 0 && string.IsNullOrEmpty (tab.Text)) {
+						if (_host.Style.TabsOnBottom) {
+							if (_host.Style.ShowTopLine) {
+								// LLCorner
+								lc.AddLine (new Point (vts.X - 1, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle);
+								lc.AddLine (new Point (vts.X - 1, vts.Bottom), 1, Orientation.Horizontal, tab.BorderStyle);
+							}
+							// ULCorner
+							lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle);
+							lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Horizontal, tab.BorderStyle);
+						} else {
+							if (_host.Style.ShowTopLine) {
+								// ULCorner
+								lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle);
+								lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Horizontal, tab.BorderStyle);
+							}
+							// LLCorner
+							lc.AddLine (new Point (vts.X - 1, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle);
+							lc.AddLine (new Point (vts.X - 1, vts.Bottom), 1, Orientation.Horizontal, tab.BorderStyle);
+						}
+					} else if (i > 0) {
+						if (_host.Style.ShowTopLine || _host.Style.TabsOnBottom) {
+							// Upper left tee
+							lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle);
+							lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle);
+						}
 
-						if (host.Focused == this) {
+						// Lower left tee
+						lc.AddLine (new Point (vts.X - 1, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle);
+						lc.AddLine (new Point (vts.X - 1, vts.Bottom), 0, Orientation.Horizontal, tab.BorderStyle);
 
-							// if focus is the tab bar ourself then show that they can switch tabs
-							Driver.SetAttribute (ColorScheme.HotFocus);
+					}
+				} else if (i < tabLocations.Length - 1) {
+					if (_host.Style.ShowTopLine) {
+						// Upper right tee
+						lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle);
+						lc.AddLine (new Point (vts.Right, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle);
+					}
 
-						} else {
+					if (_host.Style.ShowTopLine || !_host.Style.TabsOnBottom) {
+						// Lower right tee
+						lc.AddLine (new Point (vts.Right, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle);
+						lc.AddLine (new Point (vts.Right, vts.Bottom), 0, Orientation.Horizontal, tab.BorderStyle);
+					} else {
+						// Upper right tee
+						lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle);
+						lc.AddLine (new Point (vts.Right, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle);
+					}
+				}
 
-							// Focus is inside the tab
-							Driver.SetAttribute (ColorScheme.HotNormal);
-						}
+				if (i == 0 && i != selectedTab && _host.TabScrollOffset == 0 && _host.Style.ShowBorder) {
+					if (_host.Style.TabsOnBottom) {
+						// Upper left vertical line
+						lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 0, Orientation.Vertical, tab.BorderStyle);
+						lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Horizontal, tab.BorderStyle);
+					} else {
+						// Lower left vertical line
+						lc.AddLine (new Point (vts.X - 1, vts.Bottom), 0, Orientation.Vertical, tab.BorderStyle);
+						lc.AddLine (new Point (vts.X - 1, vts.Bottom), 1, Orientation.Horizontal, tab.BorderStyle);
 					}
+				}
 
-					Driver.AddStr (toRender.TextToRender);
-					Driver.SetAttribute (GetNormalColor ());
+				if (i == tabLocations.Length - 1 && i != selectedTab) {
+					if (_host.Style.TabsOnBottom) {
+						// Upper right tee
+						lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle);
+						lc.AddLine (new Point (vts.Right, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle);
+					} else {
+						// Lower right tee
+						lc.AddLine (new Point (vts.Right, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle);
+						lc.AddLine (new Point (vts.Right, vts.Bottom), 0, Orientation.Horizontal, tab.BorderStyle);
+					}
+				}
 
-					if (toRender.IsSelected) {
-						Driver.AddRune (CM.Glyphs.VLine);
+				if (i == tabLocations.Length - 1) {
+					var arrowOffset = 1;
+					var lastSelectedTab = !_host.Style.ShowTopLine && i == selectedTab ? 1 : _host.Style.TabsOnBottom ? 1 : 0;
+					var tabsBarVts = BoundsToScreen (Bounds);
+					var lineLength = tabsBarVts.Right - vts.Right;
+					// Right horizontal line
+					if (ShouldDrawRightScrollIndicator ()) {
+						if (lineLength - arrowOffset > 0) {
+							if (_host.Style.TabsOnBottom) {
+								lc.AddLine (new Point (vts.Right, vts.Y - lastSelectedTab), lineLength - arrowOffset, Orientation.Horizontal, tab.BorderStyle);
+							} else {
+								lc.AddLine (new Point (vts.Right, vts.Bottom - lastSelectedTab), lineLength - arrowOffset, Orientation.Horizontal, tab.BorderStyle);
+							}
+						}
+					} else {
+						if (_host.Style.TabsOnBottom) {
+							lc.AddLine (new Point (vts.Right, vts.Y - lastSelectedTab), lineLength, Orientation.Horizontal, tab.BorderStyle);
+						} else {
+							lc.AddLine (new Point (vts.Right, vts.Bottom - lastSelectedTab), lineLength, Orientation.Horizontal, tab.BorderStyle);
+						}
+						if (_host.Style.ShowBorder) {
+							if (_host.Style.TabsOnBottom) {
+								// More LRCorner
+								lc.AddLine (new Point (tabsBarVts.Right - 1, vts.Y - lastSelectedTab), -1, Orientation.Vertical, tab.BorderStyle);
+							} else {
+								// More URCorner
+								lc.AddLine (new Point (tabsBarVts.Right - 1, vts.Bottom - lastSelectedTab), 1, Orientation.Vertical, tab.BorderStyle);
+							}
+						}
 					}
 				}
+
+				tab.LineCanvas.Merge (lc);
+				tab.OnRenderLineCanvas ();
 			}
+		}
 
-			/// <summary>
-			/// Renders the line of the tab that adjoins the content of the tab
-			/// </summary>
-			/// <param name="tabLocations"></param>
-			/// <param name="width"></param>
-			private void RenderUnderline (TabToRender [] tabLocations, int width)
-			{
-				int y = GetUnderlineYPosition ();
+		/// <summary>
+		/// Renders the line with the tab names in it.
+		/// </summary>
+		private void RenderTabLine ()
+		{
+			var tabLocations = _host._tabLocations;
+			int y;
 
-				Move (0, y);
+			if (_host.Style.TabsOnBottom) {
 
-				// If host has no border then we need to draw the solid line first (then we draw gaps over the top)
-				if (!host.Style.ShowBorder) {
+				y = 1;
+			} else {
+				y = _host.Style.ShowTopLine ? 1 : 0;
+			}
 
-					for (int x = 0; x < width; x++) {
-						Driver.AddRune (CM.Glyphs.HLine);
+			View selected = null;
+			var topLine = _host.Style.ShowTopLine ? 1 : 0;
+			var width = Bounds.Width;
+
+			foreach (var toRender in tabLocations) {
+				var tab = toRender.Tab;
+
+				if (toRender.IsSelected) {
+					selected = tab;
+					if (_host.Style.TabsOnBottom) {
+						tab.Border.Thickness = new Thickness (1, 0, 1, topLine);
+						tab.Margin.Thickness = new Thickness (0, 1, 0, 0);
+					} else {
+						tab.Border.Thickness = new Thickness (1, topLine, 1, 0);
+						tab.Margin.Thickness = new Thickness (0, 0, 0, topLine);
 					}
-
+				} else if (selected == null) {
+					if (_host.Style.TabsOnBottom) {
+						tab.Border.Thickness = new Thickness (1, 1, 0, topLine);
+						tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
+					} else {
+						tab.Border.Thickness = new Thickness (1, topLine, 0, 1);
+						tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
+					}
+					tab.Width = Math.Max (tab.Width.Anchor (0) - 1, 1);
+				} else {
+					if (_host.Style.TabsOnBottom) {
+						tab.Border.Thickness = new Thickness (0, 1, 1, topLine);
+						tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
+					} else {
+						tab.Border.Thickness = new Thickness (0, topLine, 1, 1);
+						tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
+					}
+					tab.Width = Math.Max (tab.Width.Anchor (0) - 1, 1);
 				}
-				var selected = tabLocations.FirstOrDefault (t => t.IsSelected);
 
-				if (selected == null) {
-					return;
-				}
+				tab.Text = toRender.TextToRender;
 
-				Move (selected.X - 1, y);
+				LayoutSubviews ();
 
-				Driver.AddRune (selected.X == 1 ? CM.Glyphs.VLine :
-					(host.Style.TabsOnBottom ? CM.Glyphs.URCorner : CM.Glyphs.LRCorner));
+				tab.OnDrawAdornments ();
 
-				Driver.AddStr (new string (' ', selected.Width));
+				var prevAttr = Driver.GetAttribute ();
 
-				Driver.AddRune (selected.X + selected.Width == width - 1 ?
-				     CM.Glyphs.VLine :
-					(host.Style.TabsOnBottom ? CM.Glyphs.ULCorner : CM.Glyphs.LLCorner));
+				// if tab is the selected one and focus is inside this control
+				if (toRender.IsSelected && _host.HasFocus) {
 
-				// draw scroll indicators
+					if (_host.Focused == this) {
 
-				// if there are more tabs to the left not visible
-				if (host.TabScrollOffset > 0) {
-					Move (0, y);
+						// if focus is the tab bar ourself then show that they can switch tabs
+						prevAttr = ColorScheme.HotFocus;
+					} else {
 
-					// indicate that
-					Driver.AddRune (CM.Glyphs.LeftArrow);
+						// Focus is inside the tab
+						prevAttr = ColorScheme.HotNormal;
+					}
 				}
+				tab.TextFormatter.Draw (tab.BoundsToScreen (tab.Bounds), prevAttr, ColorScheme.HotNormal);
 
-				// if there are more tabs to the right not visible
-				if (ShouldDrawRightScrollIndicator (tabLocations)) {
-					Move (width - 1, y);
+				tab.OnRenderLineCanvas ();
 
-					// indicate that
-					Driver.AddRune (CM.Glyphs.RightArrow);
-				}
-			}
-
-			private bool ShouldDrawRightScrollIndicator (TabToRender [] tabLocations)
-			{
-				return tabLocations.LastOrDefault ()?.Tab != host.Tabs.LastOrDefault ();
+				Driver.SetAttribute (GetNormalColor ());
 			}
+		}
 
-			private int GetUnderlineYPosition ()
-			{
-				if (host.Style.TabsOnBottom) {
+		/// <summary>
+		/// Renders the line of the tab that adjoins the content of the tab.
+		/// </summary>
+		private void RenderUnderline ()
+		{
+			int y = GetUnderlineYPosition ();
 
-					return 0;
-				} else {
+			var selected = _host._tabLocations.FirstOrDefault (t => t.IsSelected);
 
-					return host.Style.ShowTopLine ? 2 : 1;
-				}
+			if (selected == null) {
+				return;
 			}
 
-			public override bool MouseEvent (MouseEvent me)
-			{
-				var hit = ScreenToTab (me.X, me.Y);
+			// draw scroll indicators
 
-				bool isClick = me.Flags.HasFlag (MouseFlags.Button1Clicked) ||
-					me.Flags.HasFlag (MouseFlags.Button2Clicked) ||
-					me.Flags.HasFlag (MouseFlags.Button3Clicked);
+			// if there are more tabs to the left not visible
+			if (_host.TabScrollOffset > 0) {
+				_leftScrollIndicator.X = 0;
+				_leftScrollIndicator.Y = y;
 
-				if (isClick) {
-					host.OnTabClicked (new TabMouseEventArgs (hit, me));
+				// indicate that
+				_leftScrollIndicator.Visible = true;
+				// Ensures this is clicked instead of the first tab
+				BringSubviewToFront (_leftScrollIndicator);
+				_leftScrollIndicator.Draw ();
+			} else {
+				_leftScrollIndicator.Visible = false;
+			}
 
-					// user canceled click
-					if (me.Handled) {
-						return true;
-					}
-				}
+			// if there are more tabs to the right not visible
+			if (ShouldDrawRightScrollIndicator ()) {
+				_rightScrollIndicator.X = Bounds.Width - 1;
+				_rightScrollIndicator.Y = y;
 
-				if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
-				!me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
-				!me.Flags.HasFlag (MouseFlags.Button1TripleClicked))
-					return false;
+				// indicate that
+				_rightScrollIndicator.Visible = true;
+				// Ensures this is clicked instead of the last tab if under this
+				BringSubviewToFront (_rightScrollIndicator);
+				_rightScrollIndicator.Draw ();
+			} else {
+				_rightScrollIndicator.Visible = false;
+			}
+		}
 
-				if (!HasFocus && CanFocus) {
-					SetFocus ();
-				}
+		private bool ShouldDrawRightScrollIndicator ()
+		{
+			return _host._tabLocations.LastOrDefault ()?.Tab != _host.Tabs.LastOrDefault ();
+		}
 
-				if (me.Flags.HasFlag (MouseFlags.Button1Clicked) ||
-				me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) ||
-				me.Flags.HasFlag (MouseFlags.Button1TripleClicked)) {
+		private int GetUnderlineYPosition ()
+		{
+			if (_host.Style.TabsOnBottom) {
+
+				return 0;
+			} else {
 
-					var scrollIndicatorHit = ScreenToScrollIndicator (me.X, me.Y);
+				return _host.Style.ShowTopLine ? 2 : 1;
+			}
+		}
 
-					if (scrollIndicatorHit != 0) {
+		public override bool MouseEvent (MouseEvent me)
+		{
+			var hit = me.View is Tab ? (Tab)me.View : null;
 
-						host.SwitchTabBy (scrollIndicatorHit);
+			bool isClick = me.Flags.HasFlag (MouseFlags.Button1Clicked) ||
+				me.Flags.HasFlag (MouseFlags.Button2Clicked) ||
+				me.Flags.HasFlag (MouseFlags.Button3Clicked);
 
-						SetNeedsDisplay ();
-						return true;
-					}
+			if (isClick) {
+				_host.OnTabClicked (new TabMouseEventArgs (hit, me));
 
-					if (hit != null) {
-						host.SelectedTab = hit;
-						SetNeedsDisplay ();
-						return true;
-					}
+				// user canceled click
+				if (me.Handled) {
+					return true;
 				}
+			}
 
+			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
+			!me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
+			!me.Flags.HasFlag (MouseFlags.Button1TripleClicked))
 				return false;
+
+			if (!HasFocus && CanFocus) {
+				SetFocus ();
 			}
 
-			/// <summary>
-			/// Calculates whether scroll indicators are visible and if so whether the click
-			/// was on one of them.
-			/// </summary>
-			/// <param name="x"></param>
-			/// <param name="y"></param>
-			/// <returns>-1 for click in scroll left, 1 for scroll right or 0 for no hit</returns>
-			private int ScreenToScrollIndicator (int x, int y)
-			{
-				// scroll indicator is showing
-				if (host.TabScrollOffset > 0 && x == 0) {
-
-					return y == GetUnderlineYPosition () ? -1 : 0;
+			if (me.Flags.HasFlag (MouseFlags.Button1Clicked) ||
+			me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) ||
+			me.Flags.HasFlag (MouseFlags.Button1TripleClicked)) {
+
+				int scrollIndicatorHit = 0;
+				if (me.View != null && me.View.Id == "rightScrollIndicator") {
+					scrollIndicatorHit = 1;
+				} else if (me.View != null && me.View.Id == "leftScrollIndicator") {
+					scrollIndicatorHit = -1;
 				}
 
-				// scroll indicator is showing
-				if (x == Bounds.Width - 1 && ShouldDrawRightScrollIndicator (host.CalculateViewport (Bounds).ToArray ())) {
+				if (scrollIndicatorHit != 0) {
 
-					return y == GetUnderlineYPosition () ? 1 : 0;
+					_host.SwitchTabBy (scrollIndicatorHit);
+
+					SetNeedsDisplay ();
+					return true;
 				}
 
-				return 0;
+				if (hit != null) {
+					_host.SelectedTab = hit;
+					SetNeedsDisplay ();
+					return true;
+				}
 			}
 
-			/// <summary>
-			/// Translates the client coordinates of a click into a tab when the click is on top of a tab
-			/// </summary>
-			/// <param name="x"></param>
-			/// <param name="y"></param>
-			/// <returns></returns>
-			public Tab ScreenToTab (int x, int y)
-			{
-				var tabs = host.CalculateViewport (Bounds);
-
-				return tabs.LastOrDefault (t => x >= t.X && x < t.X + t.Width)?.Tab;
-			}
+			return false;
 		}
+	}
 
-		/// <summary>
-		/// Raises the <see cref="TabClicked"/> event.
-		/// </summary>
-		/// <param name="tabMouseEventArgs"></param>
-		protected virtual private void OnTabClicked (TabMouseEventArgs tabMouseEventArgs)
-		{
-			TabClicked?.Invoke (this, tabMouseEventArgs);
-		}
+	/// <summary>
+	/// Raises the <see cref="TabClicked"/> event.
+	/// </summary>
+	/// <param name="tabMouseEventArgs"></param>
+	protected virtual private void OnTabClicked (TabMouseEventArgs tabMouseEventArgs)
+	{
+		TabClicked?.Invoke (this, tabMouseEventArgs);
 	}
 }

+ 1223 - 1147
Terminal.Gui/Views/TextField.cs

@@ -1,1378 +1,1454 @@
-//
-// TextField.cs: single-line text editor with Emacs keybindings
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-
 using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
-using System.Threading;
 using System.Text;
+using System.Threading;
 using Terminal.Gui.Resources;
 
-namespace Terminal.Gui {
-	/// <summary>
-	///   Single-line text entry <see cref="View"/>
-	/// </summary>
-	/// <remarks>
-	///   The <see cref="TextField"/> <see cref="View"/> provides editing functionality and mouse support.
-	/// </remarks>
-	public class TextField : View {
-		List<Rune> _text;
-		int _first, _cursorPosition;
-		int _selectedStart = -1; // -1 represents there is no text selection.
-		string _selectedText;
-		HistoryText _historyText = new HistoryText ();
-		CultureInfo _currentCulture;
-
-		/// <summary>
-		/// Gets or sets the text to render in control when no value has 
-		/// been entered yet and the <see cref="View"/> does not yet have
-		/// input focus.
-		/// </summary>
-		public string Caption { get; set; }
-
-		/// <summary>
-		/// Gets or sets the foreground <see cref="Color"/> to use when 
-		/// rendering <see cref="Caption"/>.
-		/// </summary>
-		public Color CaptionColor { get; set; } = new Color (Color.DarkGray);
-
-		/// <summary>
-		/// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so new input should be appended at the cursor position, rather than clearing the entry
-		/// </summary>
-		public bool Used { get; set; }
-
-		/// <summary>
-		/// If set to true its not allow any changes in the text.
-		/// </summary>
-		public bool ReadOnly { get; set; } = false;
-
-		/// <summary>
-		/// Changing event, raised before the <see cref="Text"/> changes and can be canceled or changing the new text.
-		/// </summary>
-		public event EventHandler<TextChangingEventArgs> TextChanging;
-
-		/// <summary>
-		/// Changed event, raised when the text has changed.
-		/// <remarks>
-		///   This event is raised when the <see cref="Text"/> changes. 
-		///   The passed <see cref="EventArgs"/> is a <see cref="string"/> containing the old value. 
-		/// </remarks>
-		/// </summary>
-		public event EventHandler<TextChangedEventArgs> TextChanged;
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="TextField"/> class using <see cref="LayoutStyle.Computed"/> positioning.
-		/// </summary>
-		public TextField () : this (string.Empty) { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="TextField"/> class using <see cref="LayoutStyle.Computed"/> positioning.
-		/// </summary>
-		/// <param name="text">Initial text contents.</param>
-		public TextField (string text) : base (text)
-		{
-			SetInitialProperties (text, text.GetRuneCount () + 1);
-		}
+namespace Terminal.Gui;
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="TextField"/> class using <see cref="LayoutStyle.Absolute"/> positioning.
-		/// </summary>
-		/// <param name="x">The x coordinate.</param>
-		/// <param name="y">The y coordinate.</param>
-		/// <param name="w">The width.</param>
-		/// <param name="text">Initial text contents.</param>
-		public TextField (int x, int y, int w, string text) : base (new Rect (x, y, w, 1))
-		{
-			SetInitialProperties (text, w);
-		}
+/// <summary>
+/// Single-line text entry <see cref="View"/>
+/// </summary>
+/// <remarks>
+/// The <see cref="TextField"/> <see cref="View"/> provides editing functionality and mouse support.
+/// </remarks>
+public class TextField : View {
+	CultureInfo _currentCulture;
 
-		void SetInitialProperties (string text, int w)
-		{
-			Height = 1;
+	CursorVisibility _desiredCursorVisibility = CursorVisibility.Default;
+	int _cursorPosition;
+	readonly HistoryText _historyText = new ();
+	bool _isButtonPressed;
+	bool _isButtonReleased = true;
 
-			if (text == null)
-				text = "";
-
-			this._text = text.Split ("\n") [0].EnumerateRunes ().ToList ();
-			_cursorPosition = text.GetRuneCount ();
-			_first = _cursorPosition > w + 1 ? _cursorPosition - w + 1 : 0;
-			CanFocus = true;
-			Used = true;
-			WantMousePositionReports = true;
-			_savedCursorVisibility = _desiredCursorVisibility;
-
-			_historyText.ChangeText += HistoryText_ChangeText;
-
-			Initialized += TextField_Initialized;
-
-			// Things this view knows how to do
-			AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
-			AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; });
-			AddCommand (Command.LeftHomeExtend, () => { MoveHomeExtend (); return true; });
-			AddCommand (Command.RightEndExtend, () => { MoveEndExtend (); return true; });
-			AddCommand (Command.LeftHome, () => { MoveHome (); return true; });
-			AddCommand (Command.LeftExtend, () => { MoveLeftExtend (); return true; });
-			AddCommand (Command.RightExtend, () => { MoveRightExtend (); return true; });
-			AddCommand (Command.WordLeftExtend, () => { MoveWordLeftExtend (); return true; });
-			AddCommand (Command.WordRightExtend, () => { MoveWordRightExtend (); return true; });
-			AddCommand (Command.Left, () => { MoveLeft (); return true; });
-			AddCommand (Command.RightEnd, () => { MoveEnd (); return true; });
-			AddCommand (Command.Right, () => { MoveRight (); return true; });
-			AddCommand (Command.CutToEndLine, () => { KillToEnd (); return true; });
-			AddCommand (Command.CutToStartLine, () => { KillToStart (); return true; });
-			AddCommand (Command.Undo, () => { Undo (); return true; });
-			AddCommand (Command.Redo, () => { Redo (); return true; });
-			AddCommand (Command.WordLeft, () => { MoveWordLeft (); return true; });
-			AddCommand (Command.WordRight, () => { MoveWordRight (); return true; });
-			AddCommand (Command.KillWordForwards, () => { KillWordForwards (); return true; });
-			AddCommand (Command.KillWordBackwards, () => { KillWordBackwards (); return true; });
-			AddCommand (Command.ToggleOverwrite, () => { SetOverwrite (!Used); return true; });
-			AddCommand (Command.EnableOverwrite, () => { SetOverwrite (true); return true; });
-			AddCommand (Command.DisableOverwrite, () => { SetOverwrite (false); return true; });
-			AddCommand (Command.Copy, () => { Copy (); return true; });
-			AddCommand (Command.Cut, () => { Cut (); return true; });
-			AddCommand (Command.Paste, () => { Paste (); return true; });
-			AddCommand (Command.SelectAll, () => { SelectAll (); return true; });
-			AddCommand (Command.DeleteAll, () => { DeleteAll (); return true; });
-			AddCommand (Command.ShowContextMenu, () => { ShowContextMenu (); return true; });
-
-			// Default keybindings for this view
-			// We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
-			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
-			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
-
-			KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
-
-			KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend);
-			KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
-			KeyBindings.Add (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
-
-			KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.RightEndExtend);
-			KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
-			KeyBindings.Add (KeyCode.E | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
-
-			KeyBindings.Add (KeyCode.Home, Command.LeftHome);
-			KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.LeftHome);
-			KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome);
-
-			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend);
-			KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LeftExtend);
-
-			KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend);
-			KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.RightExtend);
-
-			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
-			KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
-			KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.ShiftMask | KeyCode.AltMask), Command.WordLeftExtend);
-
-			KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
-			KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
-			KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.ShiftMask | KeyCode.AltMask), Command.WordRightExtend);
-
-			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
-			KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
-
-			KeyBindings.Add (KeyCode.End, Command.RightEnd);
-			KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.RightEnd);
-			KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd);
-
-			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
-			KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
-
-			KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine);
-			KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine);
-
-			KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo);
-			KeyBindings.Add (KeyCode.Backspace | KeyCode.AltMask, Command.Undo);
-
-			KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Redo);
-
-			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft);
-			KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.WordLeft);
-			KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.AltMask), Command.WordLeft);
-
-			KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight);
-			KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight);
-			KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight);
-
-			KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards);
-			KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards);
-			KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite);
-			KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy);
-			KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut);
-			KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.Paste);
-			KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll);
-
-			KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.DeleteAll);
-			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll);
+	bool _isDrawing;
 
-			_currentCulture = Thread.CurrentThread.CurrentUICulture;
+	int _preTextChangedCursorPos;
 
-			ContextMenu = new ContextMenu (this, BuildContextMenuBarItem ());
-			ContextMenu.KeyChanged += ContextMenu_KeyChanged;
+	CursorVisibility _savedCursorVisibility;
+	int _selectedStart = -1; // -1 represents there is no text selection.
+	string _selectedText;
 
-			KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu);
-		}
+	int _start;
+	List<Rune> _text;
 
-		private MenuBarItem BuildContextMenuBarItem ()
-		{
-			return new MenuBarItem (new MenuItem [] {
-					new MenuItem (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)),
-					new MenuItem (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)),
-					new MenuItem (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)),
-					new MenuItem (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)),
-					new MenuItem (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)),
-					new MenuItem (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)),
-					new MenuItem (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)),
-				});
-		}
-
-		private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e)
-		{
-			KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode);
-		}
+	CursorVisibility _visibility;
 
-		private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj)
-		{
-			if (obj == null)
-				return;
+	/// <summary>
+	/// Initializes a new instance of the <see cref="TextField"/> class using <see cref="LayoutStyle.Computed"/> positioning.
+	/// </summary>
+	public TextField () : this (string.Empty) { }
 
-			Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]);
-			CursorPosition = obj.CursorPosition.X;
-			Adjust ();
-		}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="TextField"/> class using <see cref="LayoutStyle.Computed"/> positioning.
+	/// </summary>
+	/// <param name="text">Initial text contents.</param>
+	public TextField (string text) : base (text) => SetInitialProperties (text, text.GetRuneCount () + 1);
 
-		void TextField_Initialized (object sender, EventArgs e)
-		{
-			Autocomplete.HostControl = this;
-			Autocomplete.PopupInsideContainer = false;
-		}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="TextField"/> class using <see cref="LayoutStyle.Absolute"/> positioning.
+	/// </summary>
+	/// <param name="x">The x coordinate.</param>
+	/// <param name="y">The y coordinate.</param>
+	/// <param name="w">The width.</param>
+	/// <param name="text">Initial text contents.</param>
+	public TextField (int x, int y, int w, string text) : base (new Rect (x, y, w, 1)) => SetInitialProperties (text, w);
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			if (IsInitialized) {
-				Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
-			}
+	/// <summary>
+	/// Gets or sets the text to render in control when no value has
+	/// been entered yet and the <see cref="View"/> does not yet have
+	/// input focus.
+	/// </summary>
+	public string Caption { get; set; }
 
-			return base.OnEnter (view);
-		}
+	/// <summary>
+	/// Gets or sets the foreground <see cref="Color"/> to use when
+	/// rendering <see cref="Caption"/>.
+	/// </summary>
+	public Color CaptionColor { get; set; } = new (Color.DarkGray);
 
-		///<inheritdoc/>
-		public override bool OnLeave (View view)
-		{
-			if (Application.MouseGrabView != null && Application.MouseGrabView == this)
-				Application.UngrabMouse ();
-			//if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
-			//	ClearAllSelection ();
-
-			return base.OnLeave (view);
-		}
-
-		/// <summary>
-		/// Provides autocomplete context menu based on suggestions at the current cursor
-		/// position. Configure <see cref="ISuggestionGenerator"/> to enable this feature.
-		/// </summary>
-		public IAutocomplete Autocomplete { get; set; } = new TextFieldAutocomplete ();
-
-		///<inheritdoc/>
-		public override Rect Frame {
-			get => base.Frame;
-			set {
-				if (value.Height > 1) {
-					base.Frame = new Rect (value.X, value.Y, value.Width, 1);
-					Height = 1;
-				} else {
-					base.Frame = value;
-				}
-				Adjust ();
-			}
-		}
+	/// <summary>
+	/// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so new input
+	/// should be appended at the cursor position, rather than clearing the entry
+	/// </summary>
+	public bool Used { get; set; }
 
-		/// <summary>
-		///   Sets or gets the text held by the view.
-		/// </summary>
-		public new string Text {
-			get {
-				return StringExtensions.ToString (_text);
-			}
+	/// <summary>
+	/// If set to true its not allow any changes in the text.
+	/// </summary>
+	public bool ReadOnly { get; set; } = false;
 
-			set {
-				var oldText = StringExtensions.ToString (_text);
+	/// <summary>
+	/// Provides autocomplete context menu based on suggestions at the current cursor
+	/// position. Configure <see cref="ISuggestionGenerator"/> to enable this feature.
+	/// </summary>
+	public IAutocomplete Autocomplete { get; set; } = new TextFieldAutocomplete ();
 
-				if (oldText == value)
-					return;
+	/// <summary>
+	/// Sets or gets the text held by the view.
+	/// </summary>
+	public new string Text {
+		get => StringExtensions.ToString (_text);
+		set {
+			var oldText = StringExtensions.ToString (_text);
 
-				var newText = OnTextChanging (value.Replace ("\t", "").Split ("\n") [0]);
-				if (newText.Cancel) {
-					if (_cursorPosition > _text.Count) {
-						_cursorPosition = _text.Count;
-					}
-					return;
-				}
-				ClearAllSelection ();
-				_text = newText.NewText.EnumerateRunes ().ToList ();
+			if (oldText == value) {
+				return;
+			}
 
-				if (!Secret && !_historyText.IsFromHistory) {
-					_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCellList (oldText) },
-						new Point (_cursorPosition, 0));
-					_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)
-						, HistoryText.LineStatus.Replaced);
+			var newText = OnTextChanging (value.Replace ("\t", "").Split ("\n") [0]);
+			if (newText.Cancel) {
+				if (_cursorPosition > _text.Count) {
+					_cursorPosition = _text.Count;
 				}
+				return;
+			}
+			ClearAllSelection ();
+			_text = newText.NewText.EnumerateRunes ().ToList ();
 
-				TextChanged?.Invoke (this, new TextChangedEventArgs (oldText));
+			if (!Secret && !_historyText.IsFromHistory) {
+				_historyText.Add (new List<List<RuneCell>> { TextModel.ToRuneCellList (oldText) },
+					new Point (_cursorPosition, 0));
+				_historyText.Add (new List<List<RuneCell>> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)
+					, HistoryText.LineStatus.Replaced);
+			}
 
-				ProcessAutocomplete ();
+			TextChanged?.Invoke (this, new TextChangedEventArgs (oldText));
 
-				if (_cursorPosition > _text.Count) {
-					_cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0);
-				}
+			ProcessAutocomplete ();
 
-				Adjust ();
-				SetNeedsDisplay ();
+			if (_cursorPosition > _text.Count) {
+				_cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0);
 			}
+
+			Adjust ();
+			SetNeedsDisplay ();
 		}
+	}
 
-		/// <summary>
-		///   Sets the secret property.
-		/// <remarks>
-		///   This makes the text entry suitable for entering passwords.
-		/// </remarks>
-		/// </summary>
-		public bool Secret { get; set; }
-
-		/// <summary>
-		///    Sets or gets the current cursor position.
-		/// </summary>
-		public virtual int CursorPosition {
-			get { return _cursorPosition; }
-			set {
-				if (value < 0) {
-					_cursorPosition = 0;
-				} else if (value > _text.Count) {
-					_cursorPosition = _text.Count;
-				} else {
-					_cursorPosition = value;
-				}
-				PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
+	/// <summary>
+	/// Sets the secret property.
+	/// <remarks>
+	/// This makes the text entry suitable for entering passwords.
+	/// </remarks>
+	/// </summary>
+	public bool Secret { get; set; }
+
+	/// <summary>
+	/// Sets or gets the current cursor position.
+	/// </summary>
+	public virtual int CursorPosition {
+		get => _cursorPosition;
+		set {
+			if (value < 0) {
+				_cursorPosition = 0;
+			} else if (value > _text.Count) {
+				_cursorPosition = _text.Count;
+			} else {
+				_cursorPosition = value;
 			}
+			PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
 		}
+	}
 
-		/// <summary>
-		/// Gets the left offset position.
-		/// </summary>
-		public int ScrollOffset => _first;
-
-		/// <summary>
-		/// Indicates whatever the text was changed or not.
-		/// <see langword="true"/> if the text was changed <see langword="false"/> otherwise.
-		/// </summary>
-		public bool IsDirty => _historyText.IsDirty (Text);
-
-		/// <summary>
-		/// Indicates whatever the text has history changes or not.
-		/// <see langword="true"/> if the text has history changes <see langword="false"/> otherwise.
-		/// </summary>
-		public bool HasHistoryChanges => _historyText.HasHistoryChanges;
-
-		/// <summary>
-		/// Get the <see cref="ContextMenu"/> for this view.
-		/// </summary>
-		public ContextMenu ContextMenu { get; private set; }
-
-		/// <summary>
-		///   Sets the cursor position.
-		/// </summary>
-		public override void PositionCursor ()
-		{
-			if (!IsInitialized) {
-				return;
-			}
-			ProcessAutocomplete ();
+	/// <summary>
+	/// Gets the left offset position.
+	/// </summary>
+	public int ScrollOffset { get; private set; }
 
-			var col = 0;
-			for (int idx = _first < 0 ? 0 : _first; idx < _text.Count; idx++) {
-				if (idx == _cursorPosition)
-					break;
-				var cols = _text [idx].GetColumns ();
-				TextModel.SetCol (ref col, Frame.Width - 1, cols);
-			}
-			var pos = _cursorPosition - _first + Math.Min (Frame.X, 0);
-			var offB = OffSetBackground ();
-			var containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default;
-			var thisFrame = BoundsToScreen (Bounds);
-			if (pos > -1 && col >= pos && pos < Frame.Width + offB
-				&& containerFrame.IntersectsWith (thisFrame)) {
-				RestoreCursorVisibility ();
-				Move (col, 0);
+	/// <summary>
+	/// Indicates whatever the text was changed or not.
+	/// <see langword="true"/> if the text was changed <see langword="false"/> otherwise.
+	/// </summary>
+	public bool IsDirty => _historyText.IsDirty (Text);
+
+	/// <summary>
+	/// Indicates whatever the text has history changes or not.
+	/// <see langword="true"/> if the text has history changes <see langword="false"/> otherwise.
+	/// </summary>
+	public bool HasHistoryChanges => _historyText.HasHistoryChanges;
+
+	/// <summary>
+	/// Get the <see cref="ContextMenu"/> for this view.
+	/// </summary>
+	public ContextMenu ContextMenu { get; private set; }
+
+	///<inheritdoc/>
+	public override bool CanFocus {
+		get => base.CanFocus;
+		set => base.CanFocus = value;
+	}
+
+	/// <summary>
+	/// Start position of the selected text.
+	/// </summary>
+	public int SelectedStart {
+		get => _selectedStart;
+		set {
+			if (value < -1) {
+				_selectedStart = -1;
+			} else if (value > _text.Count) {
+				_selectedStart = _text.Count;
 			} else {
-				HideCursorVisibility ();
-				if (pos < 0) {
-					Move (pos, 0);
-				} else {
-					Move (pos - offB, 0);
-				}
+				_selectedStart = value;
 			}
+			PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
 		}
+	}
+
+	/// <summary>
+	/// Length of the selected text.
+	/// </summary>
+	public int SelectedLength { get; private set; }
 
-		CursorVisibility _savedCursorVisibility;
+	/// <summary>
+	/// The selected text.
+	/// </summary>
+	public string SelectedText {
+		get => Secret ? null : _selectedText;
+		private set => _selectedText = value;
+	}
 
-		void HideCursorVisibility ()
-		{
-			if (_desiredCursorVisibility != CursorVisibility.Invisible) {
-				DesiredCursorVisibility = CursorVisibility.Invisible;
+	/// <summary>
+	/// Get / Set the wished cursor when the field is focused
+	/// </summary>
+	public CursorVisibility DesiredCursorVisibility {
+		get => _desiredCursorVisibility;
+		set {
+			if ((_desiredCursorVisibility != value || _visibility != value) && HasFocus) {
+				Application.Driver.SetCursorVisibility (value);
 			}
+
+			_desiredCursorVisibility = _visibility = value;
 		}
+	}
 
-		CursorVisibility _visibility;
+	/// <summary>
+	/// Changing event, raised before the <see cref="Text"/> changes and can be canceled or changing the new text.
+	/// </summary>
+	public event EventHandler<TextChangingEventArgs> TextChanging;
 
-		void RestoreCursorVisibility ()
-		{
-			Application.Driver.GetCursorVisibility (out _visibility);
-			if (_desiredCursorVisibility != _savedCursorVisibility || _visibility != _savedCursorVisibility) {
-				DesiredCursorVisibility = _savedCursorVisibility;
-			}
+	/// <summary>
+	/// Changed event, raised when the text has changed.
+	/// <remarks>
+	/// This event is raised when the <see cref="Text"/> changes.
+	/// The passed <see cref="EventArgs"/> is a <see cref="string"/> containing the old value.
+	/// </remarks>
+	/// </summary>
+	public event EventHandler<TextChangedEventArgs> TextChanged;
+
+	void SetInitialProperties (string text, int w)
+	{
+		Height = 1;
+
+		if (text == null) {
+			text = "";
 		}
 
-		bool _isDrawing = false;
+		_text = text.Split ("\n") [0].EnumerateRunes ().ToList ();
+		_cursorPosition = text.GetRuneCount ();
+		ScrollOffset = _cursorPosition > w + 1 ? _cursorPosition - w + 1 : 0;
+		CanFocus = true;
+		Used = true;
+		WantMousePositionReports = true;
+		_savedCursorVisibility = _desiredCursorVisibility;
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			_isDrawing = true;
+		_historyText.ChangeText += HistoryText_ChangeText;
 
-			var selColor = new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground);
-			SetSelectedStartSelectedLength ();
+		Initialized += TextField_Initialized;
 
-			Driver.SetAttribute (GetNormalColor ());
-			Move (0, 0);
-
-			int p = _first;
-			int col = 0;
-			int width = Frame.Width + OffSetBackground ();
-			var tcount = _text.Count;
-			var roc = GetReadOnlyColor ();
-			for (int idx = p; idx < tcount; idx++) {
-				var rune = _text [idx];
-				var cols = rune.GetColumns ();
-				if (idx == _cursorPosition && HasFocus && !Used && _length == 0 && !ReadOnly) {
-					Driver.SetAttribute (selColor);
-				} else if (ReadOnly) {
-					Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : roc);
-				} else if (!HasFocus && Enabled) {
-					Driver.SetAttribute (ColorScheme.Focus);
-				} else if (!Enabled) {
-					Driver.SetAttribute (roc);
-				} else {
-					Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : ColorScheme.Focus);
-				}
-				if (col + cols <= width) {
-					Driver.AddRune ((Secret ? CM.Glyphs.Dot : rune));
-				}
-				if (!TextModel.SetCol (ref col, width, cols)) {
-					break;
-				}
-				if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width) {
-					break;
-				}
-			}
+		LayoutComplete += TextField_LayoutComplete;
 
-			Driver.SetAttribute (ColorScheme.Focus);
-			for (int i = col; i < width; i++) {
-				Driver.AddRune ((Rune)' ');
-			}
+		// Things this view knows how to do
+		AddCommand (Command.DeleteCharRight, () => {
+			DeleteCharRight ();
+			return true;
+		});
+		AddCommand (Command.DeleteCharLeft, () => {
+			DeleteCharLeft (false);
+			return true;
+		});
+		AddCommand (Command.LeftHomeExtend, () => {
+			MoveHomeExtend ();
+			return true;
+		});
+		AddCommand (Command.RightEndExtend, () => {
+			MoveEndExtend ();
+			return true;
+		});
+		AddCommand (Command.LeftHome, () => {
+			MoveHome ();
+			return true;
+		});
+		AddCommand (Command.LeftExtend, () => {
+			MoveLeftExtend ();
+			return true;
+		});
+		AddCommand (Command.RightExtend, () => {
+			MoveRightExtend ();
+			return true;
+		});
+		AddCommand (Command.WordLeftExtend, () => {
+			MoveWordLeftExtend ();
+			return true;
+		});
+		AddCommand (Command.WordRightExtend, () => {
+			MoveWordRightExtend ();
+			return true;
+		});
+		AddCommand (Command.Left, () => {
+			MoveLeft ();
+			return true;
+		});
+		AddCommand (Command.RightEnd, () => {
+			MoveEnd ();
+			return true;
+		});
+		AddCommand (Command.Right, () => {
+			MoveRight ();
+			return true;
+		});
+		AddCommand (Command.CutToEndLine, () => {
+			KillToEnd ();
+			return true;
+		});
+		AddCommand (Command.CutToStartLine, () => {
+			KillToStart ();
+			return true;
+		});
+		AddCommand (Command.Undo, () => {
+			Undo ();
+			return true;
+		});
+		AddCommand (Command.Redo, () => {
+			Redo ();
+			return true;
+		});
+		AddCommand (Command.WordLeft, () => {
+			MoveWordLeft ();
+			return true;
+		});
+		AddCommand (Command.WordRight, () => {
+			MoveWordRight ();
+			return true;
+		});
+		AddCommand (Command.KillWordForwards, () => {
+			KillWordForwards ();
+			return true;
+		});
+		AddCommand (Command.KillWordBackwards, () => {
+			KillWordBackwards ();
+			return true;
+		});
+		AddCommand (Command.ToggleOverwrite, () => {
+			SetOverwrite (!Used);
+			return true;
+		});
+		AddCommand (Command.EnableOverwrite, () => {
+			SetOverwrite (true);
+			return true;
+		});
+		AddCommand (Command.DisableOverwrite, () => {
+			SetOverwrite (false);
+			return true;
+		});
+		AddCommand (Command.Copy, () => {
+			Copy ();
+			return true;
+		});
+		AddCommand (Command.Cut, () => {
+			Cut ();
+			return true;
+		});
+		AddCommand (Command.Paste, () => {
+			Paste ();
+			return true;
+		});
+		AddCommand (Command.SelectAll, () => {
+			SelectAll ();
+			return true;
+		});
+		AddCommand (Command.DeleteAll, () => {
+			DeleteAll ();
+			return true;
+		});
+		AddCommand (Command.ShowContextMenu, () => {
+			ShowContextMenu ();
+			return true;
+		});
 
-			PositionCursor ();
+		// Default keybindings for this view
+		// We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
+		KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
+		KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
 
-			RenderCaption ();
+		KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
 
-			ProcessAutocomplete ();
+		KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend);
+		KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
+		KeyBindings.Add (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
 
-			_isDrawing = false;
-		}
+		KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.RightEndExtend);
+		KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
+		KeyBindings.Add (KeyCode.E | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
 
-		private void ProcessAutocomplete ()
-		{
-			if (_isDrawing) {
-				return;
-			}
-			if (SelectedLength > 0) {
-				return;
-			}
+		KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+		KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.LeftHome);
+		KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome);
 
-			// draw autocomplete
-			GenerateSuggestions ();
+		KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend);
+		KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LeftExtend);
 
-			var renderAt = new Point (
-				Autocomplete.Context.CursorPosition, 0);
+		KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend);
+		KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.RightExtend);
 
-			Autocomplete.RenderOverlay (renderAt);
-		}
+		KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
+		KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
+		KeyBindings.Add ('B' + KeyCode.ShiftMask | KeyCode.AltMask, Command.WordLeftExtend);
 
-		private void RenderCaption ()
-		{
-			if (HasFocus || Caption == null || Caption.Length == 0
-				|| Text?.Length > 0) {
-				return;
-			}
+		KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
+		KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
+		KeyBindings.Add ('F' + KeyCode.ShiftMask | KeyCode.AltMask, Command.WordRightExtend);
 
-			var color = new Attribute (CaptionColor, GetNormalColor ().Background);
-			Driver.SetAttribute (color);
+		KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+		KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
 
-			Move (0, 0);
-			var render = Caption;
+		KeyBindings.Add (KeyCode.End, Command.RightEnd);
+		KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.RightEnd);
+		KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd);
 
-			if (render.GetColumns () > Bounds.Width) {
-				render = render [..Bounds.Width];
-			}
+		KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+		KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
+
+		KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine);
+		KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine);
+
+		KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo);
+		KeyBindings.Add (KeyCode.Backspace | KeyCode.AltMask, Command.Undo);
 
-			Driver.AddStr (render);
+		KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Redo);
+
+		KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft);
+		KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.WordLeft);
+		KeyBindings.Add ('B' + KeyCode.AltMask, Command.WordLeft);
+
+		KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight);
+		KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight);
+		KeyBindings.Add ('F' + KeyCode.AltMask, Command.WordRight);
+
+		KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards);
+		KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards);
+		KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite);
+		KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy);
+		KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut);
+		KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.Paste);
+		KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll);
+
+		KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.DeleteAll);
+		KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll);
+
+		_currentCulture = Thread.CurrentThread.CurrentUICulture;
+
+		ContextMenu = new ContextMenu (this, BuildContextMenuBarItem ());
+		ContextMenu.KeyChanged += ContextMenu_KeyChanged;
+
+		KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu);
+	}
+
+	void TextField_LayoutComplete (object sender, LayoutEventArgs e)
+	{
+		// Don't let height > 1
+		if (Frame.Height > 1) {
+			Height = 1;
 		}
+	}
 
-		private void GenerateSuggestions ()
-		{
-			var currentLine = TextModel.ToRuneCellList (Text);
-			var cursorPosition = Math.Min (this.CursorPosition, currentLine.Count);
-			Autocomplete.Context = new AutocompleteContext (currentLine, cursorPosition,
-				Autocomplete.Context != null ? Autocomplete.Context.Canceled : false);
 
-			Autocomplete.GenerateSuggestions (
-				Autocomplete.Context);
+	MenuBarItem BuildContextMenuBarItem () => new (new MenuItem [] {
+		new (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)),
+		new (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)),
+		new (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)),
+		new (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)),
+		new (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)),
+		new (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)),
+		new (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo))
+	});
+
+	void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode);
+
+	void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj)
+	{
+		if (obj == null) {
+			return;
 		}
 
-		/// <inheritdoc/>
-		public override Attribute GetNormalColor ()
-		{
-			return Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+		Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]);
+		CursorPosition = obj.CursorPosition.X;
+		Adjust ();
+	}
+
+	void TextField_Initialized (object sender, EventArgs e)
+	{
+		Autocomplete.HostControl = this;
+		Autocomplete.PopupInsideContainer = false;
+	}
+
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		if (IsInitialized) {
+			Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
 		}
 
-		Attribute GetReadOnlyColor ()
-		{
-			if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) {
-				return new Attribute (ColorScheme.Focus.Foreground, ColorScheme.Focus.Background);
-			}
-			return new Attribute (ColorScheme.Disabled.Foreground, ColorScheme.Focus.Background);
+		return base.OnEnter (view);
+	}
+
+	///<inheritdoc/>
+	public override bool OnLeave (View view)
+	{
+		if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
+			Application.UngrabMouse ();
 		}
+		//if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
+		//	ClearAllSelection ();
 
-		void Adjust ()
-		{
-			if (!IsAdded) {
-				return;
-			}
+		return base.OnLeave (view);
+	}
+
+	/// <summary>
+	/// Sets the cursor position.
+	/// </summary>
+	public override void PositionCursor ()
+	{
+		if (!IsInitialized) {
+			return;
+		}
+		ProcessAutocomplete ();
 
-			int offB = OffSetBackground ();
-			bool need = NeedsDisplay || !Used;
-			if (_cursorPosition < _first) {
-				_first = _cursorPosition;
-				need = true;
-			} else if (Frame.Width > 0 && (_first + _cursorPosition - (Frame.Width + offB) == 0 ||
-				  TextModel.DisplaySize (_text, _first, _cursorPosition).size >= Frame.Width + offB)) {
-
-				_first = Math.Max (TextModel.CalculateLeftColumn (_text, _first,
-					_cursorPosition, Frame.Width + offB), 0);
-				need = true;
+		var col = 0;
+		for (var idx = ScrollOffset < 0 ? 0 : ScrollOffset; idx < _text.Count; idx++) {
+			if (idx == _cursorPosition) {
+				break;
 			}
-			if (need) {
-				SetNeedsDisplay ();
+			var cols = _text [idx].GetColumns ();
+			TextModel.SetCol (ref col, Frame.Width - 1, cols);
+		}
+		var pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
+		var offB = OffSetBackground ();
+		var containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default;
+		var thisFrame = BoundsToScreen (Bounds);
+		if (pos > -1 && col >= pos && pos < Frame.Width + offB
+		    && containerFrame.IntersectsWith (thisFrame)) {
+			RestoreCursorVisibility ();
+			Move (col, 0);
+		} else {
+			HideCursorVisibility ();
+			if (pos < 0) {
+				Move (pos, 0);
 			} else {
-				PositionCursor ();
+				Move (pos - offB, 0);
 			}
 		}
+	}
 
-		int OffSetBackground ()
-		{
-			int offB = 0;
-			if (SuperView?.Frame.Right - Frame.Right < 0) {
-				offB = SuperView.Frame.Right - Frame.Right - 1;
-			}
-
-			return offB;
+	void HideCursorVisibility ()
+	{
+		if (_desiredCursorVisibility != CursorVisibility.Invisible) {
+			DesiredCursorVisibility = CursorVisibility.Invisible;
 		}
+	}
 
-		void SetText (List<Rune> newText)
-		{
-			Text = StringExtensions.ToString (newText);
+	void RestoreCursorVisibility ()
+	{
+		Application.Driver.GetCursorVisibility (out _visibility);
+		if (_desiredCursorVisibility != _savedCursorVisibility || _visibility != _savedCursorVisibility) {
+			DesiredCursorVisibility = _savedCursorVisibility;
 		}
+	}
 
-		void SetText (IEnumerable<Rune> newText)
-		{
-			SetText (newText.ToList ());
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		_isDrawing = true;
+
+		var selColor = new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground);
+		SetSelectedStartSelectedLength ();
+
+		Driver.SetAttribute (GetNormalColor ());
+		Move (0, 0);
+
+		var p = ScrollOffset;
+		var col = 0;
+		var width = Frame.Width + OffSetBackground ();
+		var tcount = _text.Count;
+		var roc = GetReadOnlyColor ();
+		for (var idx = p; idx < tcount; idx++) {
+			var rune = _text [idx];
+			var cols = rune.GetColumns ();
+			if (idx == _cursorPosition && HasFocus && !Used && SelectedLength == 0 && !ReadOnly) {
+				Driver.SetAttribute (selColor);
+			} else if (ReadOnly) {
+				Driver.SetAttribute (idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength ? selColor : roc);
+			} else if (!HasFocus && Enabled) {
+				Driver.SetAttribute (ColorScheme.Focus);
+			} else if (!Enabled) {
+				Driver.SetAttribute (roc);
+			} else {
+				Driver.SetAttribute (idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength ? selColor : ColorScheme.Focus);
+			}
+			if (col + cols <= width) {
+				Driver.AddRune (Secret ? Glyphs.Dot : rune);
+			}
+			if (!TextModel.SetCol (ref col, width, cols)) {
+				break;
+			}
+			if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width) {
+				break;
+			}
 		}
 
-		///<inheritdoc/>
-		public override bool CanFocus {
-			get => base.CanFocus;
-			set { base.CanFocus = value; }
+		Driver.SetAttribute (ColorScheme.Focus);
+		for (var i = col; i < width; i++) {
+			Driver.AddRune ((Rune)' ');
 		}
 
-		void SetClipboard (IEnumerable<Rune> text)
-		{
-			if (!Secret)
-				Clipboard.Contents = StringExtensions.ToString (text.ToList ());
+		PositionCursor ();
+
+		RenderCaption ();
+
+		ProcessAutocomplete ();
+
+		_isDrawing = false;
+	}
+
+	void ProcessAutocomplete ()
+	{
+		if (_isDrawing) {
+			return;
+		}
+		if (SelectedLength > 0) {
+			return;
 		}
 
-		int _preTextChangedCursorPos;
+		// draw autocomplete
+		GenerateSuggestions ();
 
-		///<inheritdoc/>
-		public override bool? OnInvokingKeyBindings (Key a)
-		{
-			// Give autocomplete first opportunity to respond to key presses
-			if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) {
-				return true;
-			}
-			return base.OnInvokingKeyBindings (a);
-		}
-
-		/// TODO: Flush out these docs
-		/// <summary>
-		/// Processes key presses for the <see cref="TextField"/>.
-		/// <remarks>
-		/// The <see cref="TextField"/> control responds to the following keys:
-		/// <list type="table">
-		///    <listheader>
-		///        <term>Keys</term>
-		///        <description>Function</description>
-		///    </listheader>
-		///    <item>
-		///        <term><see cref="Key.Delete"/>, <see cref="Key.Backspace"/></term>
-		///        <description>Deletes the character before cursor.</description>
-		///    </item>
-		/// </list>
-		/// </remarks>
-		/// </summary>
-		/// <param name="a"></param>
-		/// <returns></returns>
-		public override bool OnProcessKeyDown (Key a)
-		{
-			// Remember the cursor position because the new calculated cursor position is needed
-			// to be set BEFORE the TextChanged event is triggered.
-			// Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
-			_preTextChangedCursorPos = _cursorPosition;
+		var renderAt = new Point (
+			Autocomplete.Context.CursorPosition, 0);
 
-			// Ignore other control characters.
-			if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) {
-				return false;
-			}
+		Autocomplete.RenderOverlay (renderAt);
+	}
 
-			if (ReadOnly) {
-				return true;
-			}
+	void RenderCaption ()
+	{
+		if (HasFocus || Caption == null || Caption.Length == 0
+		    || Text?.Length > 0) {
+			return;
+		}
 
-			InsertText (a, true);
+		var color = new Attribute (CaptionColor, GetNormalColor ().Background);
+		Driver.SetAttribute (color);
 
-			return true;
+		Move (0, 0);
+		var render = Caption;
+
+		if (render.GetColumns () > Bounds.Width) {
+			render = render [..Bounds.Width];
 		}
 
-		void InsertText (Key a, bool usePreTextChangedCursorPos)
-		{
-			_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
+		Driver.AddStr (render);
+	}
 
-			List<Rune> newText = _text;
-			if (_length > 0) {
-				newText = DeleteSelectedText ();
-				_preTextChangedCursorPos = _cursorPosition;
-			}
-			if (!usePreTextChangedCursorPos) {
-				_preTextChangedCursorPos = _cursorPosition;
-			}
-			var kbstr = a.AsRune.ToString ().EnumerateRunes ();
-			if (Used) {
-				_cursorPosition++;
-				if (_cursorPosition == newText.Count + 1) {
-					SetText (newText.Concat (kbstr).ToList ());
-				} else {
-					if (_preTextChangedCursorPos > newText.Count) {
-						_preTextChangedCursorPos = newText.Count;
-					}
-					SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (_preTextChangedCursorPos, Math.Min (newText.Count - _preTextChangedCursorPos, newText.Count))));
-				}
-			} else {
-				SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (Math.Min (_preTextChangedCursorPos + 1, newText.Count), Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0))));
-				_cursorPosition++;
-			}
-			Adjust ();
+	void GenerateSuggestions ()
+	{
+		var currentLine = TextModel.ToRuneCellList (Text);
+		var cursorPosition = Math.Min (CursorPosition, currentLine.Count);
+		Autocomplete.Context = new AutocompleteContext (currentLine, cursorPosition,
+			Autocomplete.Context != null ? Autocomplete.Context.Canceled : false);
+
+		Autocomplete.GenerateSuggestions (
+			Autocomplete.Context);
+	}
+
+	/// <inheritdoc/>
+	public override Attribute GetNormalColor () => Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+
+	Attribute GetReadOnlyColor ()
+	{
+		if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) {
+			return new Attribute (ColorScheme.Focus.Foreground, ColorScheme.Focus.Background);
 		}
+		return new Attribute (ColorScheme.Disabled.Foreground, ColorScheme.Focus.Background);
+	}
 
-		void SetOverwrite (bool overwrite)
-		{
-			Used = overwrite;
+	void Adjust ()
+	{
+		if (!IsAdded) {
+			return;
+		}
+
+		var offB = OffSetBackground ();
+		var need = NeedsDisplay || !Used;
+		if (_cursorPosition < ScrollOffset) {
+			ScrollOffset = _cursorPosition;
+			need = true;
+		} else if (Frame.Width > 0 && (ScrollOffset + _cursorPosition - (Frame.Width + offB) == 0 ||
+					       TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Frame.Width + offB)) {
+
+			ScrollOffset = Math.Max (TextModel.CalculateLeftColumn (_text, ScrollOffset,
+				_cursorPosition, Frame.Width + offB), 0);
+			need = true;
+		}
+		if (need) {
 			SetNeedsDisplay ();
+		} else {
+			PositionCursor ();
 		}
+	}
 
-		TextModel GetModel ()
-		{
-			var model = new TextModel ();
-			model.LoadString (Text);
-			return model;
+	int OffSetBackground ()
+	{
+		var offB = 0;
+		if (SuperView?.Frame.Right - Frame.Right < 0) {
+			offB = SuperView.Frame.Right - Frame.Right - 1;
 		}
 
-		/// <summary>
-		/// Deletes word backwards.
-		/// </summary>
-		public virtual void KillWordBackwards ()
-		{
-			ClearAllSelection ();
-			var newPos = GetModel ().WordBackward (_cursorPosition, 0);
-			if (newPos == null) return;
-			if (newPos.Value.col != -1) {
-				SetText (_text.GetRange (0, newPos.Value.col).Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition)));
-				_cursorPosition = newPos.Value.col;
-			}
-			Adjust ();
+		return offB;
+	}
+
+	void SetText (List<Rune> newText) => Text = StringExtensions.ToString (newText);
+
+	void SetText (IEnumerable<Rune> newText) => SetText (newText.ToList ());
+
+	void SetClipboard (IEnumerable<Rune> text)
+	{
+		if (!Secret) {
+			Clipboard.Contents = StringExtensions.ToString (text.ToList ());
 		}
+	}
 
-		/// <summary>
-		/// Deletes word forwards.
-		/// </summary>
-		public virtual void KillWordForwards ()
-		{
-			ClearAllSelection ();
-			var newPos = GetModel ().WordForward (_cursorPosition, 0);
-			if (newPos == null) return;
-			if (newPos.Value.col != -1) {
-				SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col)));
-			}
-			Adjust ();
+	///<inheritdoc/>
+	public override bool? OnInvokingKeyBindings (Key a)
+	{
+		// Give autocomplete first opportunity to respond to key presses
+		if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) {
+			return true;
 		}
+		return base.OnInvokingKeyBindings (a);
+	}
 
-		void MoveWordRight ()
-		{
-			ClearAllSelection ();
-			var newPos = GetModel ().WordForward (_cursorPosition, 0);
-			if (newPos == null) return;
-			if (newPos.Value.col != -1)
-				_cursorPosition = newPos.Value.col;
-			Adjust ();
+	/// TODO: Flush out these docs
+	/// <summary>
+	/// Processes key presses for the <see cref="TextField"/>.
+	/// <remarks>
+	/// The <see cref="TextField"/> control responds to the following keys:
+	/// <list type="table">
+	///         <listheader>
+	///                 <term>Keys</term>
+	///                 <description>Function</description>
+	///         </listheader>
+	///         <item>
+	///                 <term><see cref="Key.Delete"/>, <see cref="Key.Backspace"/></term>
+	///                 <description>Deletes the character before cursor.</description>
+	///         </item>
+	/// </list>
+	/// </remarks>
+	/// </summary>
+	/// <param name="a"></param>
+	/// <returns></returns>
+	public override bool OnProcessKeyDown (Key a)
+	{
+		// Remember the cursor position because the new calculated cursor position is needed
+		// to be set BEFORE the TextChanged event is triggered.
+		// Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
+		_preTextChangedCursorPos = _cursorPosition;
+
+		// Ignore other control characters.
+		if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) {
+			return false;
 		}
 
-		void MoveWordLeft ()
-		{
-			ClearAllSelection ();
-			var newPos = GetModel ().WordBackward (_cursorPosition, 0);
-			if (newPos == null) return;
-			if (newPos.Value.col != -1)
-				_cursorPosition = newPos.Value.col;
-			Adjust ();
+		if (ReadOnly) {
+			return true;
 		}
 
-		/// <summary>
-		/// Redoes the latest changes.
-		/// </summary>
-		public void Redo ()
-		{
-			if (ReadOnly) {
-				return;
+		InsertText (a, true);
+
+		return true;
+	}
+
+	void InsertText (Key a, bool usePreTextChangedCursorPos)
+	{
+		_historyText.Add (new List<List<RuneCell>> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
+
+		var newText = _text;
+		if (SelectedLength > 0) {
+			newText = DeleteSelectedText ();
+			_preTextChangedCursorPos = _cursorPosition;
+		}
+		if (!usePreTextChangedCursorPos) {
+			_preTextChangedCursorPos = _cursorPosition;
+		}
+		var kbstr = a.AsRune.ToString ().EnumerateRunes ();
+		if (Used) {
+			_cursorPosition++;
+			if (_cursorPosition == newText.Count + 1) {
+				SetText (newText.Concat (kbstr).ToList ());
+			} else {
+				if (_preTextChangedCursorPos > newText.Count) {
+					_preTextChangedCursorPos = newText.Count;
+				}
+				SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (_preTextChangedCursorPos, Math.Min (newText.Count - _preTextChangedCursorPos, newText.Count))));
 			}
+		} else {
+			SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (Math.Min (_preTextChangedCursorPos + 1, newText.Count), Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0))));
+			_cursorPosition++;
+		}
+		Adjust ();
+	}
 
-			_historyText.Redo ();
+	void SetOverwrite (bool overwrite)
+	{
+		Used = overwrite;
+		SetNeedsDisplay ();
+	}
+
+	TextModel GetModel ()
+	{
+		var model = new TextModel ();
+		model.LoadString (Text);
+		return model;
+	}
+
+	/// <summary>
+	/// Deletes word backwards.
+	/// </summary>
+	public virtual void KillWordBackwards ()
+	{
+		ClearAllSelection ();
+		var newPos = GetModel ().WordBackward (_cursorPosition, 0);
+		if (newPos == null) {
+			return;
+		}
+		if (newPos.Value.col != -1) {
+			SetText (_text.GetRange (0, newPos.Value.col).Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition)));
+			_cursorPosition = newPos.Value.col;
+		}
+		Adjust ();
+	}
+
+	/// <summary>
+	/// Deletes word forwards.
+	/// </summary>
+	public virtual void KillWordForwards ()
+	{
+		ClearAllSelection ();
+		var newPos = GetModel ().WordForward (_cursorPosition, 0);
+		if (newPos == null) {
+			return;
+		}
+		if (newPos.Value.col != -1) {
+			SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col)));
+		}
+		Adjust ();
+	}
+
+	void MoveWordRight ()
+	{
+		ClearAllSelection ();
+		var newPos = GetModel ().WordForward (_cursorPosition, 0);
+		if (newPos == null) {
+			return;
+		}
+		if (newPos.Value.col != -1) {
+			_cursorPosition = newPos.Value.col;
+		}
+		Adjust ();
+	}
+
+	void MoveWordLeft ()
+	{
+		ClearAllSelection ();
+		var newPos = GetModel ().WordBackward (_cursorPosition, 0);
+		if (newPos == null) {
+			return;
+		}
+		if (newPos.Value.col != -1) {
+			_cursorPosition = newPos.Value.col;
+		}
+		Adjust ();
+	}
 
-			//if (string.IsNullOrEmpty (Clipboard.Contents))
-			//	return true;
-			//var clip = TextModel.ToRunes (Clipboard.Contents);
-			//if (clip == null)
-			//	return true;
+	/// <summary>
+	/// Redoes the latest changes.
+	/// </summary>
+	public void Redo ()
+	{
+		if (ReadOnly) {
+			return;
+		}
+
+		_historyText.Redo ();
+
+		//if (string.IsNullOrEmpty (Clipboard.Contents))
+		//	return true;
+		//var clip = TextModel.ToRunes (Clipboard.Contents);
+		//if (clip == null)
+		//	return true;
+
+		//if (point == text.Count) {
+		//	point = text.Count;
+		//	SetText(text.Concat(clip).ToList());
+		//} else {
+		//	point += clip.Count;
+		//	SetText(text.GetRange(0, oldCursorPos).Concat(clip).Concat(text.GetRange(oldCursorPos, text.Count - oldCursorPos)));
+		//}
+		//Adjust ();
+	}
 
-			//if (point == text.Count) {
-			//	point = text.Count;
-			//	SetText(text.Concat(clip).ToList());
-			//} else {
-			//	point += clip.Count;
-			//	SetText(text.GetRange(0, oldCursorPos).Concat(clip).Concat(text.GetRange(oldCursorPos, text.Count - oldCursorPos)));
-			//}
-			//Adjust ();
+	/// <summary>
+	/// Undoes the latest changes.
+	/// </summary>
+	public void Undo ()
+	{
+		if (ReadOnly) {
+			return;
 		}
 
-		/// <summary>
-		/// Undoes the latest changes.
-		/// </summary>
-		public void Undo ()
-		{
-			if (ReadOnly) {
-				return;
-			}
+		_historyText.Undo ();
+	}
 
-			_historyText.Undo ();
+	void KillToStart ()
+	{
+		if (ReadOnly) {
+			return;
 		}
 
-		void KillToStart ()
-		{
-			if (ReadOnly)
-				return;
-
-			ClearAllSelection ();
-			if (_cursorPosition == 0)
-				return;
-			SetClipboard (_text.GetRange (0, _cursorPosition));
-			SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
-			_cursorPosition = 0;
-			Adjust ();
+		ClearAllSelection ();
+		if (_cursorPosition == 0) {
+			return;
 		}
+		SetClipboard (_text.GetRange (0, _cursorPosition));
+		SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
+		_cursorPosition = 0;
+		Adjust ();
+	}
 
-		void KillToEnd ()
-		{
-			if (ReadOnly)
-				return;
+	void KillToEnd ()
+	{
+		if (ReadOnly) {
+			return;
+		}
 
-			ClearAllSelection ();
-			if (_cursorPosition >= _text.Count)
-				return;
-			SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
-			SetText (_text.GetRange (0, _cursorPosition));
-			Adjust ();
+		ClearAllSelection ();
+		if (_cursorPosition >= _text.Count) {
+			return;
 		}
+		SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
+		SetText (_text.GetRange (0, _cursorPosition));
+		Adjust ();
+	}
 
-		void MoveRight ()
-		{
-			ClearAllSelection ();
-			if (_cursorPosition == _text.Count)
-				return;
-			_cursorPosition++;
-			Adjust ();
+	void MoveRight ()
+	{
+		ClearAllSelection ();
+		if (_cursorPosition == _text.Count) {
+			return;
 		}
+		_cursorPosition++;
+		Adjust ();
+	}
 
-		/// <summary>
-		/// Moves cursor to the end of the typed text.
-		/// </summary>
-		public void MoveEnd ()
-		{
-			ClearAllSelection ();
-			_cursorPosition = _text.Count;
+	/// <summary>
+	/// Moves cursor to the end of the typed text.
+	/// </summary>
+	public void MoveEnd ()
+	{
+		ClearAllSelection ();
+		_cursorPosition = _text.Count;
+		Adjust ();
+	}
+
+	void MoveLeft ()
+	{
+		ClearAllSelection ();
+		if (_cursorPosition > 0) {
+			_cursorPosition--;
 			Adjust ();
 		}
+	}
 
-		void MoveLeft ()
-		{
-			ClearAllSelection ();
-			if (_cursorPosition > 0) {
-				_cursorPosition--;
-				Adjust ();
+	void MoveWordRightExtend ()
+	{
+		if (_cursorPosition < _text.Count) {
+			var x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
+			var newPos = GetModel ().WordForward (x, 0);
+			if (newPos == null) {
+				return;
+			}
+			if (newPos.Value.col != -1) {
+				_cursorPosition = newPos.Value.col;
 			}
+			PrepareSelection (x, newPos.Value.col - x);
 		}
+	}
 
-		void MoveWordRightExtend ()
-		{
-			if (_cursorPosition < _text.Count) {
-				int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
-				var newPos = GetModel ().WordForward (x, 0);
-				if (newPos == null) return;
-				if (newPos.Value.col != -1)
+	void MoveWordLeftExtend ()
+	{
+		if (_cursorPosition > 0) {
+			var x = Math.Min (_start > -1 && _start > _cursorPosition ? _start : _cursorPosition, _text.Count);
+			if (x > 0) {
+				var newPos = GetModel ().WordBackward (x, 0);
+				if (newPos == null) {
+					return;
+				}
+				if (newPos.Value.col != -1) {
 					_cursorPosition = newPos.Value.col;
+				}
 				PrepareSelection (x, newPos.Value.col - x);
 			}
 		}
+	}
 
-		void MoveWordLeftExtend ()
-		{
-			if (_cursorPosition > 0) {
-				int x = Math.Min (_start > -1 && _start > _cursorPosition ? _start : _cursorPosition, _text.Count);
-				if (x > 0) {
-					var newPos = GetModel ().WordBackward (x, 0);
-					if (newPos == null) return;
-					if (newPos.Value.col != -1)
-						_cursorPosition = newPos.Value.col;
-					PrepareSelection (x, newPos.Value.col - x);
-				}
-			}
+	void MoveRightExtend ()
+	{
+		if (_cursorPosition < _text.Count) {
+			PrepareSelection (_cursorPosition++, 1);
 		}
+	}
 
-		void MoveRightExtend ()
-		{
-			if (_cursorPosition < _text.Count) {
-				PrepareSelection (_cursorPosition++, 1);
-			}
+	void MoveLeftExtend ()
+	{
+		if (_cursorPosition > 0) {
+			PrepareSelection (_cursorPosition--, -1);
 		}
+	}
 
-		void MoveLeftExtend ()
-		{
-			if (_cursorPosition > 0) {
-				PrepareSelection (_cursorPosition--, -1);
-			}
+	void MoveHome ()
+	{
+		ClearAllSelection ();
+		_cursorPosition = 0;
+		Adjust ();
+	}
+
+	void MoveEndExtend ()
+	{
+		if (_cursorPosition <= _text.Count) {
+			var x = _cursorPosition;
+			_cursorPosition = _text.Count;
+			PrepareSelection (x, _cursorPosition - x);
 		}
+	}
 
-		void MoveHome ()
-		{
-			ClearAllSelection ();
+	void MoveHomeExtend ()
+	{
+		if (_cursorPosition > 0) {
+			var x = _cursorPosition;
 			_cursorPosition = 0;
-			Adjust ();
+			PrepareSelection (x, _cursorPosition - x);
 		}
+	}
 
-		void MoveEndExtend ()
-		{
-			if (_cursorPosition <= _text.Count) {
-				int x = _cursorPosition;
-				_cursorPosition = _text.Count;
-				PrepareSelection (x, _cursorPosition - x);
-			}
+	/// <summary>
+	/// Deletes the character to the left.
+	/// </summary>
+	/// <param name="usePreTextChangedCursorPos">
+	/// If set to <see langword="true">true</see> use the cursor position cached
+	/// ; otherwise use <see cref="CursorPosition"/>.
+	/// use .
+	/// </param>
+	public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
+	{
+		if (ReadOnly) {
+			return;
 		}
 
-		void MoveHomeExtend ()
-		{
-			if (_cursorPosition > 0) {
-				int x = _cursorPosition;
-				_cursorPosition = 0;
-				PrepareSelection (x, _cursorPosition - x);
-			}
-		}
+		_historyText.Add (new List<List<RuneCell>> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
 
-		/// <summary>
-		/// Deletes the character to the left.
-		/// </summary>
-		/// <param name="usePreTextChangedCursorPos">If set to <see langword="true">true</see> use the cursor position cached
-		/// ; otherwise use <see cref="CursorPosition"/>.
-		/// use .</param>
-		public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
-		{
-			if (ReadOnly)
+		if (SelectedLength == 0) {
+			if (_cursorPosition == 0) {
 				return;
+			}
 
-			_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
-
-			if (_length == 0) {
-				if (_cursorPosition == 0)
-					return;
-
-				if (!usePreTextChangedCursorPos) {
-					_preTextChangedCursorPos = _cursorPosition;
-				}
-				_cursorPosition--;
-				if (_preTextChangedCursorPos < _text.Count) {
-					SetText (_text.GetRange (0, _preTextChangedCursorPos - 1).Concat (_text.GetRange (_preTextChangedCursorPos, _text.Count - _preTextChangedCursorPos)));
-				} else {
-					SetText (_text.GetRange (0, _preTextChangedCursorPos - 1));
-				}
-				Adjust ();
+			if (!usePreTextChangedCursorPos) {
+				_preTextChangedCursorPos = _cursorPosition;
+			}
+			_cursorPosition--;
+			if (_preTextChangedCursorPos < _text.Count) {
+				SetText (_text.GetRange (0, _preTextChangedCursorPos - 1).Concat (_text.GetRange (_preTextChangedCursorPos, _text.Count - _preTextChangedCursorPos)));
 			} else {
-				var newText = DeleteSelectedText ();
-				Text = StringExtensions.ToString (newText);
-				Adjust ();
+				SetText (_text.GetRange (0, _preTextChangedCursorPos - 1));
 			}
+			Adjust ();
+		} else {
+			var newText = DeleteSelectedText ();
+			Text = StringExtensions.ToString (newText);
+			Adjust ();
 		}
+	}
 
-		/// <summary>
-		/// Deletes the character to the right.
-		/// </summary>
-		public virtual void DeleteCharRight ()
-		{
-			if (ReadOnly)
-				return;
-
-			_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
+	/// <summary>
+	/// Deletes the character to the right.
+	/// </summary>
+	public virtual void DeleteCharRight ()
+	{
+		if (ReadOnly) {
+			return;
+		}
 
-			if (_length == 0) {
-				if (_text.Count == 0 || _text.Count == _cursorPosition)
-					return;
+		_historyText.Add (new List<List<RuneCell>> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
 
-				SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1))));
-				Adjust ();
-			} else {
-				var newText = DeleteSelectedText ();
-				Text = StringExtensions.ToString (newText);
-				Adjust ();
+		if (SelectedLength == 0) {
+			if (_text.Count == 0 || _text.Count == _cursorPosition) {
+				return;
 			}
+
+			SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1))));
+			Adjust ();
+		} else {
+			var newText = DeleteSelectedText ();
+			Text = StringExtensions.ToString (newText);
+			Adjust ();
 		}
+	}
 
-		void ShowContextMenu ()
-		{
-			if (_currentCulture != Thread.CurrentThread.CurrentUICulture) {
+	void ShowContextMenu ()
+	{
+		if (_currentCulture != Thread.CurrentThread.CurrentUICulture) {
 
-				_currentCulture = Thread.CurrentThread.CurrentUICulture;
+			_currentCulture = Thread.CurrentThread.CurrentUICulture;
 
-				ContextMenu.MenuItems = BuildContextMenuBarItem ();
-			}
-			ContextMenu.Show ();
+			ContextMenu.MenuItems = BuildContextMenuBarItem ();
 		}
+		ContextMenu.Show ();
+	}
 
-		/// <summary>
-		/// Selects all text.
-		/// </summary>
-		public void SelectAll ()
-		{
-			if (_text.Count == 0) {
-				return;
-			}
-
-			_selectedStart = 0;
-			MoveEndExtend ();
-			SetNeedsDisplay ();
+	/// <summary>
+	/// Selects all text.
+	/// </summary>
+	public void SelectAll ()
+	{
+		if (_text.Count == 0) {
+			return;
 		}
 
-		/// <summary>
-		/// Deletes all text.
-		/// </summary>
-		public void DeleteAll ()
-		{
-			if (_text.Count == 0) {
-				return;
-			}
+		_selectedStart = 0;
+		MoveEndExtend ();
+		SetNeedsDisplay ();
+	}
 
-			_selectedStart = 0;
-			MoveEndExtend ();
-			DeleteCharLeft (false);
-			SetNeedsDisplay ();
+	/// <summary>
+	/// Deletes all text.
+	/// </summary>
+	public void DeleteAll ()
+	{
+		if (_text.Count == 0) {
+			return;
 		}
 
-		/// <summary>
-		/// Start position of the selected text.
-		/// </summary>
-		public int SelectedStart {
-			get => _selectedStart;
-			set {
-				if (value < -1) {
-					_selectedStart = -1;
-				} else if (value > _text.Count) {
-					_selectedStart = _text.Count;
-				} else {
-					_selectedStart = value;
-				}
-				PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
-			}
+		_selectedStart = 0;
+		MoveEndExtend ();
+		DeleteCharLeft (false);
+		SetNeedsDisplay ();
+	}
+
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent ev)
+	{
+		if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed) && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition) &&
+		    !ev.Flags.HasFlag (MouseFlags.Button1Released) && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
+		    !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) && !ev.Flags.HasFlag (ContextMenu.MouseFlags)) {
+			return false;
 		}
 
-		/// <summary>
-		/// Length of the selected text.
-		/// </summary>
-		public int SelectedLength { get => _length; }
+		if (!CanFocus) {
+			return true;
+		}
 
-		/// <summary>
-		/// The selected text.
-		/// </summary>
-		public string SelectedText {
-			get => Secret ? null : _selectedText;
-			private set => _selectedText = value;
+		if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition) {
+			SetFocus ();
 		}
 
-		int _start, _length;
-		bool _isButtonPressed;
-		bool _isButtonReleased = true;
+		// Give autocomplete first opportunity to respond to mouse clicks
+		if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) {
+			return true;
+		}
 
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent ev)
-		{
-			if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed) && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition) &&
-				!ev.Flags.HasFlag (MouseFlags.Button1Released) && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
-				!ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) && !ev.Flags.HasFlag (ContextMenu.MouseFlags)) {
-				return false;
+		if (ev.Flags == MouseFlags.Button1Pressed) {
+			EnsureHasFocus ();
+			PositionCursor (ev);
+			if (_isButtonReleased) {
+				ClearAllSelection ();
 			}
-
-			if (!CanFocus) {
-				return true;
+			_isButtonReleased = true;
+			_isButtonPressed = true;
+		} else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed) {
+			var x = PositionCursor (ev);
+			_isButtonReleased = false;
+			PrepareSelection (x);
+			if (Application.MouseGrabView == null) {
+				Application.GrabMouse (this);
 			}
-
-			if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition) {
-				SetFocus ();
+		} else if (ev.Flags == MouseFlags.Button1Released) {
+			_isButtonReleased = true;
+			_isButtonPressed = false;
+			Application.UngrabMouse ();
+		} else if (ev.Flags == MouseFlags.Button1DoubleClicked) {
+			EnsureHasFocus ();
+			var x = PositionCursor (ev);
+			var sbw = x;
+			if (x == _text.Count || x > 0 && (char)_text [x - 1].Value != ' '
+					     || x > 0 && (char)_text [x].Value == ' ') {
+
+				var newPosBw = GetModel ().WordBackward (x, 0);
+				if (newPosBw == null) {
+					return true;
+				}
+				sbw = newPosBw.Value.col;
 			}
-
-			// Give autocomplete first opportunity to respond to mouse clicks
-			if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) {
-				return true;
+			if (sbw != -1) {
+				x = sbw;
+				PositionCursor (x);
 			}
-
-			if (ev.Flags == MouseFlags.Button1Pressed) {
-				EnsureHasFocus ();
-				PositionCursor (ev);
-				if (_isButtonReleased) {
-					ClearAllSelection ();
-				}
-				_isButtonReleased = true;
-				_isButtonPressed = true;
-			} else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed) {
-				int x = PositionCursor (ev);
-				_isButtonReleased = false;
-				PrepareSelection (x);
-				if (Application.MouseGrabView == null) {
-					Application.GrabMouse (this);
-				}
-			} else if (ev.Flags == MouseFlags.Button1Released) {
-				_isButtonReleased = true;
-				_isButtonPressed = false;
-				Application.UngrabMouse ();
-			} else if (ev.Flags == MouseFlags.Button1DoubleClicked) {
-				EnsureHasFocus ();
-				int x = PositionCursor (ev);
-				int sbw = x;
-				if (x == _text.Count || (x > 0 && (char)_text [x - 1].Value != ' ')
-					|| (x > 0 && (char)_text [x].Value == ' ')) {
-
-					var newPosBw = GetModel ().WordBackward (x, 0);
-					if (newPosBw == null) return true;
-					sbw = newPosBw.Value.col;
-				}
-				if (sbw != -1) {
-					x = sbw;
-					PositionCursor (x);
-				}
-				var newPosFw = GetModel ().WordForward (x, 0);
-				if (newPosFw == null) return true;
-				ClearAllSelection ();
-				if (newPosFw.Value.col != -1 && sbw != -1) {
-					_cursorPosition = newPosFw.Value.col;
-				}
-				PrepareSelection (sbw, newPosFw.Value.col - sbw);
-			} else if (ev.Flags == MouseFlags.Button1TripleClicked) {
-				EnsureHasFocus ();
-				PositionCursor (0);
-				ClearAllSelection ();
-				PrepareSelection (0, _text.Count);
-			} else if (ev.Flags == ContextMenu.MouseFlags) {
-				ShowContextMenu ();
+			var newPosFw = GetModel ().WordForward (x, 0);
+			if (newPosFw == null) {
+				return true;
 			}
-
-			SetNeedsDisplay ();
-			return true;
-
-			void EnsureHasFocus ()
-			{
-				if (!HasFocus) {
-					SetFocus ();
-				}
+			ClearAllSelection ();
+			if (newPosFw.Value.col != -1 && sbw != -1) {
+				_cursorPosition = newPosFw.Value.col;
 			}
+			PrepareSelection (sbw, newPosFw.Value.col - sbw);
+		} else if (ev.Flags == MouseFlags.Button1TripleClicked) {
+			EnsureHasFocus ();
+			PositionCursor (0);
+			ClearAllSelection ();
+			PrepareSelection (0, _text.Count);
+		} else if (ev.Flags == ContextMenu.MouseFlags) {
+			ShowContextMenu ();
 		}
 
-		int PositionCursor (MouseEvent ev)
+		SetNeedsDisplay ();
+		return true;
+
+		void EnsureHasFocus ()
 		{
-			// We could also set the cursor position.
-			int x;
-			var pX = TextModel.GetColFromX (_text, _first, ev.X);
-			if (_text.Count == 0) {
-				x = pX - ev.OfX;
-			} else {
-				x = pX;
+			if (!HasFocus) {
+				SetFocus ();
 			}
-			return PositionCursor (x, false);
 		}
+	}
 
-		int PositionCursor (int x, bool getX = true)
-		{
-			int pX = x;
-			if (getX) {
-				pX = TextModel.GetColFromX (_text, _first, x);
-			}
-			if (_first + pX > _text.Count) {
-				_cursorPosition = _text.Count;
-			} else if (_first + pX < _first) {
-				_cursorPosition = 0;
-			} else {
-				_cursorPosition = _first + pX;
-			}
+	int PositionCursor (MouseEvent ev)
+	{
+		// We could also set the cursor position.
+		int x;
+		var pX = TextModel.GetColFromX (_text, ScrollOffset, ev.X);
+		if (_text.Count == 0) {
+			x = pX - ev.OfX;
+		} else {
+			x = pX;
+		}
+		return PositionCursor (x, false);
+	}
 
-			return _cursorPosition;
+	int PositionCursor (int x, bool getX = true)
+	{
+		var pX = x;
+		if (getX) {
+			pX = TextModel.GetColFromX (_text, ScrollOffset, x);
+		}
+		if (ScrollOffset + pX > _text.Count) {
+			_cursorPosition = _text.Count;
+		} else if (ScrollOffset + pX < ScrollOffset) {
+			_cursorPosition = 0;
+		} else {
+			_cursorPosition = ScrollOffset + pX;
 		}
 
-		void PrepareSelection (int x, int direction = 0)
-		{
-			x = x + _first < -1 ? 0 : x;
-			_selectedStart = _selectedStart == -1 && _text.Count > 0 && x >= 0 && x <= _text.Count ? x : _selectedStart;
-			if (_selectedStart > -1) {
-				_length = Math.Abs (x + direction <= _text.Count ? x + direction - _selectedStart : _text.Count - _selectedStart);
-				SetSelectedStartSelectedLength ();
-				if (_start > -1 && _length > 0) {
-					_selectedText = _length > 0 ? StringExtensions.ToString (_text.GetRange (
-						_start < 0 ? 0 : _start, _length > _text.Count ? _text.Count : _length)) : "";
-					if (_first > _start) {
-						_first = _start;
-					}
-				} else if (_start > -1 && _length == 0) {
-					_selectedText = null;
+		return _cursorPosition;
+	}
+
+	void PrepareSelection (int x, int direction = 0)
+	{
+		x = x + ScrollOffset < -1 ? 0 : x;
+		_selectedStart = _selectedStart == -1 && _text.Count > 0 && x >= 0 && x <= _text.Count ? x : _selectedStart;
+		if (_selectedStart > -1) {
+			SelectedLength = Math.Abs (x + direction <= _text.Count ? x + direction - _selectedStart : _text.Count - _selectedStart);
+			SetSelectedStartSelectedLength ();
+			if (_start > -1 && SelectedLength > 0) {
+				_selectedText = SelectedLength > 0 ? StringExtensions.ToString (_text.GetRange (
+					_start < 0 ? 0 : _start, SelectedLength > _text.Count ? _text.Count : SelectedLength)) : "";
+				if (ScrollOffset > _start) {
+					ScrollOffset = _start;
 				}
-				SetNeedsDisplay ();
-			} else if (_length > 0 || _selectedText != null) {
-				ClearAllSelection ();
+			} else if (_start > -1 && SelectedLength == 0) {
+				_selectedText = null;
 			}
-			Adjust ();
+			SetNeedsDisplay ();
+		} else if (SelectedLength > 0 || _selectedText != null) {
+			ClearAllSelection ();
 		}
+		Adjust ();
+	}
 
-		/// <summary>
-		/// Clear the selected text.
-		/// </summary>
-		public void ClearAllSelection ()
-		{
-			if (_selectedStart == -1 && _length == 0 && string.IsNullOrEmpty (_selectedText)) {
-				return;
-			}
+	/// <summary>
+	/// Clear the selected text.
+	/// </summary>
+	public void ClearAllSelection ()
+	{
+		if (_selectedStart == -1 && SelectedLength == 0 && string.IsNullOrEmpty (_selectedText)) {
+			return;
+		}
+
+		_selectedStart = -1;
+		SelectedLength = 0;
+		_selectedText = null;
+		_start = 0;
+		SelectedLength = 0;
+		SetNeedsDisplay ();
+	}
 
-			_selectedStart = -1;
-			_length = 0;
-			_selectedText = null;
-			_start = 0;
-			_length = 0;
-			SetNeedsDisplay ();
+	void SetSelectedStartSelectedLength ()
+	{
+		if (SelectedStart > -1 && _cursorPosition < SelectedStart) {
+			_start = _cursorPosition;
+		} else {
+			_start = SelectedStart;
 		}
+	}
 
-		void SetSelectedStartSelectedLength ()
-		{
-			if (SelectedStart > -1 && _cursorPosition < SelectedStart) {
-				_start = _cursorPosition;
-			} else {
-				_start = SelectedStart;
-			}
+	/// <summary>
+	/// Copy the selected text to the clipboard.
+	/// </summary>
+	public virtual void Copy ()
+	{
+		if (Secret || SelectedLength == 0) {
+			return;
 		}
 
-		/// <summary>
-		/// Copy the selected text to the clipboard.
-		/// </summary>
-		public virtual void Copy ()
-		{
-			if (Secret || _length == 0)
-				return;
+		Clipboard.Contents = SelectedText;
+	}
 
-			Clipboard.Contents = SelectedText;
+	/// <summary>
+	/// Cut the selected text to the clipboard.
+	/// </summary>
+	public virtual void Cut ()
+	{
+		if (ReadOnly || Secret || SelectedLength == 0) {
+			return;
 		}
 
-		/// <summary>
-		/// Cut the selected text to the clipboard.
-		/// </summary>
-		public virtual void Cut ()
-		{
-			if (ReadOnly || Secret || _length == 0)
-				return;
+		Clipboard.Contents = SelectedText;
+		var newText = DeleteSelectedText ();
+		Text = StringExtensions.ToString (newText);
+		Adjust ();
+	}
 
-			Clipboard.Contents = SelectedText;
-			var newText = DeleteSelectedText ();
-			Text = StringExtensions.ToString (newText);
-			Adjust ();
-		}
+	List<Rune> DeleteSelectedText ()
+	{
+		SetSelectedStartSelectedLength ();
+		var selStart = SelectedStart > -1 ? _start : _cursorPosition;
+		var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) +
+			      StringExtensions.ToString (_text.GetRange (selStart + SelectedLength, _text.Count - (selStart + SelectedLength)));
 
-		List<Rune> DeleteSelectedText ()
-		{
-			SetSelectedStartSelectedLength ();
-			int selStart = SelectedStart > -1 ? _start : _cursorPosition;
-			var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) +
-				 StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
+		ClearAllSelection ();
+		_cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
+		return newText.ToRuneList ();
+	}
 
-			ClearAllSelection ();
-			_cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
-			return newText.ToRuneList ();
+	/// <summary>
+	/// Paste the selected text from the clipboard.
+	/// </summary>
+	public virtual void Paste ()
+	{
+		if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents)) {
+			return;
 		}
 
-		/// <summary>
-		/// Paste the selected text from the clipboard.
-		/// </summary>
-		public virtual void Paste ()
-		{
-			if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents)) {
-				return;
-			}
-
-			SetSelectedStartSelectedLength ();
-			int selStart = _start == -1 ? CursorPosition : _start;
-			string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
-			Text = StringExtensions.ToString (_text.GetRange (0, selStart)) +
-				cbTxt +
-				StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
+		SetSelectedStartSelectedLength ();
+		var selStart = _start == -1 ? CursorPosition : _start;
+		var cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
+		Text = StringExtensions.ToString (_text.GetRange (0, selStart)) +
+		       cbTxt +
+		       StringExtensions.ToString (_text.GetRange (selStart + SelectedLength, _text.Count - (selStart + SelectedLength)));
 
-			_cursorPosition = selStart + cbTxt.GetRuneCount ();
+			_cursorPosition = Math.Min (selStart + cbTxt.GetRuneCount (), _text.Count);
 			ClearAllSelection ();
 			SetNeedsDisplay ();
 			Adjust ();
 		}
 
-		/// <summary>
-		/// Virtual method that invoke the <see cref="TextChanging"/> event if it's defined.
-		/// </summary>
-		/// <param name="newText">The new text to be replaced.</param>
-		/// <returns>Returns the <see cref="TextChangingEventArgs"/></returns>
-		public virtual TextChangingEventArgs OnTextChanging (string newText)
-		{
-			var ev = new TextChangingEventArgs (newText);
-			TextChanging?.Invoke (this, ev);
-			return ev;
-		}
-
-		CursorVisibility _desiredCursorVisibility = CursorVisibility.Default;
-
-		/// <summary>
-		/// Get / Set the wished cursor when the field is focused
-		/// </summary>
-		public CursorVisibility DesiredCursorVisibility {
-			get => _desiredCursorVisibility;
-			set {
-				if ((_desiredCursorVisibility != value || _visibility != value) && HasFocus) {
-					Application.Driver.SetCursorVisibility (value);
-				}
-
-				_desiredCursorVisibility = _visibility = value;
-			}
-		}
-
-		/// <summary>
-		/// Inserts the given <paramref name="toAdd"/> text at the current cursor position
-		/// exactly as if the user had just typed it
-		/// </summary>
-		/// <param name="toAdd">Text to add</param>
-		/// <param name="useOldCursorPos">Use the previous cursor position.</param>
-		public void InsertText (string toAdd, bool useOldCursorPos = true)
-		{
-			foreach (var ch in toAdd) {
+	/// <summary>
+	/// Virtual method that invoke the <see cref="TextChanging"/> event if it's defined.
+	/// </summary>
+	/// <param name="newText">The new text to be replaced.</param>
+	/// <returns>Returns the <see cref="TextChangingEventArgs"/></returns>
+	public virtual TextChangingEventArgs OnTextChanging (string newText)
+	{
+		var ev = new TextChangingEventArgs (newText);
+		TextChanging?.Invoke (this, ev);
+		return ev;
+	}
 
-				KeyCode key;
+	/// <summary>
+	/// Inserts the given <paramref name="toAdd"/> text at the current cursor position
+	/// exactly as if the user had just typed it
+	/// </summary>
+	/// <param name="toAdd">Text to add</param>
+	/// <param name="useOldCursorPos">Use the previous cursor position.</param>
+	public void InsertText (string toAdd, bool useOldCursorPos = true)
+	{
+		foreach (var ch in toAdd) {
 
-				try {
-					key = (KeyCode)ch;
-				} catch (Exception) {
+			KeyCode key;
 
-					throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
-				}
+			try {
+				key = (KeyCode)ch;
+			} catch (Exception) {
 
-				InsertText (new Key () { KeyCode = key }, useOldCursorPos);
+				throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
 			}
-		}
 
-		/// <summary>
-		/// Allows clearing the <see cref="HistoryText.HistoryTextItem"/> items updating the original text.
-		/// </summary>
-		public void ClearHistoryChanges ()
-		{
-			_historyText.Clear (Text);
+			InsertText (new Key { KeyCode = key }, useOldCursorPos);
 		}
+	}
 
-		/// <summary>
-		/// Returns <see langword="true"/> if the current cursor position is
-		/// at the end of the <see cref="Text"/>. This includes when it is empty.
-		/// </summary>
-		/// <returns></returns>
-		internal bool CursorIsAtEnd ()
-		{
-			return CursorPosition == Text.Length;
-		}
+	/// <summary>
+	/// Allows clearing the <see cref="HistoryText.HistoryTextItem"/> items updating the original text.
+	/// </summary>
+	public void ClearHistoryChanges () => _historyText.Clear (Text);
 
-		/// <summary>
-		/// Returns <see langword="true"/> if the current cursor position is
-		/// at the start of the <see cref="TextField"/>.
-		/// </summary>
-		/// <returns></returns>
-		internal bool CursorIsAtStart ()
-		{
-			return CursorPosition <= 0;
-		}
-	}
 	/// <summary>
-	/// Renders an overlay on another view at a given point that allows selecting
-	/// from a range of 'autocomplete' options.
-	/// An implementation on a TextField.
+	/// Returns <see langword="true"/> if the current cursor position is
+	/// at the end of the <see cref="Text"/>. This includes when it is empty.
 	/// </summary>
-	public class TextFieldAutocomplete : PopupAutocomplete {
+	/// <returns></returns>
+	internal bool CursorIsAtEnd () => CursorPosition == Text.Length;
 
-		/// <inheritdoc/>
-		protected override void DeleteTextBackwards ()
-		{
-			((TextField)HostControl).DeleteCharLeft (false);
-		}
+	/// <summary>
+	/// Returns <see langword="true"/> if the current cursor position is
+	/// at the start of the <see cref="TextField"/>.
+	/// </summary>
+	/// <returns></returns>
+	internal bool CursorIsAtStart () => CursorPosition <= 0;
+}
 
-		/// <inheritdoc/>
-		protected override void InsertText (string accepted)
-		{
-			((TextField)HostControl).InsertText (accepted, false);
-		}
+/// <summary>
+/// Renders an overlay on another view at a given point that allows selecting
+/// from a range of 'autocomplete' options.
+/// An implementation on a TextField.
+/// </summary>
+public class TextFieldAutocomplete : PopupAutocomplete {
 
-		/// <inheritdoc/>
-		protected override void SetCursorPosition (int column)
-		{
-			((TextField)HostControl).CursorPosition = column;
-		}
-	}
+	/// <inheritdoc/>
+	protected override void DeleteTextBackwards () => ((TextField)HostControl).DeleteCharLeft (false);
+
+	/// <inheritdoc/>
+	protected override void InsertText (string accepted) => ((TextField)HostControl).InsertText (accepted, false);
+
+	/// <inheritdoc/>
+	protected override void SetCursorPosition (int column) => ((TextField)HostControl).CursorPosition = column;
 }

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 508 - 807
Terminal.Gui/Views/TextView.cs


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

@@ -301,7 +301,7 @@ namespace Terminal.Gui {
 		/// Overridden so no Frames get drawn (BUGBUG: v2 fix this hack)
 		/// </summary>
 		/// <returns></returns>
-		public override bool OnDrawFrames ()
+		public override bool OnDrawAdornments ()
 		{
 			return false;
 		}

+ 337 - 277
Terminal.Gui/Views/TimeField.cs

@@ -9,334 +9,394 @@ using System.Globalization;
 using System.Linq;
 using System.Text;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+/// <summary>
+///   Time editing <see cref="View"/>
+/// </summary>
+/// <remarks>
+///   The <see cref="TimeField"/> <see cref="View"/> provides time editing functionality with mouse support.
+/// </remarks>
+public class TimeField : TextField {
+	TimeSpan _time;
+	bool _isShort;
+
+	int _longFieldLen = 8;
+	int _shortFieldLen = 5;
+	string _sepChar;
+	string _longFormat;
+	string _shortFormat;
+
+	int _fieldLen => _isShort ? _shortFieldLen : _longFieldLen;
+	string _format => _isShort ? _shortFormat : _longFormat;
+
 	/// <summary>
-	///   Time editing <see cref="View"/>
+	///   TimeChanged event, raised when the Date has changed.
 	/// </summary>
 	/// <remarks>
-	///   The <see cref="TimeField"/> <see cref="View"/> provides time editing functionality with mouse support.
+	///   This event is raised when the <see cref="Time"/> changes.
 	/// </remarks>
-	public class TimeField : TextField {
-		TimeSpan time;
-		bool isShort;
-
-		int longFieldLen = 8;
-		int shortFieldLen = 5;
-		string sepChar;
-		string longFormat;
-		string shortFormat;
-
-		int fieldLen => isShort ? shortFieldLen : longFieldLen;
-		string format => isShort ? shortFormat : longFormat;
-
-		/// <summary>
-		///   TimeChanged event, raised when the Date has changed.
-		/// </summary>
-		/// <remarks>
-		///   This event is raised when the <see cref="Time"/> changes.
-		/// </remarks>
-		/// <remarks>
-		///   The passed <see cref="EventArgs"/> is a <see cref="DateTimeEventArgs{T}"/> containing the old value, new value, and format string.
-		/// </remarks>
-		public event EventHandler<DateTimeEventArgs<TimeSpan>> TimeChanged;
-
-		/// <summary>
-		///    Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Absolute"/> positioning.
-		/// </summary>
-		/// <param name="x">The x coordinate.</param>
-		/// <param name="y">The y coordinate.</param>
-		/// <param name="time">Initial time.</param>
-		/// <param name="isShort">If true, the seconds are hidden. Sets the <see cref="IsShortFormat"/> property.</param>
-		public TimeField (int x, int y, TimeSpan time, bool isShort = false) : base (x, y, isShort ? 7 : 10, "")
-		{
-			Initialize (time, isShort);
-		}
-
-		/// <summary>
-		///    Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
-		/// </summary>
-		/// <param name="time">Initial time</param>
-		public TimeField (TimeSpan time) : base (string.Empty)
-		{
-			Width = fieldLen + 2;
-			Initialize (time);
-		}
-
-		/// <summary>
-		///    Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
-		/// </summary>
-		public TimeField () : this (time: TimeSpan.MinValue) { }
-
-		void Initialize (TimeSpan time, bool isShort = false)
-		{
-			CultureInfo cultureInfo = CultureInfo.CurrentCulture;
-			sepChar = cultureInfo.DateTimeFormat.TimeSeparator;
-			longFormat = $" hh\\{sepChar}mm\\{sepChar}ss";
-			shortFormat = $" hh\\{sepChar}mm";
-			this.isShort = isShort;
-			Time = time;
-			CursorPosition = 1;
-			TextChanged += TextField_TextChanged;
-
-			// Things this view knows how to do
-			AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
-			AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; });
-			AddCommand (Command.LeftHome, () => MoveHome ());
-			AddCommand (Command.Left, () => MoveLeft ());
-			AddCommand (Command.RightEnd, () => MoveEnd ());
-			AddCommand (Command.Right, () => MoveRight ());
-
-			// Default keybindings for this view
-			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
-			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
-
-			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft);
-			KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
-
-			KeyBindings.Add (KeyCode.Home, Command.LeftHome);
-			KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome);
+	/// <remarks>
+	///   The passed <see cref="EventArgs"/> is a <see cref="DateTimeEventArgs{T}"/> containing the old value, new value, and format string.
+	/// </remarks>
+	public event EventHandler<DateTimeEventArgs<TimeSpan>> TimeChanged;
 
-			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
-			KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
+	/// <summary>
+	///    Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Absolute"/> positioning.
+	/// </summary>
+	/// <param name="x">The x coordinate.</param>
+	/// <param name="y">The y coordinate.</param>
+	/// <param name="time">Initial time.</param>
+	/// <param name="isShort">If true, the seconds are hidden. Sets the <see cref="IsShortFormat"/> property.</param>
+	public TimeField (int x, int y, TimeSpan time, bool isShort = false) : base (x, y, isShort ? 7 : 10, "")
+	{
+		SetInitialProperties (time, isShort);
+	}
 
-			KeyBindings.Add (KeyCode.End, Command.RightEnd);
-			KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd);
+	/// <summary>
+	///    Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
+	/// </summary>
+	/// <param name="time">Initial time</param>
+	public TimeField (TimeSpan time) : base (string.Empty)
+	{
+		Width = _fieldLen + 2;
+		SetInitialProperties (time);
+	}
 
-			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
-			KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
-		}
+	/// <summary>
+	///    Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
+	/// </summary>
+	public TimeField () : this (time: TimeSpan.MinValue) { }
+
+	void SetInitialProperties (TimeSpan time, bool isShort = false)
+	{
+		CultureInfo cultureInfo = CultureInfo.CurrentCulture;
+		_sepChar = cultureInfo.DateTimeFormat.TimeSeparator;
+		_longFormat = $" hh\\{_sepChar}mm\\{_sepChar}ss";
+		_shortFormat = $" hh\\{_sepChar}mm";
+		this._isShort = isShort;
+		Time = time;
+		CursorPosition = 1;
+		TextChanging += TextField_TextChanging;
+
+		// Things this view knows how to do
+		AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
+		AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; });
+		AddCommand (Command.LeftHome, () => MoveHome ());
+		AddCommand (Command.Left, () => MoveLeft ());
+		AddCommand (Command.RightEnd, () => MoveEnd ());
+		AddCommand (Command.Right, () => MoveRight ());
+
+		// Default keybindings for this view
+		KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
+		KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
+
+		KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
+		KeyBindings.Add (Key.D.WithAlt, Command.DeleteCharLeft);
+
+		KeyBindings.Add (Key.Home, Command.LeftHome);
+		KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome);
+
+		KeyBindings.Add (Key.CursorLeft, Command.Left);
+		KeyBindings.Add (Key.B.WithCtrl, Command.Left);
+
+		KeyBindings.Add (Key.End, Command.RightEnd);
+		KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
+
+		KeyBindings.Add (Key.CursorRight, Command.Right);
+		KeyBindings.Add (Key.F.WithCtrl, Command.Right);
+	}
 
-		void TextField_TextChanged (object sender, TextChangedEventArgs e)
-		{
-			try {
-				if (!TimeSpan.TryParseExact (Text.Trim (), format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result))
-					Text = e.OldValue;
-			} catch (Exception) {
-				Text = e.OldValue;
+	void TextField_TextChanging (object sender, TextChangingEventArgs e)
+	{
+		try {
+			int spaces = 0;
+			for (int i = 0; i < e.NewText.Length; i++) {
+				if (e.NewText [i] == ' ') {
+					spaces++;
+				} else {
+					break;
+				}
 			}
-		}
-
-		/// <summary>
-		///   Gets or sets the time of the <see cref="TimeField"/>.
-		/// </summary>
-		/// <remarks>
-		/// </remarks>
-		public TimeSpan Time {
-			get {
-				return time;
+			spaces += _fieldLen;
+			string trimedText = e.NewText [..spaces];
+			spaces -= _fieldLen;
+			trimedText = trimedText.Replace (new string (' ', spaces), " ");
+			if (trimedText != e.NewText) {
+				e.NewText = trimedText;
 			}
-			set {
-				if (ReadOnly)
-					return;
-
-				var oldTime = time;
-				time = value;
-				this.Text = " " + value.ToString (format.Trim ());
-				var args = new DateTimeEventArgs<TimeSpan> (oldTime, value, format);
-				if (oldTime != value) {
-					OnTimeChanged (args);
-				}
+			if (!TimeSpan.TryParseExact (e.NewText.Trim (), _format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result)) {
+				e.Cancel = true;
 			}
+			AdjCursorPosition (CursorPosition, true);
+		} catch (Exception) {
+			e.Cancel = true;
 		}
+	}
 
-		/// <summary>
-		/// Get or sets whether <see cref="TimeField"/> uses the short or long time format.
-		/// </summary>
-		public bool IsShortFormat {
-			get => isShort;
-			set {
-				isShort = value;
-				if (isShort)
-					Width = 7;
-				else
-					Width = 10;
-				var ro = ReadOnly;
-				if (ro)
-					ReadOnly = false;
-				SetText (Text);
-				ReadOnly = ro;
-				SetNeedsDisplay ();
-			}
+	/// <summary>
+	///   Gets or sets the time of the <see cref="TimeField"/>.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	public TimeSpan Time {
+		get {
+			return _time;
 		}
+		set {
+			if (ReadOnly)
+				return;
 
-		/// <inheritdoc/>
-		public override int CursorPosition {
-			get => base.CursorPosition;
-			set {
-				base.CursorPosition = Math.Max (Math.Min (value, fieldLen), 1);
+			var oldTime = _time;
+			_time = value;
+			this.Text = " " + value.ToString (_format.Trim ());
+			var args = new DateTimeEventArgs<TimeSpan> (oldTime, value, _format);
+			if (oldTime != value) {
+				OnTimeChanged (args);
 			}
 		}
+	}
 
-		bool SetText (Rune key)
-		{
-			var text = Text.EnumerateRunes ().ToList ();
-			var newText = text.GetRange (0, CursorPosition);
-			newText.Add (key);
-			if (CursorPosition < fieldLen)
-				newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
-			return SetText (StringExtensions.ToString (newText));
+	/// <summary>
+	/// Get or sets whether <see cref="TimeField"/> uses the short or long time format.
+	/// </summary>
+	public bool IsShortFormat {
+		get => _isShort;
+		set {
+			_isShort = value;
+			if (_isShort)
+				Width = 7;
+			else
+				Width = 10;
+			var ro = ReadOnly;
+			if (ro)
+				ReadOnly = false;
+			SetText (Text);
+			ReadOnly = ro;
+			SetNeedsDisplay ();
 		}
+	}
 
-		bool SetText (string text)
-		{
-			if (string.IsNullOrEmpty (text)) {
-				return false;
-			}
+	/// <inheritdoc/>
+	public override int CursorPosition {
+		get => base.CursorPosition;
+		set {
+			base.CursorPosition = Math.Max (Math.Min (value, _fieldLen), 1);
+		}
+	}
 
-			string [] vals = text.Split (sepChar);
-			bool isValidTime = true;
-			int hour = Int32.Parse (vals [0]);
-			int minute = Int32.Parse (vals [1]);
-			int second = isShort ? 0 : vals.Length > 2 ? Int32.Parse (vals [2].ToString ()) : 0;
-			if (hour < 0) {
-				isValidTime = false;
-				hour = 0;
-				vals [0] = "0";
-			} else if (hour > 23) {
-				isValidTime = false;
-				hour = 23;
-				vals [0] = "23";
-			}
-			if (minute < 0) {
-				isValidTime = false;
-				minute = 0;
-				vals [1] = "0";
-			} else if (minute > 59) {
-				isValidTime = false;
-				minute = 59;
-				vals [1] = "59";
-			}
-			if (second < 0) {
-				isValidTime = false;
-				second = 0;
-				vals [2] = "0";
-			} else if (second > 59) {
-				isValidTime = false;
-				second = 59;
-				vals [2] = "59";
-			}
-			string t = isShort ? $" {hour,2:00}{sepChar}{minute,2:00}" : $" {hour,2:00}{sepChar}{minute,2:00}{sepChar}{second,2:00}";
+	bool SetText (Rune key)
+	{
+		var text = Text.EnumerateRunes ().ToList ();
+		var newText = text.GetRange (0, CursorPosition);
+		newText.Add (key);
+		if (CursorPosition < _fieldLen)
+			newText = [.. newText, .. text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))];
+		return SetText (StringExtensions.ToString (newText));
+	}
 
-			if (!TimeSpan.TryParseExact (t.Trim (), format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result) ||
-				!isValidTime)
-				return false;
-			Time = result;
-			return true;
+	bool SetText (string text)
+	{
+		if (string.IsNullOrEmpty (text)) {
+			return false;
 		}
 
-		void IncCursorPosition ()
-		{
-			if (CursorPosition == fieldLen)
-				return;
-			if (Text [++CursorPosition] == sepChar.ToCharArray () [0])
-				CursorPosition++;
+		text = NormalizeFormat (text);
+		string [] vals = text.Split (_sepChar);
+		bool isValidTime = true;
+		int hour = Int32.Parse (vals [0]);
+		int minute = Int32.Parse (vals [1]);
+		int second = _isShort ? 0 : vals.Length > 2 ? Int32.Parse (vals [2]) : 0;
+		if (hour < 0) {
+			isValidTime = false;
+			hour = 0;
+			vals [0] = "0";
+		} else if (hour > 23) {
+			isValidTime = false;
+			hour = 23;
+			vals [0] = "23";
 		}
+		if (minute < 0) {
+			isValidTime = false;
+			minute = 0;
+			vals [1] = "0";
+		} else if (minute > 59) {
+			isValidTime = false;
+			minute = 59;
+			vals [1] = "59";
+		}
+		if (second < 0) {
+			isValidTime = false;
+			second = 0;
+			vals [2] = "0";
+		} else if (second > 59) {
+			isValidTime = false;
+			second = 59;
+			vals [2] = "59";
+		}
+		string t = _isShort ? $" {hour,2:00}{_sepChar}{minute,2:00}" : $" {hour,2:00}{_sepChar}{minute,2:00}{_sepChar}{second,2:00}";
 
-		void DecCursorPosition ()
-		{
-			if (CursorPosition == 1)
-				return;
-			if (Text [--CursorPosition] == sepChar.ToCharArray () [0])
-				CursorPosition--;
+		if (!TimeSpan.TryParseExact (t.Trim (), _format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result) ||
+			!isValidTime) {
+			return false;
 		}
+		Time = result;
+		return true;
+	}
 
-		void AdjCursorPosition ()
-		{
-			if (Text [CursorPosition] == sepChar.ToCharArray () [0])
-				CursorPosition++;
+	string NormalizeFormat (string text, string fmt = null, string sepChar = null)
+	{
+		if (string.IsNullOrEmpty (fmt)) {
+			fmt = _format;
+		}
+		fmt = fmt.Replace ("\\", "");
+		if (string.IsNullOrEmpty (sepChar)) {
+			sepChar = _sepChar;
+		}
+		if (fmt.Length != text.Length) {
+			return text;
 		}
 
-		///<inheritdoc/>
-		public override bool OnProcessKeyDown (Key a)
-		{
-			// Ignore non-numeric characters.
-			if (a.KeyCode is >= (KeyCode)(int)KeyCode.D0 and <= (KeyCode)(int)KeyCode.D9) {
-				if (!ReadOnly) {
-					if (SetText ((Rune)a)) {
-						IncCursorPosition ();
-					}
-				}
-				return true;
+		var fmtText = text.ToCharArray ();
+		for (int i = 0; i < text.Length; i++) {
+			var c = fmt [i];
+			if (c.ToString () == sepChar && text [i].ToString () != sepChar) {
+				fmtText [i] = c;
 			}
+		}
 
-			if (a.IsKeyCodeAtoZ) {
-				return true;
-			}
-			
-			return false;
+		return new string (fmtText);
+	}
+
+	void IncCursorPosition ()
+	{
+		if (CursorPosition >= _fieldLen) {
+			CursorPosition = _fieldLen;
+			return;
 		}
+		CursorPosition++;
+		AdjCursorPosition (CursorPosition);
+	}
 
-		bool MoveRight ()
-		{
-			IncCursorPosition ();
-			return true;
+	void DecCursorPosition ()
+	{
+		if (CursorPosition <= 1) {
+			CursorPosition = 1;
+			return;
 		}
+		CursorPosition--;
+		AdjCursorPosition (CursorPosition, false);
+	}
 
-		new bool MoveEnd ()
-		{
-			CursorPosition = fieldLen;
-			return true;
+	void AdjCursorPosition (int point, bool increment = true)
+	{
+		var newPoint = point;
+		if (point > _fieldLen) {
+			newPoint = _fieldLen;
+		}
+		if (point < 1) {
+			newPoint = 1;
+		}
+		if (newPoint != point) {
+			CursorPosition = newPoint;
 		}
 
-		bool MoveLeft ()
-		{
-			DecCursorPosition ();
-			return true;
+		while (Text [CursorPosition] == _sepChar [0]) {
+			if (increment) {
+				CursorPosition++;
+			} else {
+				CursorPosition--;
+			}
 		}
+	}
 
-		bool MoveHome ()
-		{
-			// Home, C-A
-			CursorPosition = 1;
+	///<inheritdoc/>
+	public override bool OnProcessKeyDown (Key a)
+	{
+		// Ignore non-numeric characters.
+		if (a.KeyCode is >= (KeyCode)(int)KeyCode.D0 and <= (KeyCode)(int)KeyCode.D9) {
+			if (!ReadOnly) {
+				if (SetText ((Rune)a)) {
+					IncCursorPosition ();
+				}
+			}
 			return true;
 		}
 
-		/// <inheritdoc/>
-		public override void DeleteCharLeft (bool useOldCursorPos = true)
-		{
-			if (ReadOnly)
-				return;
+		return false;
+	}
 
-			SetText ((Rune)'0');
-			DecCursorPosition ();
+	bool MoveRight ()
+	{
+		ClearAllSelection ();
+		IncCursorPosition ();
+		return true;
+	}
+
+	new bool MoveEnd ()
+	{
+		ClearAllSelection ();
+		CursorPosition = _fieldLen;
+		return true;
+	}
+
+	bool MoveLeft ()
+	{
+		ClearAllSelection ();
+		DecCursorPosition ();
+		return true;
+	}
+
+	bool MoveHome ()
+	{
+		// Home, C-A
+		ClearAllSelection ();
+		CursorPosition = 1;
+		return true;
+	}
+
+	/// <inheritdoc/>
+	public override void DeleteCharLeft (bool useOldCursorPos = true)
+	{
+		if (ReadOnly) {
 			return;
 		}
 
-		/// <inheritdoc/>
-		public override void DeleteCharRight ()
-		{
-			if (ReadOnly)
-				return;
+		ClearAllSelection ();
+		SetText ((Rune)'0');
+		DecCursorPosition ();
+		return;
+	}
 
-			SetText ((Rune)'0');
+	/// <inheritdoc/>
+	public override void DeleteCharRight ()
+	{
+		if (ReadOnly) {
 			return;
 		}
 
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent ev)
-		{
-			if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
-				return false;
-			if (!HasFocus)
-				SetFocus ();
-
-			var point = ev.X;
-			if (point > fieldLen)
-				point = fieldLen;
-			if (point < 1)
-				point = 1;
-			CursorPosition = point;
-			AdjCursorPosition ();
-			return true;
-		}
+		ClearAllSelection ();
+		SetText ((Rune)'0');
+		return;
+	}
+
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent ev)
+	{
+		var result = base.MouseEvent (ev);
 
-		/// <summary>
-		/// Event firing method that invokes the <see cref="TimeChanged"/> event.
-		/// </summary>
-		/// <param name="args">The event arguments</param>
-		public virtual void OnTimeChanged (DateTimeEventArgs<TimeSpan> args)
-		{
-			TimeChanged?.Invoke (this, args);
+		if (result && SelectedLength == 0 && ev.Flags.HasFlag (MouseFlags.Button1Pressed)) {
+			int point = ev.X;
+			AdjCursorPosition (point, true);
 		}
+		return result;
+	}
+
+	/// <summary>
+	/// Event firing method that invokes the <see cref="TimeChanged"/> event.
+	/// </summary>
+	/// <param name="args">The event arguments</param>
+	public virtual void OnTimeChanged (DateTimeEventArgs<TimeSpan> args)
+	{
+		TimeChanged?.Invoke (this, args);
 	}
-}
+}

+ 145 - 132
Terminal.Gui/Views/Toplevel.cs

@@ -1,88 +1,171 @@
-using System;
+using System;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.Linq;
 
-namespace Terminal.Gui; 
+namespace Terminal.Gui;
 
 /// <summary>
-/// Toplevel views can be modally executed. They are used for both an application's main view (filling the entire screeN and
-/// for pop-up views such as <see cref="Dialog"/>, <see cref="MessageBox"/>, and <see cref="Wizard"/>.
+/// Toplevel views are used for both an application's main view (filling the entire screen and
+/// for modal (pop-up) views such as <see cref="Dialog"/>, <see cref="MessageBox"/>, and
+/// <see cref="Wizard"/>).
 /// </summary>
 /// <remarks>
-///   <para>
-///     Toplevels can be modally executing views, started by calling <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>. 
-///     They return control to the caller when <see cref="Application.RequestStop(Toplevel)"/> has 
-///     been called (which sets the <see cref="Toplevel.Running"/> property to <c>false</c>). 
-///   </para>
-///   <para>
-///     A Toplevel is created when an application initializes Terminal.Gui by calling <see cref="Application.Init"/>.
-///     The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional Toplevels can be created 
-///     and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and 
-///     call <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>.
-///   </para>
+///         <para>
+///         Toplevels can run as modal (popup) views, started by calling
+///         <see cref="Application.Run(Toplevel, Func{Exception,bool})"/>.
+///         They return control to the caller when <see cref="Application.RequestStop(Toplevel)"/> has
+///         been called (which sets the <see cref="Toplevel.Running"/> property to <c>false</c>).
+///         </para>
+///         <para>
+///         A Toplevel is created when an application initializes Terminal.Gui by calling
+///         <see cref="Application.Init"/>.
+///         The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional
+///         Toplevels can be created
+///         and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and
+///         call <see cref="Application.Run(Toplevel, Func{Exception,bool})"/>.
+///         </para>
 /// </remarks>
 public partial class Toplevel : View {
+
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Toplevel"/> class with the specified
+	/// <see cref="LayoutStyle.Absolute"/> layout.
+	/// </summary>
+	/// <param name="frame">
+	/// A Superview-relative rectangle specifying the location and size for the new
+	/// Toplevel
+	/// </param>
+	public Toplevel (Rect frame) : base (frame) => SetInitialProperties ();
+
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Toplevel"/> class with
+	/// <see cref="LayoutStyle.Computed"/> layout, defaulting to full screen. The <see cref="View.Width"/> and
+	/// <see cref="View.Height"/> properties
+	/// will be set to the dimensions of the terminal using <see cref="Dim.Fill"/>.
+	/// </summary>
+	public Toplevel ()
+	{
+		SetInitialProperties ();
+		Width = Dim.Fill ();
+		Height = Dim.Fill ();
+	}
+
 	/// <summary>
-	/// Gets or sets whether the main loop for this <see cref="Toplevel"/> is running or not. 
+	/// Gets or sets whether the main loop for this <see cref="Toplevel"/> is running or not.
 	/// </summary>
 	/// <remarks>
-	///    Setting this property directly is discouraged. Use <see cref="Application.RequestStop"/> instead. 
+	/// Setting this property directly is discouraged. Use <see cref="Application.RequestStop"/>
+	/// instead.
 	/// </remarks>
 	public bool Running { get; set; }
 
+	/// <summary>
+	/// Gets or sets a value indicating whether this <see cref="Toplevel"/> can focus.
+	/// </summary>
+	/// <value><c>true</c> if can focus; otherwise, <c>false</c>.</value>
+	public override bool CanFocus => SuperView == null ? true : base.CanFocus;
+
+	/// <summary>
+	/// Determines whether the <see cref="Toplevel"/> is modal or not.
+	/// If set to <c>false</c> (the default):
+	/// <list type="bullet">
+	///         <item>
+	///                 <description><see cref="View.OnKeyDown"/> events will propagate keys upwards.</description>
+	///         </item>
+	///         <item>
+	///                 <description>The Toplevel will act as an embedded view (not a modal/pop-up).</description>
+	///         </item>
+	/// </list>
+	/// If set to <c>true</c>:
+	/// <list type="bullet">
+	///         <item>
+	///                 <description><see cref="View.OnKeyDown"/> events will NOT propagate keys upwards.</description>
+	///         </item>
+	///         <item>
+	///                 <description>
+	///                 The Toplevel will and look like a modal (pop-up) (e.g. see
+	///                 <see cref="Dialog"/>.
+	///                 </description>
+	///         </item>
+	/// </list>
+	/// </summary>
+	public bool Modal { get; set; }
+
+	/// <summary>
+	/// Gets or sets the menu for this Toplevel.
+	/// </summary>
+	public virtual MenuBar MenuBar { get; set; }
+
+	/// <summary>
+	/// Gets or sets the status bar for this Toplevel.
+	/// </summary>
+	public virtual StatusBar StatusBar { get; set; }
+
+	/// <summary>
+	/// <see langword="true"/> if was already loaded by the <see cref="Application.Begin(Toplevel)"/>
+	/// <see langword="false"/>, otherwise.
+	/// </summary>
+	public bool IsLoaded { get; private set; }
+
 	/// <summary>
 	/// Invoked when the <see cref="Toplevel"/> <see cref="RunState"/> has begun to be loaded.
-	/// A Loaded event handler is a good place to finalize initialization before calling 
+	/// A Loaded event handler is a good place to finalize initialization before calling
 	/// <see cref="Application.RunLoop(RunState)"/>.
 	/// </summary>
 	public event EventHandler Loaded;
 
 	/// <summary>
 	/// Invoked when the <see cref="Toplevel"/> main loop has started it's first iteration.
-	/// Subscribe to this event to perform tasks when the <see cref="Toplevel"/> has been laid out and focus has been set.
-	/// changes. 
-	/// <para>A Ready event handler is a good place to finalize initialization after calling 
-	/// <see cref="Application.Run(Func{Exception, bool})"/> on this <see cref="Toplevel"/>.</para>
+	/// Subscribe to this event to perform tasks when the <see cref="Toplevel"/> has been laid out and
+	/// focus has been set.
+	/// changes.
+	/// <para>
+	/// A Ready event handler is a good place to finalize initialization after calling
+	/// <see cref="Application.Run(Func{Exception, bool})"/> on this <see cref="Toplevel"/>.
+	/// </para>
 	/// </summary>
 	public event EventHandler Ready;
 
 	/// <summary>
 	/// Invoked when the Toplevel <see cref="RunState"/> has been unloaded.
-	/// A Unloaded event handler is a good place to dispose objects after calling <see cref="Application.End(RunState)"/>.
+	/// A Unloaded event handler is a good place to dispose objects after calling
+	/// <see cref="Application.End(RunState)"/>.
 	/// </summary>
 	public event EventHandler Unloaded;
 
 	/// <summary>
-	/// Invoked when the Toplevel <see cref="RunState"/> becomes the <see cref="Application.Current"/> Toplevel.
+	/// Invoked when the Toplevel <see cref="RunState"/> becomes the <see cref="Application.Current"/>
+	/// Toplevel.
 	/// </summary>
 	public event EventHandler<ToplevelEventArgs> Activate;
 
 	/// <summary>
-	/// Invoked when the Toplevel<see cref="RunState"/> ceases to be the <see cref="Application.Current"/> Toplevel.
+	/// Invoked when the Toplevel<see cref="RunState"/> ceases to be the <see cref="Application.Current"/>
+	/// Toplevel.
 	/// </summary>
 	public event EventHandler<ToplevelEventArgs> Deactivate;
 
 	/// <summary>
-	/// Invoked when a child of the Toplevel <see cref="RunState"/> is closed by  
+	/// Invoked when a child of the Toplevel <see cref="RunState"/> is closed by
 	/// <see cref="Application.End(RunState)"/>.
 	/// </summary>
 	public event EventHandler<ToplevelEventArgs> ChildClosed;
 
 	/// <summary>
-	/// Invoked when the last child of the Toplevel <see cref="RunState"/> is closed from 
+	/// Invoked when the last child of the Toplevel <see cref="RunState"/> is closed from
 	/// by <see cref="Application.End(RunState)"/>.
 	/// </summary>
 	public event EventHandler AllChildClosed;
 
 	/// <summary>
-	/// Invoked when the Toplevel's <see cref="RunState"/> is being closed by  
+	/// Invoked when the Toplevel's <see cref="RunState"/> is being closed by
 	/// <see cref="Application.RequestStop(Toplevel)"/>.
 	/// </summary>
 	public event EventHandler<ToplevelClosingEventArgs> Closing;
 
 	/// <summary>
-	/// Invoked when the Toplevel's <see cref="RunState"/> is closed by <see cref="Application.End(RunState)"/>.
+	/// Invoked when the Toplevel's <see cref="RunState"/> is closed by
+	/// <see cref="Application.End(RunState)"/>.
 	/// </summary>
 	public event EventHandler<ToplevelEventArgs> Closed;
 
@@ -131,7 +214,8 @@ public partial class Toplevel : View {
 	internal virtual void OnActivate (Toplevel deactivated) => Activate?.Invoke (this, new ToplevelEventArgs (deactivated));
 
 	/// <summary>
-	/// Called from <see cref="Application.Begin(Toplevel)"/> before the <see cref="Toplevel"/> redraws for the first time. 
+	/// Called from <see cref="Application.Begin(Toplevel)"/> before the <see cref="Toplevel"/> redraws for
+	/// the first time.
 	/// </summary>
 	public virtual void OnLoaded ()
 	{
@@ -143,7 +227,7 @@ public partial class Toplevel : View {
 	}
 
 	/// <summary>
-	/// Called from <see cref="Application.RunLoop"/> after the <see cref="Toplevel"/> has entered the 
+	/// Called from <see cref="Application.RunLoop"/> after the <see cref="Toplevel"/> has entered the
 	/// first iteration of the loop.
 	/// </summary>
 	internal virtual void OnReady ()
@@ -165,26 +249,9 @@ public partial class Toplevel : View {
 		Unloaded?.Invoke (this, EventArgs.Empty);
 	}
 
-	/// <summary>
-	/// Initializes a new instance of the <see cref="Toplevel"/> class with the specified <see cref="LayoutStyle.Absolute"/> layout.
-	/// </summary>
-	/// <param name="frame">A Superview-relative rectangle specifying the location and size for the new Toplevel</param>
-	public Toplevel (Rect frame) : base (frame) => SetInitialProperties ();
-
-	/// <summary>
-	/// Initializes a new instance of the <see cref="Toplevel"/> class with <see cref="LayoutStyle.Computed"/> layout, 
-	/// defaulting to full screen.
-	/// </summary>
-	public Toplevel () : base ()
-	{
-		SetInitialProperties ();
-		Width = Dim.Fill ();
-		Height = Dim.Fill ();
-	}
-
 	void SetInitialProperties ()
 	{
-		ColorScheme = Colors.TopLevel;
+		ColorScheme = Colors.ColorSchemes ["TopLevel"];
 
 		Application.GrabbingMouse += Application_GrabbingMouse;
 		Application.UnGrabbingMouse += Application_UnGrabbingMouse;
@@ -251,20 +318,6 @@ public partial class Toplevel : View {
 #endif
 	}
 
-	void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
-	{
-		if (Application.MouseGrabView == this && _dragPosition.HasValue) {
-			e.Cancel = true;
-		}
-	}
-
-	void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
-	{
-		if (Application.MouseGrabView == this && _dragPosition.HasValue) {
-			e.Cancel = true;
-		}
-	}
-
 	/// <summary>
 	/// Invoked when the <see cref="Application.AlternateForwardKey"/> is changed.
 	/// </summary>
@@ -310,60 +363,6 @@ public partial class Toplevel : View {
 		QuitKeyChanged?.Invoke (this, e);
 	}
 
-	/// <summary>
-	/// Convenience factory method that creates a new Toplevel with the current terminal dimensions.
-	/// </summary>
-	/// <returns>The created Toplevel.</returns>
-	public static Toplevel Create () => new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
-
-	/// <summary>
-	/// Gets or sets a value indicating whether this <see cref="Toplevel"/> can focus.
-	/// </summary>
-	/// <value><c>true</c> if can focus; otherwise, <c>false</c>.</value>
-	public override bool CanFocus => SuperView == null ? true : base.CanFocus;
-
-	/// <summary>
-	/// Determines whether the <see cref="Toplevel"/> is modal or not. 
-	/// If set to <c>false</c> (the default):
-	/// 
-	/// <list type="bullet">
-	///   <item>
-	///		<description><see cref="View.OnKeyDown"/> events will propagate keys upwards.</description>
-	///   </item>
-	///   <item>
-	///		<description>The Toplevel will act as an embedded view (not a modal/pop-up).</description>
-	///   </item>
-	/// </list>
-	///
-	/// If set to <c>true</c>:
-	/// 
-	/// <list type="bullet">
-	///   <item>
-	///		<description><see cref="View.OnKeyDown"/> events will NOT propagate keys upwards.</description>
-	///	  </item>
-	///   <item>
-	///		<description>The Toplevel will and look like a modal (pop-up) (e.g. see <see cref="Dialog"/>.</description>
-	///   </item>
-	/// </list>
-	/// </summary>
-	public bool Modal { get; set; }
-
-	/// <summary>
-	/// Gets or sets the menu for this Toplevel.
-	/// </summary>
-	public virtual MenuBar MenuBar { get; set; }
-
-	/// <summary>
-	/// Gets or sets the status bar for this Toplevel.
-	/// </summary>
-	public virtual StatusBar StatusBar { get; set; }
-
-	/// <summary>
-	/// <see langword="true"/> if was already loaded by the <see cref="Application.Begin(Toplevel)"/>
-	/// <see langword="false"/>, otherwise.
-	/// </summary>
-	public bool IsLoaded { get; private set; }
-
 	void MovePreviousViewOrTop ()
 	{
 		if (Application.OverlappedTop == null) {
@@ -561,7 +560,7 @@ public partial class Toplevel : View {
 			superView = top.SuperView;
 		}
 		if (superView.Margin != null && superView == top.SuperView) {
-			maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
+			maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right;
 		}
 		if (top.Frame.Width <= maxWidth) {
 			nx = Math.Max (targetX, 0);
@@ -608,7 +607,7 @@ public partial class Toplevel : View {
 			maxWidth = statusVisible ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height;
 		}
 		if (superView.Margin != null && superView == top.SuperView) {
-			maxWidth -= superView.GetFramesThickness ().Top + superView.GetFramesThickness ().Bottom;
+			maxWidth -= superView.GetAdornmentsThickness ().Top + superView.GetAdornmentsThickness ().Bottom;
 		}
 		ny = Math.Min (ny, maxWidth);
 		if (top.Frame.Height <= maxWidth) {
@@ -645,11 +644,12 @@ public partial class Toplevel : View {
 		bool layoutSubviews = false;
 		int maxWidth = 0;
 		if (superView.Margin != null && superView == top.SuperView) {
-			maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
+			maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right;
 		}
-		if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal
-			|| top?.SuperView == null && top.IsOverlapped)
-		&& (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
+		if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal || top?.SuperView == null && top.IsOverlapped)
+		    // BUGBUG: Prevously PositionToplevel required LayotuStyle.Computed
+		    &&
+		    (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) /*&& top.LayoutStyle == LayoutStyle.Computed*/) {
 
 			if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) {
 				top.X = nx;
@@ -662,8 +662,7 @@ public partial class Toplevel : View {
 		}
 
 		// TODO: v2 - This is a hack to get the StatusBar to be positioned correctly.
-		if (sb != null && !top.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0)
-		&& top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) {
+		if (sb != null && !top.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) && top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) {
 
 			top.Height = Dim.Fill (sb.Visible ? 1 : 0);
 			layoutSubviews = true;
@@ -734,6 +733,20 @@ public partial class Toplevel : View {
 	internal static Point? _dragPosition;
 	Point _startGrabPoint;
 
+	void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
+	{
+		if (Application.MouseGrabView == this && _dragPosition.HasValue) {
+			e.Cancel = true;
+		}
+	}
+
+	void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
+	{
+		if (Application.MouseGrabView == this && _dragPosition.HasValue) {
+			e.Cancel = true;
+		}
+	}
+
 	///<inheritdoc/>
 	public override bool MouseEvent (MouseEvent mouseEvent)
 	{
@@ -910,9 +923,8 @@ public class ToplevelEqualityComparer : IEqualityComparer<Toplevel> {
 			return false;
 		} else if (x.Id == y.Id) {
 			return true;
-		} else {
-			return false;
 		}
+		return false;
 	}
 
 	/// <summary>Returns a hash code for the specified object.</summary>
@@ -949,12 +961,13 @@ public sealed class ToplevelComparer : IComparer<Toplevel> {
 	{
 		if (ReferenceEquals (x, y)) {
 			return 0;
-		} else if (x == null) {
+		}
+		if (x == null) {
 			return -1;
-		} else if (y == null) {
+		}
+		if (y == null) {
 			return 1;
-		} else {
-			return string.Compare (x.Id, y.Id);
 		}
+		return string.Compare (x.Id, y.Id);
 	}
 }

+ 137 - 183
Terminal.Gui/Views/ToplevelOverlapped.cs

@@ -1,226 +1,180 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.Linq;
 
-namespace Terminal.Gui {
-	public partial class Toplevel {
-		/// <summary>
-		/// Gets or sets if this Toplevel is a container for overlapped children.
-		/// </summary>
-		public bool IsOverlappedContainer { get; set; }
-
-		/// <summary>
-		/// Gets or sets if this Toplevel is in overlapped mode within a Toplevel container.
-		/// </summary>
-		public bool IsOverlapped {
-			get {
-				return Application.OverlappedTop != null && Application.OverlappedTop != this && !Modal;
-			}
-		}
-
-	}
+namespace Terminal.Gui;
 
-	public static partial class Application {
-
-		/// <summary>
-		/// Gets the list of the Overlapped children which are not modal <see cref="Toplevel"/> from the <see cref="OverlappedTop"/>.
-		/// </summary>
-		public static List<Toplevel> OverlappedChildren {
-			get {
-				if (OverlappedTop != null) {
-					List<Toplevel> _overlappedChildren = new List<Toplevel> ();
-					foreach (var top in _topLevels) {
-						if (top != OverlappedTop && !top.Modal) {
-							_overlappedChildren.Add (top);
-						}
-					}
-					return _overlappedChildren;
-				}
-				return null;
-			}
-		}
-
-		/// <summary>
-		/// The <see cref="Toplevel"/> object used for the application on startup which <see cref="Toplevel.IsOverlappedContainer"/> is true.
-		/// </summary>
-		public static Toplevel OverlappedTop {
-			get {
-				if (Top.IsOverlappedContainer) {
-					return Top;
-				}
-				return null;
-			}
-		}
-
-
-		static View FindDeepestOverlappedView (View start, int x, int y, out int resx, out int resy)
-		{
-			if (start.GetType ().BaseType != typeof (Toplevel)
-				&& !((Toplevel)start).IsOverlappedContainer) {
-				resx = 0;
-				resy = 0;
-				return null;
-			}
+public partial class Toplevel {
+	/// <summary>
+	/// Gets or sets if this Toplevel is a container for overlapped children.
+	/// </summary>
+	public bool IsOverlappedContainer { get; set; }
 
-			var startFrame = start.Frame;
-
-			if (!startFrame.Contains (x, y)) {
-				resx = 0;
-				resy = 0;
-				return null;
-			}
+	/// <summary>
+	/// Gets or sets if this Toplevel is in overlapped mode within a Toplevel container.
+	/// </summary>
+	public bool IsOverlapped => Application.OverlappedTop != null && Application.OverlappedTop != this && !Modal;
+}
 
-			int count = _topLevels.Count;
-			for (int i = count - 1; i >= 0; i--) {
+public static partial class Application {
+	/// <summary>
+	/// Gets the list of the Overlapped children which are not modal <see cref="Toplevel"/> from the
+	/// <see cref="OverlappedTop"/>.
+	/// </summary>
+	public static List<Toplevel> OverlappedChildren {
+		get {
+			if (OverlappedTop != null) {
+				var _overlappedChildren = new List<Toplevel> ();
 				foreach (var top in _topLevels) {
-					var rx = x - startFrame.X;
-					var ry = y - startFrame.Y;
-					if (top.Visible && top.Frame.Contains (rx, ry)) {
-						var deep = View.FindDeepestView (top, rx, ry, out resx, out resy);
-						if (deep == null)
-							return FindDeepestOverlappedView (top, rx, ry, out resx, out resy);
-						if (deep != OverlappedTop)
-							return deep;
+					if (top != OverlappedTop && !top.Modal) {
+						_overlappedChildren.Add (top);
 					}
 				}
+				return _overlappedChildren;
 			}
-			resx = x - startFrame.X;
-			resy = y - startFrame.Y;
-			return start;
+			return null;
 		}
+	}
 
-		static bool OverlappedChildNeedsDisplay ()
-		{
-			if (OverlappedTop == null) {
-				return false;
+	/// <summary>
+	/// The <see cref="Toplevel"/> object used for the application on startup which
+	/// <see cref="Toplevel.IsOverlappedContainer"/> is true.
+	/// </summary>
+	public static Toplevel OverlappedTop {
+		get {
+			if (Top is { IsOverlappedContainer: true }) {
+				return Top;
 			}
+			return null;
+		}
+	}
 
-			foreach (var top in _topLevels) {
-				if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) {
-					OverlappedTop.SetSubViewNeedsDisplay ();
-					return true;
-				}
-			}
+	static bool OverlappedChildNeedsDisplay ()
+	{
+		if (OverlappedTop == null) {
 			return false;
 		}
 
-
-		static bool SetCurrentOverlappedAsTop ()
-		{
-			if (OverlappedTop == null && Current != Top && Current?.SuperView == null && Current?.Modal == false) {
-				if (Current.Frame != new Rect (0, 0, Driver.Cols, Driver.Rows)) {
-					Current.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows);
-				}
-				Top = Current;
+		foreach (var top in _topLevels) {
+			if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) {
+				OverlappedTop.SetSubViewNeedsDisplay ();
 				return true;
 			}
-			return false;
 		}
+		return false;
+	}
 
-		/// <summary>
-		/// Move to the next Overlapped child from the <see cref="OverlappedTop"/>.
-		/// </summary>
-		public static void OverlappedMoveNext ()
-		{
-			if (OverlappedTop != null && !Current.Modal) {
-				lock (_topLevels) {
-					_topLevels.MoveNext ();
-					var isOverlapped = false;
-					while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) {
-						if (!isOverlapped && _topLevels.Peek () == OverlappedTop) {
-							isOverlapped = true;
-						} else if (isOverlapped && _topLevels.Peek () == OverlappedTop) {
-							MoveCurrent (Top);
-							break;
-						}
-						_topLevels.MoveNext ();
+
+	static bool SetCurrentOverlappedAsTop ()
+	{
+		if (OverlappedTop == null && Current != Top && Current?.SuperView == null && Current?.Modal == false) {
+			Top = Current;
+			return true;
+		}
+		return false;
+	}
+
+	/// <summary>
+	/// Move to the next Overlapped child from the <see cref="OverlappedTop"/>.
+	/// </summary>
+	public static void OverlappedMoveNext ()
+	{
+		if (OverlappedTop != null && !Current.Modal) {
+			lock (_topLevels) {
+				_topLevels.MoveNext ();
+				var isOverlapped = false;
+				while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) {
+					if (!isOverlapped && _topLevels.Peek () == OverlappedTop) {
+						isOverlapped = true;
+					} else if (isOverlapped && _topLevels.Peek () == OverlappedTop) {
+						MoveCurrent (Top);
+						break;
 					}
-					Current = _topLevels.Peek ();
+					_topLevels.MoveNext ();
 				}
+				Current = _topLevels.Peek ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Move to the previous Overlapped child from the <see cref="OverlappedTop"/>.
-		/// </summary>
-		public static void OverlappedMovePrevious ()
-		{
-			if (OverlappedTop != null && !Current.Modal) {
-				lock (_topLevels) {
-					_topLevels.MovePrevious ();
-					var isOverlapped = false;
-					while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) {
-						if (!isOverlapped && _topLevels.Peek () == OverlappedTop) {
-							isOverlapped = true;
-						} else if (isOverlapped && _topLevels.Peek () == OverlappedTop) {
-							MoveCurrent (Top);
-							break;
-						}
-						_topLevels.MovePrevious ();
+	/// <summary>
+	/// Move to the previous Overlapped child from the <see cref="OverlappedTop"/>.
+	/// </summary>
+	public static void OverlappedMovePrevious ()
+	{
+		if (OverlappedTop != null && !Current.Modal) {
+			lock (_topLevels) {
+				_topLevels.MovePrevious ();
+				var isOverlapped = false;
+				while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) {
+					if (!isOverlapped && _topLevels.Peek () == OverlappedTop) {
+						isOverlapped = true;
+					} else if (isOverlapped && _topLevels.Peek () == OverlappedTop) {
+						MoveCurrent (Top);
+						break;
 					}
-					Current = _topLevels.Peek ();
+					_topLevels.MovePrevious ();
 				}
+				Current = _topLevels.Peek ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Move to the next Overlapped child from the <see cref="OverlappedTop"/> and set it as the <see cref="Top"/> if it is not already.
-		/// </summary>
-		/// <param name="top"></param>
-		/// <returns></returns>
-		public static bool MoveToOverlappedChild (Toplevel top)
-		{
-			if (top.Visible && OverlappedTop != null && Current?.Modal == false) {
-				lock (_topLevels) {
-					_topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
-					Current = top;
-				}
-				return true;
-			}
-			return false;
+	/// <summary>
+	/// Move to the next Overlapped child from the <see cref="OverlappedTop"/> and set it as the
+	/// <see cref="Top"/> if it is not already.
+	/// </summary>
+	/// <param name="top"></param>
+	/// <returns></returns>
+	public static bool MoveToOverlappedChild (Toplevel top)
+	{
+		if (top.Visible && OverlappedTop != null && Current?.Modal == false) {
+			lock (_topLevels) {
+				_topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
+				Current = top;
+			}
+			return true;
 		}
+		return false;
+	}
 
 
-		/// <summary>
-		/// Brings the superview of the most focused overlapped view is on front.
-		/// </summary>
-		public static void BringOverlappedTopToFront ()
-		{
-			if (OverlappedTop != null) {
-				return;
-			}
-			var top = FindTopFromView (Top?.MostFocused);
-			if (top != null && Top.Subviews.Count > 1 && Top.Subviews [Top.Subviews.Count - 1] != top) {
-				Top.BringSubviewToFront (top);
-			}
+	/// <summary>
+	/// Brings the superview of the most focused overlapped view is on front.
+	/// </summary>
+	public static void BringOverlappedTopToFront ()
+	{
+		if (OverlappedTop != null) {
+			return;
 		}
+		var top = FindTopFromView (Top?.MostFocused);
+		if (top != null && Top.Subviews.Count > 1 && Top.Subviews [Top.Subviews.Count - 1] != top) {
+			Top.BringSubviewToFront (top);
+		}
+	}
 
 
-		/// <summary>
-		/// Gets the current visible Toplevel overlapped child that matches the arguments pattern.
-		/// </summary>
-		/// <param name="type">The type.</param>
-		/// <param name="exclude">The strings to exclude.</param>
-		/// <returns>The matched view.</returns>
-		public static Toplevel GetTopOverlappedChild (Type type = null, string [] exclude = null)
-		{
-			if (Application.OverlappedTop == null) {
-				return null;
-			}
+	/// <summary>
+	/// Gets the current visible Toplevel overlapped child that matches the arguments pattern.
+	/// </summary>
+	/// <param name="type">The type.</param>
+	/// <param name="exclude">The strings to exclude.</param>
+	/// <returns>The matched view.</returns>
+	public static Toplevel GetTopOverlappedChild (Type type = null, string [] exclude = null)
+	{
+		if (OverlappedTop == null) {
+			return null;
+		}
 
-			foreach (var top in Application.OverlappedChildren) {
-				if (type != null && top.GetType () == type
-					&& exclude?.Contains (top.Data.ToString ()) == false) {
-					return top;
-				} else if ((type != null && top.GetType () != type)
-					|| (exclude?.Contains (top.Data.ToString ()) == true)) {
-					continue;
-				}
+		foreach (var top in OverlappedChildren) {
+			if (type != null && top.GetType () == type && exclude?.Contains (top.Data.ToString ()) == false) {
 				return top;
 			}
-			return null;
+			if (type != null && top.GetType () != type || exclude?.Contains (top.Data.ToString ()) == true) {
+				continue;
+			}
+			return top;
 		}
-
+		return null;
 	}
-}
+}

+ 50 - 58
Terminal.Gui/Views/TreeView/TreeNode.cs

@@ -1,73 +1,65 @@
 using System.Collections.Generic;
 
-namespace Terminal.Gui {
-		
+namespace Terminal.Gui; 
+
+/// <summary>
+/// Interface to implement when you want the regular (non generic) <see cref="TreeView"/>
+/// to automatically determine children for your class (without having to specify
+/// an <see cref="ITreeBuilder{T}"/>)
+/// </summary>
+public interface ITreeNode {
+	/// <summary>
+	/// Text to display when rendering the node
+	/// </summary>
+	string Text { get; set; }
+
 	/// <summary>
-	/// Interface to implement when you want the regular (non generic) <see cref="TreeView"/>
-	/// to automatically determine children for your class (without having to specify 
-	/// an <see cref="ITreeBuilder{T}"/>)
+	/// The children of your class which should be rendered underneath it when expanded
 	/// </summary>
-	public interface ITreeNode {
-		/// <summary>
-		/// Text to display when rendering the node
-		/// </summary>
-		string Text { get; set; }
+	/// <value></value>
+	IList<ITreeNode> Children { get; }
 
-		/// <summary>
-		/// The children of your class which should be rendered underneath it when expanded
-		/// </summary>
-		/// <value></value>
-		IList<ITreeNode> Children { get; }
+	/// <summary>
+	/// Optionally allows you to store some custom data/class here.
+	/// </summary>
+	object Tag { get; set; }
+}
 
-		/// <summary>
-		/// Optionally allows you to store some custom data/class here.
-		/// </summary>
-		object Tag { get; set; }
-	}
+/// <summary>
+/// Simple class for representing nodes, use with regular (non generic) <see cref="TreeView"/>.
+/// </summary>
+public class TreeNode : ITreeNode {
 
 	/// <summary>
-	/// Simple class for representing nodes, use with regular (non generic) <see cref="TreeView"/>.
+	/// Initialises a new instance with no <see cref="Text"/>
 	/// </summary>
-	public class TreeNode : ITreeNode {
-		/// <summary>
-		/// Children of the current node
-		/// </summary>
-		/// <returns></returns>
-		public virtual IList<ITreeNode> Children { get; set; } = new List<ITreeNode> ();
+	public TreeNode () { }
 
-		/// <summary>
-		/// Text to display in tree node for current entry
-		/// </summary>
-		/// <value></value>
-		public virtual string Text { get; set; }
+	/// <summary>
+	/// Initialises a new instance and sets starting <see cref="Text"/>
+	/// </summary>
+	public TreeNode (string text) => Text = text;
 
-		/// <summary>
-		/// Optionally allows you to store some custom data/class here.
-		/// </summary>
-		public object Tag { get; set; }
+	/// <summary>
+	/// Children of the current node
+	/// </summary>
+	/// <returns></returns>
+	public virtual IList<ITreeNode> Children { get; set; } = new List<ITreeNode> ();
 
-		/// <summary>
-		/// returns <see cref="Text"/>
-		/// </summary>
-		/// <returns></returns>
-		public override string ToString ()
-		{
-			return Text ?? "Unamed Node";
-		}
+	/// <summary>
+	/// Text to display in tree node for current entry
+	/// </summary>
+	/// <value></value>
+	public virtual string Text { get; set; }
 
-		/// <summary>
-		/// Initialises a new instance with no <see cref="Text"/>
-		/// </summary>
-		public TreeNode ()
-		{
+	/// <summary>
+	/// Optionally allows you to store some custom data/class here.
+	/// </summary>
+	public object Tag { get; set; }
 
-		}
-		/// <summary>
-		/// Initialises a new instance and sets starting <see cref="Text"/>
-		/// </summary>
-		public TreeNode (string text)
-		{
-			Text = text;
-		}
-	}
+	/// <summary>
+	/// returns <see cref="Text"/>
+	/// </summary>
+	/// <returns></returns>
+	public override string ToString () => Text ?? "Unamed Node";
 }

+ 1242 - 1221
Terminal.Gui/Views/TreeView/TreeView.cs

@@ -2,1474 +2,1495 @@
 // by [email protected]). Phillip has explicitly granted permission for his design
 // and code to be used in this library under the MIT license.
 
-using System.Text;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Linq;
-using Terminal.Gui;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui; 
 
+/// <summary>
+/// Interface for all non generic members of <see cref="TreeView{T}"/>.
+/// <a href="../docs/treeview.md">See TreeView Deep Dive for more information</a>.
+/// </summary>
+public interface ITreeView {
 	/// <summary>
-	/// Interface for all non generic members of <see cref="TreeView{T}"/>.
-	/// 
-	/// <a href="../docs/treeview.md">See TreeView Deep Dive for more information</a>.
-	/// </summary>
-	public interface ITreeView {
-		/// <summary>
-		/// Contains options for changing how the tree is rendered.
-		/// </summary>
-		TreeStyle Style { get; set; }
-
-		/// <summary>
-		/// Removes all objects from the tree and clears selection.
-		/// </summary>
-		void ClearObjects ();
-
-		/// <summary>
-		/// Sets a flag indicating this view needs to be redisplayed because its state has changed.
-		/// </summary>
-		void SetNeedsDisplay ();
+	/// Contains options for changing how the tree is rendered.
+	/// </summary>
+	TreeStyle Style { get; set; }
+
+	/// <summary>
+	/// Removes all objects from the tree and clears selection.
+	/// </summary>
+	void ClearObjects ();
+
+	/// <summary>
+	/// Sets a flag indicating this view needs to be redisplayed because its state has changed.
+	/// </summary>
+	void SetNeedsDisplay ();
+}
+
+/// <summary>
+/// Convenience implementation of generic <see cref="TreeView{T}"/> for any tree were all nodes
+/// implement <see cref="ITreeNode"/>.
+/// <a href="../docs/treeview.md">See TreeView Deep Dive for more information</a>.
+/// </summary>
+public class TreeView : TreeView<ITreeNode> {
+
+	/// <summary>
+	/// Creates a new instance of the tree control with absolute positioning and initialises
+	/// <see cref="TreeBuilder{T}"/> with default <see cref="ITreeNode"/> based builder.
+	/// </summary>
+	public TreeView ()
+	{
+		TreeBuilder = new TreeNodeBuilder ();
+		AspectGetter = o => o == null ? "Null" : o.Text ?? o?.ToString () ?? "Unamed Node";
 	}
+}
+
+/// <summary>
+/// Hierarchical tree view with expandable branches. Branch objects are dynamically determined
+/// when expanded using a user defined <see cref="ITreeBuilder{T}"/>.
+/// <a href="../docs/treeview.md">See TreeView Deep Dive for more information</a>.
+/// </summary>
+public class TreeView<T> : View, ITreeView where T : class {
 
 	/// <summary>
-	/// Convenience implementation of generic <see cref="TreeView{T}"/> for any tree were all nodes
-	/// implement <see cref="ITreeNode"/>.
-	/// 
-	/// <a href="../docs/treeview.md">See TreeView Deep Dive for more information</a>.
+	/// Error message to display when the control is not properly initialized at draw time
+	/// (nodes added but no tree builder set).
 	/// </summary>
-	public class TreeView : TreeView<ITreeNode> {
+	public static string NoBuilderError = "ERROR: TreeBuilder Not Set";
 
-		/// <summary>
-		/// Creates a new instance of the tree control with absolute positioning and initialises
-		/// <see cref="TreeBuilder{T}"/> with default <see cref="ITreeNode"/> based builder.
-		/// </summary>
-		public TreeView ()
-		{
-			TreeBuilder = new TreeNodeBuilder ();
-			AspectGetter = o => o == null ? "Null" : (o.Text ?? o?.ToString () ?? "Unamed Node");
-		}
+	/// <summary>
+	/// Cached result of <see cref="BuildLineMap"/>
+	/// </summary>
+	IReadOnlyCollection<Branch<T>> cachedLineMap;
+
+	CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
+
+	/// <summary>
+	/// Interface for filtering which lines of the tree are displayed
+	/// e.g. to provide text searching.  Defaults to <see langword="null"/>
+	/// (no filtering).
+	/// </summary>
+	public ITreeViewFilter<T> Filter = null;
+
+	/// <summary>
+	/// Secondary selected regions of tree when <see cref="MultiSelect"/> is true.
+	/// </summary>
+	readonly Stack<TreeSelection<T>> multiSelectedRegions = new ();
+
+	KeyCode objectActivationKey = KeyCode.Enter;
+	int scrollOffsetHorizontal;
+	int scrollOffsetVertical;
+
+	/// <summary>
+	/// private variable for <see cref="SelectedObject"/>
+	/// </summary>
+	T selectedObject;
+
+	/// <summary>
+	/// Creates a new tree view with absolute positioning.
+	/// Use <see cref="AddObjects(IEnumerable{T})"/> to set set root objects for the tree.
+	/// Children will not be rendered until you set <see cref="TreeBuilder"/>.
+	/// </summary>
+	public TreeView ()
+	{
+		CanFocus = true;
+
+		// Things this view knows how to do
+		AddCommand (Command.PageUp, () => {
+			MovePageUp ();
+			return true;
+		});
+		AddCommand (Command.PageDown, () => {
+			MovePageDown ();
+			return true;
+		});
+		AddCommand (Command.PageUpExtend, () => {
+			MovePageUp (true);
+			return true;
+		});
+		AddCommand (Command.PageDownExtend, () => {
+			MovePageDown (true);
+			return true;
+		});
+		AddCommand (Command.Expand, () => {
+			Expand ();
+			return true;
+		});
+		AddCommand (Command.ExpandAll, () => {
+			ExpandAll (SelectedObject);
+			return true;
+		});
+		AddCommand (Command.Collapse, () => {
+			CursorLeft (false);
+			return true;
+		});
+		AddCommand (Command.CollapseAll, () => {
+			CursorLeft (true);
+			return true;
+		});
+		AddCommand (Command.LineUp, () => {
+			AdjustSelection (-1);
+			return true;
+		});
+		AddCommand (Command.LineUpExtend, () => {
+			AdjustSelection (-1, true);
+			return true;
+		});
+		AddCommand (Command.LineUpToFirstBranch, () => {
+			AdjustSelectionToBranchStart ();
+			return true;
+		});
+
+		AddCommand (Command.LineDown, () => {
+			AdjustSelection (1);
+			return true;
+		});
+		AddCommand (Command.LineDownExtend, () => {
+			AdjustSelection (1, true);
+			return true;
+		});
+		AddCommand (Command.LineDownToLastBranch, () => {
+			AdjustSelectionToBranchEnd ();
+			return true;
+		});
+
+		AddCommand (Command.TopHome, () => {
+			GoToFirst ();
+			return true;
+		});
+		AddCommand (Command.BottomEnd, () => {
+			GoToEnd ();
+			return true;
+		});
+		AddCommand (Command.SelectAll, () => {
+			SelectAll ();
+			return true;
+		});
+
+		AddCommand (Command.ScrollUp, () => {
+			ScrollUp ();
+			return true;
+		});
+		AddCommand (Command.ScrollDown, () => {
+			ScrollDown ();
+			return true;
+		});
+		AddCommand (Command.Accept, () => {
+			ActivateSelectedObjectIfAny ();
+			return true;
+		});
+
+		// Default keybindings for this view
+		KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+		KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+		KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend);
+		KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend);
+		KeyBindings.Add (KeyCode.CursorRight, Command.Expand);
+		KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.ExpandAll);
+		KeyBindings.Add (KeyCode.CursorLeft, Command.Collapse);
+		KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.CollapseAll);
+
+		KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+		KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend);
+		KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.LineUpToFirstBranch);
+
+		KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+		KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend);
+		KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.LineDownToLastBranch);
+
+		KeyBindings.Add (KeyCode.Home, Command.TopHome);
+		KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+		KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.SelectAll);
+		KeyBindings.Add (ObjectActivationKey, Command.Accept);
 	}
 
 	/// <summary>
-	/// Hierarchical tree view with expandable branches. Branch objects are dynamically determined
-	/// when expanded using a user defined <see cref="ITreeBuilder{T}"/>.
-	/// 
-	/// <a href="../docs/treeview.md">See TreeView Deep Dive for more information</a>.
-	/// </summary>
-	public class TreeView<T> : View, ITreeView where T : class {
-		private int scrollOffsetVertical;
-		private int scrollOffsetHorizontal;
-
-		/// <summary>
-		/// Determines how sub branches of the tree are dynamically built at runtime as the user
-		/// expands root nodes.
-		/// </summary>
-		/// <value></value>
-		public ITreeBuilder<T> TreeBuilder { get; set; }
-
-		/// <summary>
-		/// private variable for <see cref="SelectedObject"/>
-		/// </summary>
-		T selectedObject;
-
-		/// <summary>
-		/// Contains options for changing how the tree is rendered.
-		/// </summary>
-		public TreeStyle Style { get; set; } = new TreeStyle ();
-
-		/// <summary>
-		/// True to allow multiple objects to be selected at once.
-		/// </summary>
-		/// <value></value>
-		public bool MultiSelect { get; set; } = true;
-
-		/// <summary>
-		/// Maximum number of nodes that can be expanded in any given branch.
-		/// </summary>
-		public int MaxDepth { get; set; } = 100;
-
-		/// <summary>
-		/// True makes a letter key press navigate to the next visible branch that begins with
-		/// that letter/digit.
-		/// </summary>
-		/// <value></value>
-		public bool AllowLetterBasedNavigation { get; set; } = true;
-
-		/// <summary>
-		/// The currently selected object in the tree. When <see cref="MultiSelect"/> is true this
-		/// is the object at which the cursor is at.
-		/// </summary>
-		public T SelectedObject {
-			get => selectedObject;
-			set {
-				var oldValue = selectedObject;
-				selectedObject = value;
-
-				if (!ReferenceEquals (oldValue, value)) {
-					OnSelectionChanged (new SelectionChangedEventArgs<T> (this, oldValue, value));
-				}
-			}
-		}
+	/// Initialises <see cref="TreeBuilder"/>.Creates a new tree view with absolute
+	/// positioning. Use <see cref="AddObjects(IEnumerable{T})"/> to set set root
+	/// objects for the tree.
+	/// </summary>
+	public TreeView (ITreeBuilder<T> builder) : this () => TreeBuilder = builder;
 
-		/// <summary>
-		/// This event is raised when an object is activated e.g. by double clicking or 
-		/// pressing <see cref="ObjectActivationKey"/>.
-		/// </summary>
-		public event EventHandler<ObjectActivatedEventArgs<T>> ObjectActivated;
-
-		// TODO: Update to use Key instead of KeyCode
-		/// <summary>
-		/// Key which when pressed triggers <see cref="TreeView{T}.ObjectActivated"/>.
-		/// Defaults to Enter.
-		/// </summary>
-		public KeyCode ObjectActivationKey {
-			get => objectActivationKey;
-			set {
-				if (objectActivationKey != value) {
-					KeyBindings.Replace (ObjectActivationKey, value);
-					objectActivationKey = value;
-				}
-			}
-		}
+	/// <summary>
+	/// Determines how sub branches of the tree are dynamically built at runtime as the user
+	/// expands root nodes.
+	/// </summary>
+	/// <value></value>
+	public ITreeBuilder<T> TreeBuilder { get; set; }
 
-		/// <summary>
-		/// Mouse event to trigger <see cref="TreeView{T}.ObjectActivated"/>.
-		/// Defaults to double click (<see cref="MouseFlags.Button1DoubleClicked"/>).
-		/// Set to null to disable this feature.
-		/// </summary>
-		/// <value></value>
-		public MouseFlags? ObjectActivationButton { get; set; } = MouseFlags.Button1DoubleClicked;
-
-		/// <summary>
-		/// Delegate for multi colored tree views. Return the <see cref="ColorScheme"/> to use
-		/// for each passed object or null to use the default.
-		/// </summary>
-		public Func<T, ColorScheme> ColorGetter { get; set; }
-
-		/// <summary>
-		/// Secondary selected regions of tree when <see cref="MultiSelect"/> is true.
-		/// </summary>
-		private Stack<TreeSelection<T>> multiSelectedRegions = new Stack<TreeSelection<T>> ();
-
-		/// <summary>
-		/// Cached result of <see cref="BuildLineMap"/>
-		/// </summary>
-		private IReadOnlyCollection<Branch<T>> cachedLineMap;
-
-		/// <summary>
-		/// Error message to display when the control is not properly initialized at draw time 
-		/// (nodes added but no tree builder set).
-		/// </summary>
-		public static string NoBuilderError = "ERROR: TreeBuilder Not Set";
-		private KeyCode objectActivationKey = KeyCode.Enter;
-
-		/// <summary>
-		/// Called when the <see cref="SelectedObject"/> changes.
-		/// </summary>
-		public event EventHandler<SelectionChangedEventArgs<T>> SelectionChanged;
-
-		/// <summary>
-		/// Called once for each visible row during rendering.  Can be used
-		/// to make last minute changes to color or text rendered
-		/// </summary>
-		public event EventHandler<DrawTreeViewLineEventArgs<T>> DrawLine;
-
-		/// <summary>
-		/// The root objects in the tree, note that this collection is of root objects only.
-		/// </summary>
-		public IEnumerable<T> Objects { get => roots.Keys; }
-
-		/// <summary>
-		/// Map of root objects to the branches under them. All objects have 
-		/// a <see cref="Branch{T}"/> even if that branch has no children.
-		/// </summary>
-		internal Dictionary<T, Branch<T>> roots { get; set; } = new Dictionary<T, Branch<T>> ();
-
-		/// <summary>
-		/// The amount of tree view that has been scrolled off the top of the screen (by the user 
-		/// scrolling down).
-		/// </summary>
-		/// <remarks>Setting a value of less than 0 will result in a offset of 0. To see changes 
-		/// in the UI call <see cref="View.SetNeedsDisplay()"/>.</remarks>
-		public int ScrollOffsetVertical {
-			get => scrollOffsetVertical;
-			set {
-				scrollOffsetVertical = Math.Max (0, value);
-			}
-		}
+	/// <summary>
+	/// True to allow multiple objects to be selected at once.
+	/// </summary>
+	/// <value></value>
+	public bool MultiSelect { get; set; } = true;
+
+	/// <summary>
+	/// Maximum number of nodes that can be expanded in any given branch.
+	/// </summary>
+	public int MaxDepth { get; set; } = 100;
+
+	/// <summary>
+	/// True makes a letter key press navigate to the next visible branch that begins with
+	/// that letter/digit.
+	/// </summary>
+	/// <value></value>
+	public bool AllowLetterBasedNavigation { get; set; } = true;
+
+	/// <summary>
+	/// The currently selected object in the tree. When <see cref="MultiSelect"/> is true this
+	/// is the object at which the cursor is at.
+	/// </summary>
+	public T SelectedObject {
+		get => selectedObject;
+		set {
+			var oldValue = selectedObject;
+			selectedObject = value;
 
-		/// <summary>
-		/// The amount of tree view that has been scrolled to the right (horizontally).
-		/// </summary>
-		/// <remarks>Setting a value of less than 0 will result in a offset of 0. To see changes 
-		/// in the UI call <see cref="View.SetNeedsDisplay()"/>.</remarks>
-		public int ScrollOffsetHorizontal {
-			get => scrollOffsetHorizontal;
-			set {
-				scrollOffsetHorizontal = Math.Max (0, value);
+			if (!ReferenceEquals (oldValue, value)) {
+				OnSelectionChanged (new SelectionChangedEventArgs<T> (this, oldValue, value));
 			}
 		}
+	}
 
-		/// <summary>
-		/// The current number of rows in the tree (ignoring the controls bounds).
-		/// </summary>
-		public int ContentHeight => BuildLineMap ().Count ();
-
-		/// <summary>
-		/// Returns the string representation of model objects hosted in the tree. Default 
-		/// implementation is to call <see cref="object.ToString"/>.
-		/// </summary>
-		/// <value></value>
-		public AspectGetterDelegate<T> AspectGetter { get; set; } = (o) => o.ToString () ?? "";
-
-		CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
-
-		/// <summary>
-		/// Interface for filtering which lines of the tree are displayed
-		///  e.g. to provide text searching.  Defaults to <see langword="null"/>
-		/// (no filtering).
-		/// </summary>
-		public ITreeViewFilter<T> Filter = null;
-
-		/// <summary>
-		/// Get / Set the wished cursor when the tree is focused.
-		/// Only applies when <see cref="MultiSelect"/> is true.
-		/// Defaults to <see cref="CursorVisibility.Invisible"/>.
-		/// </summary>
-		public CursorVisibility DesiredCursorVisibility {
-			get {
-				return MultiSelect ? desiredCursorVisibility : CursorVisibility.Invisible;
-			}
-			set {
-				if (desiredCursorVisibility != value) {
-					desiredCursorVisibility = value;
-					if (HasFocus) {
-						Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
-					}
-				}
+	// TODO: Update to use Key instead of KeyCode
+	/// <summary>
+	/// Key which when pressed triggers <see cref="TreeView{T}.ObjectActivated"/>.
+	/// Defaults to Enter.
+	/// </summary>
+	public KeyCode ObjectActivationKey {
+		get => objectActivationKey;
+		set {
+			if (objectActivationKey != value) {
+				KeyBindings.Replace (ObjectActivationKey, value);
+				objectActivationKey = value;
 			}
 		}
+	}
 
-		/// <summary>
-		/// Creates a new tree view with absolute positioning. 
-		/// Use <see cref="AddObjects(IEnumerable{T})"/> to set set root objects for the tree.
-		/// Children will not be rendered until you set <see cref="TreeBuilder"/>.
-		/// </summary>
-		public TreeView () : base ()
-		{
-			CanFocus = true;
-
-			// Things this view knows how to do
-			AddCommand (Command.PageUp, () => { MovePageUp (false); return true; });
-			AddCommand (Command.PageDown, () => { MovePageDown (false); return true; });
-			AddCommand (Command.PageUpExtend, () => { MovePageUp (true); return true; });
-			AddCommand (Command.PageDownExtend, () => { MovePageDown (true); return true; });
-			AddCommand (Command.Expand, () => { Expand (); return true; });
-			AddCommand (Command.ExpandAll, () => { ExpandAll (SelectedObject); return true; });
-			AddCommand (Command.Collapse, () => { CursorLeft (false); return true; });
-			AddCommand (Command.CollapseAll, () => { CursorLeft (true); return true; });
-			AddCommand (Command.LineUp, () => { AdjustSelection (-1, false); return true; });
-			AddCommand (Command.LineUpExtend, () => { AdjustSelection (-1, true); return true; });
-			AddCommand (Command.LineUpToFirstBranch, () => { AdjustSelectionToBranchStart (); return true; });
-
-			AddCommand (Command.LineDown, () => { AdjustSelection (1, false); return true; });
-			AddCommand (Command.LineDownExtend, () => { AdjustSelection (1, true); return true; });
-			AddCommand (Command.LineDownToLastBranch, () => { AdjustSelectionToBranchEnd (); return true; });
-
-			AddCommand (Command.TopHome, () => { GoToFirst (); return true; });
-			AddCommand (Command.BottomEnd, () => { GoToEnd (); return true; });
-			AddCommand (Command.SelectAll, () => { SelectAll (); return true; });
-
-			AddCommand (Command.ScrollUp, () => { ScrollUp (); return true; });
-			AddCommand (Command.ScrollDown, () => { ScrollDown (); return true; });
-			AddCommand (Command.Accept, () => { ActivateSelectedObjectIfAny (); return true; });
-
-			// Default keybindings for this view
-			KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
-			KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
-			KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend);
-			KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend);
-			KeyBindings.Add (KeyCode.CursorRight, Command.Expand);
-			KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.ExpandAll);
-			KeyBindings.Add (KeyCode.CursorLeft, Command.Collapse);
-			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.CollapseAll);
-
-			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
-			KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend);
-			KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.LineUpToFirstBranch);
-
-			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
-			KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend);
-			KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.LineDownToLastBranch);
-
-			KeyBindings.Add (KeyCode.Home, Command.TopHome);
-			KeyBindings.Add (KeyCode.End, Command.BottomEnd);
-			KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.SelectAll);
-			KeyBindings.Add (ObjectActivationKey, Command.Accept);
-		}
-
-		/// <summary>
-		/// Initialises <see cref="TreeBuilder"/>.Creates a new tree view with absolute 
-		/// positioning. Use <see cref="AddObjects(IEnumerable{T})"/> to set set root 
-		/// objects for the tree.
-		/// </summary>
-		public TreeView (ITreeBuilder<T> builder) : this ()
-		{
-			TreeBuilder = builder;
-		}
-
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
-
-			if (SelectedObject == null && Objects.Any ()) {
-				SelectedObject = Objects.First ();
-			}
+	/// <summary>
+	/// Mouse event to trigger <see cref="TreeView{T}.ObjectActivated"/>.
+	/// Defaults to double click (<see cref="MouseFlags.Button1DoubleClicked"/>).
+	/// Set to null to disable this feature.
+	/// </summary>
+	/// <value></value>
+	public MouseFlags? ObjectActivationButton { get; set; } = MouseFlags.Button1DoubleClicked;
 
-			return base.OnEnter (view);
-		}
+	/// <summary>
+	/// Delegate for multi colored tree views. Return the <see cref="ColorScheme"/> to use
+	/// for each passed object or null to use the default.
+	/// </summary>
+	public Func<T, ColorScheme> ColorGetter { get; set; }
 
-		/// <summary>
-		/// Adds a new root level object unless it is already a root of the tree.
-		/// </summary>
-		/// <param name="o"></param>
-		public void AddObject (T o)
-		{
-			if (!roots.ContainsKey (o)) {
-				roots.Add (o, new Branch<T> (this, null, o));
-				InvalidateLineMap ();
-				SetNeedsDisplay ();
-			}
-		}
+	/// <summary>
+	/// The root objects in the tree, note that this collection is of root objects only.
+	/// </summary>
+	public IEnumerable<T> Objects => roots.Keys;
 
-		/// <summary>
-		/// Removes all objects from the tree and clears <see cref="SelectedObject"/>.
-		/// </summary>
-		public void ClearObjects ()
-		{
-			SelectedObject = default (T);
-			multiSelectedRegions.Clear ();
-			roots = new Dictionary<T, Branch<T>> ();
-			InvalidateLineMap ();
-			SetNeedsDisplay ();
-		}
+	/// <summary>
+	/// Map of root objects to the branches under them. All objects have
+	/// a <see cref="Branch{T}"/> even if that branch has no children.
+	/// </summary>
+	internal Dictionary<T, Branch<T>> roots { get; set; } = new ();
 
-		/// <summary>
-		/// Removes the given root object from the tree
-		/// </summary>
-		/// <remarks>If <paramref name="o"/> is the currently <see cref="SelectedObject"/> then the
-		/// selection is cleared</remarks>.
-		/// <param name="o"></param>
-		public void Remove (T o)
-		{
-			if (roots.ContainsKey (o)) {
-				roots.Remove (o);
-				InvalidateLineMap ();
-				SetNeedsDisplay ();
+	/// <summary>
+	/// The amount of tree view that has been scrolled off the top of the screen (by the user
+	/// scrolling down).
+	/// </summary>
+	/// <remarks>
+	/// Setting a value of less than 0 will result in a offset of 0. To see changes
+	/// in the UI call <see cref="View.SetNeedsDisplay()"/>.
+	/// </remarks>
+	public int ScrollOffsetVertical {
+		get => scrollOffsetVertical;
+		set => scrollOffsetVertical = Math.Max (0, value);
+	}
+
+	/// <summary>
+	/// The amount of tree view that has been scrolled to the right (horizontally).
+	/// </summary>
+	/// <remarks>
+	/// Setting a value of less than 0 will result in a offset of 0. To see changes
+	/// in the UI call <see cref="View.SetNeedsDisplay()"/>.
+	/// </remarks>
+	public int ScrollOffsetHorizontal {
+		get => scrollOffsetHorizontal;
+		set => scrollOffsetHorizontal = Math.Max (0, value);
+	}
+
+	/// <summary>
+	/// The current number of rows in the tree (ignoring the controls bounds).
+	/// </summary>
+	public int ContentHeight => BuildLineMap ().Count ();
+
+	/// <summary>
+	/// Returns the string representation of model objects hosted in the tree. Default
+	/// implementation is to call <see cref="object.ToString"/>.
+	/// </summary>
+	/// <value></value>
+	public AspectGetterDelegate<T> AspectGetter { get; set; } = o => o.ToString () ?? "";
 
-				if (Equals (SelectedObject, o)) {
-					SelectedObject = default (T);
+	/// <summary>
+	/// Get / Set the wished cursor when the tree is focused.
+	/// Only applies when <see cref="MultiSelect"/> is true.
+	/// Defaults to <see cref="CursorVisibility.Invisible"/>.
+	/// </summary>
+	public CursorVisibility DesiredCursorVisibility {
+		get => MultiSelect ? desiredCursorVisibility : CursorVisibility.Invisible;
+		set {
+			if (desiredCursorVisibility != value) {
+				desiredCursorVisibility = value;
+				if (HasFocus) {
+					Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
 				}
 			}
 		}
+	}
 
-		/// <summary>
-		/// Adds many new root level objects. Objects that are already root objects are ignored.
-		/// </summary>
-		/// <param name="collection">Objects to add as new root level objects.</param>.\
-		public void AddObjects (IEnumerable<T> collection)
-		{
-			bool objectsAdded = false;
+	/// <summary>
+	/// Gets the <see cref="CollectionNavigator"/> that searches the <see cref="Objects"/> collection as
+	/// the user types.
+	/// </summary>
+	public CollectionNavigator KeystrokeNavigator { get; } = new ();
 
-			foreach (var o in collection) {
-				if (!roots.ContainsKey (o)) {
-					roots.Add (o, new Branch<T> (this, null, o));
-					objectsAdded = true;
-				}
-			}
+	/// <summary>
+	/// Contains options for changing how the tree is rendered.
+	/// </summary>
+	public TreeStyle Style { get; set; } = new ();
 
-			if (objectsAdded) {
-				InvalidateLineMap ();
-				SetNeedsDisplay ();
-			}
-		}
+	/// <summary>
+	/// Removes all objects from the tree and clears <see cref="SelectedObject"/>.
+	/// </summary>
+	public void ClearObjects ()
+	{
+		SelectedObject = default;
+		multiSelectedRegions.Clear ();
+		roots = new Dictionary<T, Branch<T>> ();
+		InvalidateLineMap ();
+		SetNeedsDisplay ();
+	}
 
-		/// <summary>
-		/// Refreshes the state of the object <paramref name="o"/> in the tree. This will 
-		/// recompute children, string representation etc.
-		/// </summary>
-		/// <remarks>This has no effect if the object is not exposed in the tree.</remarks>
-		/// <param name="o"></param>
-		/// <param name="startAtTop">True to also refresh all ancestors of the objects branch 
-		/// (starting with the root). False to refresh only the passed node.</param>
-		public void RefreshObject (T o, bool startAtTop = false)
-		{
-			var branch = ObjectToBranch (o);
-			if (branch != null) {
-				branch.Refresh (startAtTop);
-				InvalidateLineMap ();
-				SetNeedsDisplay ();
-			}
+	/// <summary>
+	/// This event is raised when an object is activated e.g. by double clicking or
+	/// pressing <see cref="ObjectActivationKey"/>.
+	/// </summary>
+	public event EventHandler<ObjectActivatedEventArgs<T>> ObjectActivated;
+
+	/// <summary>
+	/// Called when the <see cref="SelectedObject"/> changes.
+	/// </summary>
+	public event EventHandler<SelectionChangedEventArgs<T>> SelectionChanged;
+
+	/// <summary>
+	/// Called once for each visible row during rendering.  Can be used
+	/// to make last minute changes to color or text rendered
+	/// </summary>
+	public event EventHandler<DrawTreeViewLineEventArgs<T>> DrawLine;
+
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
 
+		if (SelectedObject == null && Objects.Any ()) {
+			SelectedObject = Objects.First ();
 		}
 
-		/// <summary>
-		/// Rebuilds the tree structure for all exposed objects starting with the root objects.
-		/// Call this method when you know there are changes to the tree but don't know which 
-		/// objects have changed (otherwise use <see cref="RefreshObject(T, bool)"/>).
-		/// </summary>
-		public void RebuildTree ()
-		{
-			foreach (var branch in roots.Values) {
-				branch.Rebuild ();
-			}
+		return base.OnEnter (view);
+	}
 
+	/// <summary>
+	/// Adds a new root level object unless it is already a root of the tree.
+	/// </summary>
+	/// <param name="o"></param>
+	public void AddObject (T o)
+	{
+		if (!roots.ContainsKey (o)) {
+			roots.Add (o, new Branch<T> (this, null, o));
 			InvalidateLineMap ();
 			SetNeedsDisplay ();
 		}
+	}
 
-		/// <summary>
-		/// Returns the currently expanded children of the passed object. Returns an empty
-		/// collection if the branch is not exposed or not expanded.
-		/// </summary>
-		/// <param name="o">An object in the tree.</param>
-		/// <returns></returns>
-		public IEnumerable<T> GetChildren (T o)
-		{
-			var branch = ObjectToBranch (o);
+	/// <summary>
+	/// Removes the given root object from the tree
+	/// </summary>
+	/// <remarks>
+	/// If <paramref name="o"/> is the currently <see cref="SelectedObject"/> then the
+	/// selection is cleared
+	/// </remarks>
+	/// .
+	/// <param name="o"></param>
+	public void Remove (T o)
+	{
+		if (roots.ContainsKey (o)) {
+			roots.Remove (o);
+			InvalidateLineMap ();
+			SetNeedsDisplay ();
 
-			if (branch == null || !branch.IsExpanded) {
-				return new T [0];
+			if (Equals (SelectedObject, o)) {
+				SelectedObject = default;
 			}
-
-			return branch.ChildBranches?.Values?.Select (b => b.Model)?.ToArray () ?? new T [0];
-		}
-		/// <summary>
-		/// Returns the parent object of <paramref name="o"/> in the tree. Returns null if 
-		/// the object is not exposed in the tree.
-		/// </summary>
-		/// <param name="o">An object in the tree.</param>
-		/// <returns></returns>
-		public T GetParent (T o)
-		{
-			return ObjectToBranch (o)?.Parent?.Model;
 		}
+	}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			if (roots == null) {
-				return;
-			}
+	/// <summary>
+	/// Adds many new root level objects. Objects that are already root objects are ignored.
+	/// </summary>
+	/// <param name="collection">Objects to add as new root level objects.</param>
+	/// .\
+	public void AddObjects (IEnumerable<T> collection)
+	{
+		var objectsAdded = false;
 
-			if (TreeBuilder == null) {
-				Move (0, 0);
-				Driver.AddStr (NoBuilderError);
-				return;
+		foreach (var o in collection) {
+			if (!roots.ContainsKey (o)) {
+				roots.Add (o, new Branch<T> (this, null, o));
+				objectsAdded = true;
 			}
+		}
 
-			var map = BuildLineMap ();
-
-			for (int line = 0; line < Bounds.Height; line++) {
+		if (objectsAdded) {
+			InvalidateLineMap ();
+			SetNeedsDisplay ();
+		}
+	}
 
-				var idxToRender = ScrollOffsetVertical + line;
+	/// <summary>
+	/// Refreshes the state of the object <paramref name="o"/> in the tree. This will
+	/// recompute children, string representation etc.
+	/// </summary>
+	/// <remarks>This has no effect if the object is not exposed in the tree.</remarks>
+	/// <param name="o"></param>
+	/// <param name="startAtTop">
+	/// True to also refresh all ancestors of the objects branch
+	/// (starting with the root). False to refresh only the passed node.
+	/// </param>
+	public void RefreshObject (T o, bool startAtTop = false)
+	{
+		var branch = ObjectToBranch (o);
+		if (branch != null) {
+			branch.Refresh (startAtTop);
+			InvalidateLineMap ();
+			SetNeedsDisplay ();
+		}
 
-				// Is there part of the tree view to render?
-				if (idxToRender < map.Count) {
-					// Render the line
-					map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Bounds.Width);
-				} else {
+	}
 
-					// Else clear the line to prevent stale symbols due to scrolling etc
-					Move (0, line);
-					Driver.SetAttribute (GetNormalColor ());
-					Driver.AddStr (new string (' ', Bounds.Width));
-				}
-			}
+	/// <summary>
+	/// Rebuilds the tree structure for all exposed objects starting with the root objects.
+	/// Call this method when you know there are changes to the tree but don't know which
+	/// objects have changed (otherwise use <see cref="RefreshObject(T, bool)"/>).
+	/// </summary>
+	public void RebuildTree ()
+	{
+		foreach (var branch in roots.Values) {
+			branch.Rebuild ();
 		}
 
-		/// <summary>
-		/// Returns the index of the object <paramref name="o"/> if it is currently exposed (it's 
-		/// parent(s) have been expanded). This can be used with <see cref="ScrollOffsetVertical"/>
-		/// and <see cref="View.SetNeedsDisplay()"/> to scroll to a specific object.
-		/// </summary>
-		/// <remarks>Uses the Equals method and returns the first index at which the object is found
-		/// or -1 if it is not found.</remarks>
-		/// <param name="o">An object that appears in your tree and is currently exposed.</param>
-		/// <returns>The index the object was found at or -1 if it is not currently revealed or
-		/// not in the tree at all.</returns>
-		public int GetScrollOffsetOf (T o)
-		{
-			var map = BuildLineMap ();
-			for (int i = 0; i < map.Count; i++) {
-				if (map.ElementAt (i).Model.Equals (o)) {
-					return i;
-				}
-			}
+		InvalidateLineMap ();
+		SetNeedsDisplay ();
+	}
+
+	/// <summary>
+	/// Returns the currently expanded children of the passed object. Returns an empty
+	/// collection if the branch is not exposed or not expanded.
+	/// </summary>
+	/// <param name="o">An object in the tree.</param>
+	/// <returns></returns>
+	public IEnumerable<T> GetChildren (T o)
+	{
+		var branch = ObjectToBranch (o);
 
-			//object not found
-			return -1;
+		if (branch == null || !branch.IsExpanded) {
+			return new T [0];
 		}
 
-		/// <summary>
-		/// Returns the maximum width line in the tree including prefix and expansion symbols.
-		/// </summary>
-		/// <param name="visible">True to consider only rows currently visible (based on window
-		/// bounds and <see cref="ScrollOffsetVertical"/>. False to calculate the width of 
-		/// every exposed branch in the tree.</param>
-		/// <returns></returns>
-		public int GetContentWidth (bool visible)
-		{
-			var map = BuildLineMap ();
+		return branch.ChildBranches?.Values?.Select (b => b.Model)?.ToArray () ?? new T [0];
+	}
 
-			if (map.Count == 0) {
-				return 0;
-			}
+	/// <summary>
+	/// Returns the parent object of <paramref name="o"/> in the tree. Returns null if
+	/// the object is not exposed in the tree.
+	/// </summary>
+	/// <param name="o">An object in the tree.</param>
+	/// <returns></returns>
+	public T GetParent (T o) => ObjectToBranch (o)?.Parent?.Model;
 
-			if (visible) {
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		if (roots == null) {
+			return;
+		}
 
-				//Somehow we managed to scroll off the end of the control
-				if (ScrollOffsetVertical >= map.Count) {
-					return 0;
-				}
+		if (TreeBuilder == null) {
+			Move (0, 0);
+			Driver.AddStr (NoBuilderError);
+			return;
+		}
 
-				// If control has no height to it then there is no visible area for content
-				if (Bounds.Height == 0) {
-					return 0;
-				}
+		var map = BuildLineMap ();
 
-				return map.Skip (ScrollOffsetVertical).Take (Bounds.Height).Max (b => b.GetWidth (Driver));
+		for (var line = 0; line < Bounds.Height; line++) {
+
+			var idxToRender = ScrollOffsetVertical + line;
+
+			// Is there part of the tree view to render?
+			if (idxToRender < map.Count) {
+				// Render the line
+				map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Bounds.Width);
 			} else {
 
-				return map.Max (b => b.GetWidth (Driver));
+				// Else clear the line to prevent stale symbols due to scrolling etc
+				Move (0, line);
+				Driver.SetAttribute (GetNormalColor ());
+				Driver.AddStr (new string (' ', Bounds.Width));
 			}
 		}
+	}
 
-		/// <summary>
-		/// Calculates all currently visible/expanded branches (including leafs) and outputs them 
-		/// by index from the top of the screen.
-		/// </summary>
-		/// <remarks>Index 0 of the returned array is the first item that should be visible in the
-		/// top of the control, index 1 is the next etc.</remarks>
-		/// <returns></returns>
-		internal IReadOnlyCollection<Branch<T>> BuildLineMap ()
-		{
-			if (cachedLineMap != null) {
-				return cachedLineMap;
-			}
+	/// <summary>
+	/// Returns the index of the object <paramref name="o"/> if it is currently exposed (it's
+	/// parent(s) have been expanded). This can be used with <see cref="ScrollOffsetVertical"/>
+	/// and <see cref="View.SetNeedsDisplay()"/> to scroll to a specific object.
+	/// </summary>
+	/// <remarks>
+	/// Uses the Equals method and returns the first index at which the object is found
+	/// or -1 if it is not found.
+	/// </remarks>
+	/// <param name="o">An object that appears in your tree and is currently exposed.</param>
+	/// <returns>
+	/// The index the object was found at or -1 if it is not currently revealed or
+	/// not in the tree at all.
+	/// </returns>
+	public int GetScrollOffsetOf (T o)
+	{
+		var map = BuildLineMap ();
+		for (var i = 0; i < map.Count; i++) {
+			if (map.ElementAt (i).Model.Equals (o)) {
+				return i;
+			}
+		}
+
+		//object not found
+		return -1;
+	}
+
+	/// <summary>
+	/// Returns the maximum width line in the tree including prefix and expansion symbols.
+	/// </summary>
+	/// <param name="visible">
+	/// True to consider only rows currently visible (based on window
+	/// bounds and <see cref="ScrollOffsetVertical"/>. False to calculate the width of
+	/// every exposed branch in the tree.
+	/// </param>
+	/// <returns></returns>
+	public int GetContentWidth (bool visible)
+	{
+		var map = BuildLineMap ();
 
-			List<Branch<T>> toReturn = new List<Branch<T>> ();
+		if (map.Count == 0) {
+			return 0;
+		}
 
-			foreach (var root in roots.Values) {
+		if (visible) {
 
-				var toAdd = AddToLineMap (root, false, out var isMatch);
-				if (isMatch) {
-					toReturn.AddRange (toAdd);
-				}
+			//Somehow we managed to scroll off the end of the control
+			if (ScrollOffsetVertical >= map.Count) {
+				return 0;
 			}
 
-			cachedLineMap = new ReadOnlyCollection<Branch<T>> (toReturn);
+			// If control has no height to it then there is no visible area for content
+			if (Bounds.Height == 0) {
+				return 0;
+			}
 
-			// Update the collection used for search-typing
-			KeystrokeNavigator.Collection = cachedLineMap.Select (b => AspectGetter (b.Model)).ToArray ();
-			return cachedLineMap;
+			return map.Skip (ScrollOffsetVertical).Take (Bounds.Height).Max (b => b.GetWidth (Driver));
 		}
+		return map.Max (b => b.GetWidth (Driver));
+	}
 
-		private bool IsFilterMatch (Branch<T> branch)
-		{
-			return Filter?.IsMatch (branch.Model) ?? true;
+	/// <summary>
+	/// Calculates all currently visible/expanded branches (including leafs) and outputs them
+	/// by index from the top of the screen.
+	/// </summary>
+	/// <remarks>
+	/// Index 0 of the returned array is the first item that should be visible in the
+	/// top of the control, index 1 is the next etc.
+	/// </remarks>
+	/// <returns></returns>
+	internal IReadOnlyCollection<Branch<T>> BuildLineMap ()
+	{
+		if (cachedLineMap != null) {
+			return cachedLineMap;
 		}
 
-		private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch, bool parentMatches, out bool match)
-		{
-			bool weMatch = IsFilterMatch (currentBranch);
-			bool anyChildMatches = false;
+		var toReturn = new List<Branch<T>> ();
 
-			var toReturn = new List<Branch<T>> ();
-			var children = new List<Branch<T>> ();
+		foreach (var root in roots.Values) {
 
-			if (currentBranch.IsExpanded) {
-				foreach (var subBranch in currentBranch.ChildBranches.Values) {
+			var toAdd = AddToLineMap (root, false, out var isMatch);
+			if (isMatch) {
+				toReturn.AddRange (toAdd);
+			}
+		}
 
-					foreach (var sub in AddToLineMap (subBranch, weMatch, out var childMatch)) {
+		cachedLineMap = new ReadOnlyCollection<Branch<T>> (toReturn);
 
-						if (childMatch) {
-							children.Add (sub);
-							anyChildMatches = true;
-						}
-					}
-				}
-			}
+		// Update the collection used for search-typing
+		KeystrokeNavigator.Collection = cachedLineMap.Select (b => AspectGetter (b.Model)).ToArray ();
+		return cachedLineMap;
+	}
 
-			if (parentMatches || weMatch || anyChildMatches) {
-				match = true;
-				toReturn.Add (currentBranch);
-			} else {
-				match = false;
-			}
+	bool IsFilterMatch (Branch<T> branch) => Filter?.IsMatch (branch.Model) ?? true;
 
-			toReturn.AddRange (children);
-			return toReturn;
-		}
+	IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch, bool parentMatches, out bool match)
+	{
+		var weMatch = IsFilterMatch (currentBranch);
+		var anyChildMatches = false;
 
-		/// <summary>
-		/// Gets the <see cref="CollectionNavigator"/> that searches the <see cref="Objects"/> collection as
-		/// the user types.
-		/// </summary>
-		public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
+		var toReturn = new List<Branch<T>> ();
+		var children = new List<Branch<T>> ();
 
-		/// <inheritdoc/>
-		public override bool OnProcessKeyDown (Key keyEvent)
-		{
-			if (!Enabled) {
-				return false;
-			}
+		if (currentBranch.IsExpanded) {
+			foreach (var subBranch in currentBranch.ChildBranches.Values) {
+
+				foreach (var sub in AddToLineMap (subBranch, weMatch, out var childMatch)) {
 
-			try {
-				// BUGBUG: this should move to OnInvokingKeyBindings
-				// If not a keybinding, is the key a searchable key press?
-				if (CollectionNavigator.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) {
-					IReadOnlyCollection<Branch<T>> map;
-
-					// If there has been a call to InvalidateMap since the last time
-					// we need a new one to reflect the new exposed tree state
-					map = BuildLineMap ();
-
-					// Find the current selected object within the tree
-					var current = map.IndexOf (b => b.Model == SelectedObject);
-					var newIndex = KeystrokeNavigator?.GetNextMatchingItem (current, (char)keyEvent);
-
-					if (newIndex is int && newIndex != -1) {
-						SelectedObject = map.ElementAt ((int)newIndex).Model;
-						EnsureVisible (selectedObject);
-						SetNeedsDisplay ();
-						return true;
+					if (childMatch) {
+						children.Add (sub);
+						anyChildMatches = true;
 					}
 				}
-			} finally {
-				if (IsInitialized) {
-					PositionCursor ();
-				}
 			}
+		}
+
+		if (parentMatches || weMatch || anyChildMatches) {
+			match = true;
+			toReturn.Add (currentBranch);
+		} else {
+			match = false;
+		}
 
+		toReturn.AddRange (children);
+		return toReturn;
+	}
+
+	/// <inheritdoc/>
+	public override bool OnProcessKeyDown (Key keyEvent)
+	{
+		if (!Enabled) {
 			return false;
 		}
 
-		/// <summary>
-		/// <para>Triggers the <see cref="ObjectActivated"/> event with the <see cref="SelectedObject"/>.</para>
-		/// 
-		/// <para>This method also ensures that the selected object is visible.</para>
-		/// </summary>
-		public void ActivateSelectedObjectIfAny ()
-		{
-			var o = SelectedObject;
+		try {
+			// BUGBUG: this should move to OnInvokingKeyBindings
+			// If not a keybinding, is the key a searchable key press?
+			if (CollectionNavigatorBase.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) {
+				IReadOnlyCollection<Branch<T>> map;
+
+				// If there has been a call to InvalidateMap since the last time
+				// we need a new one to reflect the new exposed tree state
+				map = BuildLineMap ();
+
+				// Find the current selected object within the tree
+				var current = map.IndexOf (b => b.Model == SelectedObject);
+				var newIndex = KeystrokeNavigator?.GetNextMatchingItem (current, (char)keyEvent);
 
-			if (o != null) {
-				OnObjectActivated (new ObjectActivatedEventArgs<T> (this, o));
+				if (newIndex is int && newIndex != -1) {
+					SelectedObject = map.ElementAt ((int)newIndex).Model;
+					EnsureVisible (selectedObject);
+					SetNeedsDisplay ();
+					return true;
+				}
+			}
+		} finally {
+			if (IsInitialized) {
 				PositionCursor ();
 			}
 		}
 
-		/// <summary>
-		/// <para>
-		/// Returns the Y coordinate within the <see cref="View.Bounds"/> of the
-		/// tree at which <paramref name="toFind"/> would be displayed or null if
-		/// it is not currently exposed (e.g. its parent is collapsed).
-		/// </para>
-		/// <para>
-		/// Note that the returned value can be negative if the TreeView is scrolled
-		/// down and the <paramref name="toFind"/> object is off the top of the view.
-		/// </para>
-		/// </summary>
-		/// <param name="toFind"></param>
-		/// <returns></returns>
-		public int? GetObjectRow (T toFind)
-		{
-			var idx = BuildLineMap ().IndexOf (o => o.Model.Equals (toFind));
-
-			if (idx == -1)
-				return null;
-
-			return idx - ScrollOffsetVertical;
-		}
-
-		/// <summary>
-		/// <para>Moves the <see cref="SelectedObject"/> to the next item that begins with <paramref name="character"/>.</para>
-		/// <para>This method will loop back to the start of the tree if reaching the end without finding a match.</para>
-		/// </summary>
-		/// <param name="character">The first character of the next item you want selected.</param>
-		/// <param name="caseSensitivity">Case sensitivity of the search.</param>
-		public void AdjustSelectionToNextItemBeginningWith (char character, StringComparison caseSensitivity = StringComparison.CurrentCultureIgnoreCase)
-		{
-			// search for next branch that begins with that letter
-			var characterAsStr = character.ToString ();
-			AdjustSelectionToNext (b => AspectGetter (b.Model).StartsWith (characterAsStr, caseSensitivity));
+		return false;
+	}
+
+	/// <summary>
+	///         <para>Triggers the <see cref="ObjectActivated"/> event with the <see cref="SelectedObject"/>.</para>
+	/// 
+	///         <para>This method also ensures that the selected object is visible.</para>
+	/// </summary>
+	public void ActivateSelectedObjectIfAny ()
+	{
+		var o = SelectedObject;
 
+		if (o != null) {
+			OnObjectActivated (new ObjectActivatedEventArgs<T> (this, o));
 			PositionCursor ();
 		}
+	}
 
-		/// <summary>
-		/// Moves the selection up by the height of the control (1 page).
-		/// </summary>
-		/// <param name="expandSelection">True if the navigation should add the covered nodes to the selected current selection.</param>
-		/// <exception cref="NotImplementedException"></exception>
-		public void MovePageUp (bool expandSelection = false)
-		{
-			AdjustSelection (-Bounds.Height, expandSelection);
-		}
-
-		/// <summary>
-		/// Moves the selection down by the height of the control (1 page).
-		/// </summary>
-		/// <param name="expandSelection">True if the navigation should add the covered nodes to the selected current selection.</param>
-		/// <exception cref="NotImplementedException"></exception>
-		public void MovePageDown (bool expandSelection = false)
-		{
-			AdjustSelection (Bounds.Height, expandSelection);
-		}
-
-		/// <summary>
-		/// Scrolls the view area down a single line without changing the current selection.
-		/// </summary>
-		public void ScrollDown ()
-		{
-			if (ScrollOffsetVertical <= ContentHeight - 2) {
-				ScrollOffsetVertical++;
-				SetNeedsDisplay ();
-			}
+	/// <summary>
+	///         <para>
+	///         Returns the Y coordinate within the <see cref="View.Bounds"/> of the
+	///         tree at which <paramref name="toFind"/> would be displayed or null if
+	///         it is not currently exposed (e.g. its parent is collapsed).
+	///         </para>
+	///         <para>
+	///         Note that the returned value can be negative if the TreeView is scrolled
+	///         down and the <paramref name="toFind"/> object is off the top of the view.
+	///         </para>
+	/// </summary>
+	/// <param name="toFind"></param>
+	/// <returns></returns>
+	public int? GetObjectRow (T toFind)
+	{
+		var idx = BuildLineMap ().IndexOf (o => o.Model.Equals (toFind));
+
+		if (idx == -1) {
+			return null;
 		}
 
-		/// <summary>
-		/// Scrolls the view area up a single line without changing the current selection.
-		/// </summary>
-		public void ScrollUp ()
-		{
-			if (scrollOffsetVertical > 0) {
-				ScrollOffsetVertical--;
-				SetNeedsDisplay ();
-			}
+		return idx - ScrollOffsetVertical;
+	}
+
+	/// <summary>
+	///         <para>Moves the <see cref="SelectedObject"/> to the next item that begins with <paramref name="character"/>.</para>
+	///         <para>This method will loop back to the start of the tree if reaching the end without finding a match.</para>
+	/// </summary>
+	/// <param name="character">The first character of the next item you want selected.</param>
+	/// <param name="caseSensitivity">Case sensitivity of the search.</param>
+	public void AdjustSelectionToNextItemBeginningWith (char character, StringComparison caseSensitivity = StringComparison.CurrentCultureIgnoreCase)
+	{
+		// search for next branch that begins with that letter
+		var characterAsStr = character.ToString ();
+		AdjustSelectionToNext (b => AspectGetter (b.Model).StartsWith (characterAsStr, caseSensitivity));
+
+		PositionCursor ();
+	}
+
+	/// <summary>
+	/// Moves the selection up by the height of the control (1 page).
+	/// </summary>
+	/// <param name="expandSelection">True if the navigation should add the covered nodes to the selected current selection.</param>
+	/// <exception cref="NotImplementedException"></exception>
+	public void MovePageUp (bool expandSelection = false) => AdjustSelection (-Bounds.Height, expandSelection);
+
+	/// <summary>
+	/// Moves the selection down by the height of the control (1 page).
+	/// </summary>
+	/// <param name="expandSelection">True if the navigation should add the covered nodes to the selected current selection.</param>
+	/// <exception cref="NotImplementedException"></exception>
+	public void MovePageDown (bool expandSelection = false) => AdjustSelection (Bounds.Height, expandSelection);
+
+	/// <summary>
+	/// Scrolls the view area down a single line without changing the current selection.
+	/// </summary>
+	public void ScrollDown ()
+	{
+		if (ScrollOffsetVertical <= ContentHeight - 2) {
+			ScrollOffsetVertical++;
+			SetNeedsDisplay ();
 		}
+	}
 
-		/// <summary>
-		/// Raises the <see cref="ObjectActivated"/> event.
-		/// </summary>
-		/// <param name="e"></param>
-		protected virtual void OnObjectActivated (ObjectActivatedEventArgs<T> e)
-		{
-			ObjectActivated?.Invoke (this, e);
-		}
-
-		/// <summary>
-		/// Returns the object in the tree list that is currently visible.
-		/// at the provided row. Returns null if no object is at that location.
-		/// <remarks>
-		/// </remarks>
-		/// If you have screen coordinates then use <see cref="View.ScreenToFrame"/>
-		/// to translate these into the client area of the <see cref="TreeView{T}"/>.
-		/// </summary>
-		/// <param name="row">The row of the <see cref="View.Bounds"/> of the <see cref="TreeView{T}"/>.</param>
-		/// <returns>The object currently displayed on this row or null.</returns>
-		public T GetObjectOnRow (int row)
-		{
-			return HitTest (row)?.Model;
-		}
-
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			// If it is not an event we care about
-			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
-				!me.Flags.HasFlag (ObjectActivationButton ?? MouseFlags.Button1DoubleClicked) &&
-				!me.Flags.HasFlag (MouseFlags.WheeledDown) &&
-				!me.Flags.HasFlag (MouseFlags.WheeledUp) &&
-				!me.Flags.HasFlag (MouseFlags.WheeledRight) &&
-				!me.Flags.HasFlag (MouseFlags.WheeledLeft)) {
-
-				// do nothing
-				return false;
-			}
+	/// <summary>
+	/// Scrolls the view area up a single line without changing the current selection.
+	/// </summary>
+	public void ScrollUp ()
+	{
+		if (scrollOffsetVertical > 0) {
+			ScrollOffsetVertical--;
+			SetNeedsDisplay ();
+		}
+	}
 
-			if (!HasFocus && CanFocus) {
-				SetFocus ();
-			}
+	/// <summary>
+	/// Raises the <see cref="ObjectActivated"/> event.
+	/// </summary>
+	/// <param name="e"></param>
+	protected virtual void OnObjectActivated (ObjectActivatedEventArgs<T> e) => ObjectActivated?.Invoke (this, e);
 
-			if (me.Flags == MouseFlags.WheeledDown) {
+	/// <summary>
+	/// Returns the object in the tree list that is currently visible.
+	/// at the provided row. Returns null if no object is at that location.
+	/// <remarks>
+	/// </remarks>
+	/// If you have screen coordinates then use <see cref="View.ScreenToFrame"/>
+	/// to translate these into the client area of the <see cref="TreeView{T}"/>.
+	/// </summary>
+	/// <param name="row">The row of the <see cref="View.Bounds"/> of the <see cref="TreeView{T}"/>.</param>
+	/// <returns>The object currently displayed on this row or null.</returns>
+	public T GetObjectOnRow (int row) => HitTest (row)?.Model;
+
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		// If it is not an event we care about
+		if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
+		    !me.Flags.HasFlag (ObjectActivationButton ?? MouseFlags.Button1DoubleClicked) &&
+		    !me.Flags.HasFlag (MouseFlags.WheeledDown) &&
+		    !me.Flags.HasFlag (MouseFlags.WheeledUp) &&
+		    !me.Flags.HasFlag (MouseFlags.WheeledRight) &&
+		    !me.Flags.HasFlag (MouseFlags.WheeledLeft)) {
+
+			// do nothing
+			return false;
+		}
 
-				ScrollDown ();
+		if (!HasFocus && CanFocus) {
+			SetFocus ();
+		}
 
-				return true;
-			} else if (me.Flags == MouseFlags.WheeledUp) {
-				ScrollUp ();
+		if (me.Flags == MouseFlags.WheeledDown) {
 
-				return true;
-			}
+			ScrollDown ();
 
-			if (me.Flags == MouseFlags.WheeledRight) {
+			return true;
+		}
+		if (me.Flags == MouseFlags.WheeledUp) {
+			ScrollUp ();
 
-				ScrollOffsetHorizontal++;
-				SetNeedsDisplay ();
+			return true;
+		}
 
-				return true;
-			} else if (me.Flags == MouseFlags.WheeledLeft) {
-				ScrollOffsetHorizontal--;
-				SetNeedsDisplay ();
+		if (me.Flags == MouseFlags.WheeledRight) {
 
-				return true;
-			}
+			ScrollOffsetHorizontal++;
+			SetNeedsDisplay ();
+
+			return true;
+		}
+		if (me.Flags == MouseFlags.WheeledLeft) {
+			ScrollOffsetHorizontal--;
+			SetNeedsDisplay ();
 
-			if (me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+			return true;
+		}
 
-				// The line they clicked on a branch
-				var clickedBranch = HitTest (me.Y);
+		if (me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
 
-				if (clickedBranch == null) {
-					return false;
-				}
+			// The line they clicked on a branch
+			var clickedBranch = HitTest (me.Y);
 
-				bool isExpandToggleAttempt = clickedBranch.IsHitOnExpandableSymbol (Driver, me.X);
+			if (clickedBranch == null) {
+				return false;
+			}
 
-				// If we are already selected (double click)
-				if (Equals (SelectedObject, clickedBranch.Model)) {
-					isExpandToggleAttempt = true;
-				}
+			var isExpandToggleAttempt = clickedBranch.IsHitOnExpandableSymbol (Driver, me.X);
 
-				// if they clicked on the +/- expansion symbol
-				if (isExpandToggleAttempt) {
+			// If we are already selected (double click)
+			if (Equals (SelectedObject, clickedBranch.Model)) {
+				isExpandToggleAttempt = true;
+			}
 
-					if (clickedBranch.IsExpanded) {
-						clickedBranch.Collapse ();
-						InvalidateLineMap ();
-					} else
-					if (clickedBranch.CanExpand ()) {
-						clickedBranch.Expand ();
-						InvalidateLineMap ();
-					} else {
-						SelectedObject = clickedBranch.Model; // It is a leaf node
-						multiSelectedRegions.Clear ();
-					}
+			// if they clicked on the +/- expansion symbol
+			if (isExpandToggleAttempt) {
+
+				if (clickedBranch.IsExpanded) {
+					clickedBranch.Collapse ();
+					InvalidateLineMap ();
+				} else if (clickedBranch.CanExpand ()) {
+					clickedBranch.Expand ();
+					InvalidateLineMap ();
 				} else {
-					// It is a first click somewhere in the current line that doesn't look like an expansion/collapse attempt
-					SelectedObject = clickedBranch.Model;
+					SelectedObject = clickedBranch.Model; // It is a leaf node
 					multiSelectedRegions.Clear ();
 				}
-
-				SetNeedsDisplay ();
-				return true;
+			} else {
+				// It is a first click somewhere in the current line that doesn't look like an expansion/collapse attempt
+				SelectedObject = clickedBranch.Model;
+				multiSelectedRegions.Clear ();
 			}
 
-			// If it is activation via mouse (e.g. double click)
-			if (ObjectActivationButton.HasValue && me.Flags.HasFlag (ObjectActivationButton.Value)) {
-				// The line they clicked on a branch
-				var clickedBranch = HitTest (me.Y);
+			SetNeedsDisplay ();
+			return true;
+		}
+
+		// If it is activation via mouse (e.g. double click)
+		if (ObjectActivationButton.HasValue && me.Flags.HasFlag (ObjectActivationButton.Value)) {
+			// The line they clicked on a branch
+			var clickedBranch = HitTest (me.Y);
 
-				if (clickedBranch == null) {
-					return false;
-				}
+			if (clickedBranch == null) {
+				return false;
+			}
 
-				// Double click changes the selection to the clicked node as well as triggering
-				// activation otherwise it feels wierd
-				SelectedObject = clickedBranch.Model;
-				SetNeedsDisplay ();
+			// Double click changes the selection to the clicked node as well as triggering
+			// activation otherwise it feels wierd
+			SelectedObject = clickedBranch.Model;
+			SetNeedsDisplay ();
 
-				// trigger activation event				
-				OnObjectActivated (new ObjectActivatedEventArgs<T> (this, clickedBranch.Model));
+			// trigger activation event				
+			OnObjectActivated (new ObjectActivatedEventArgs<T> (this, clickedBranch.Model));
 
-				// mouse event is handled.
-				return true;
-			}
-			return false;
+			// mouse event is handled.
+			return true;
 		}
+		return false;
+	}
 
-		/// <summary>
-		/// Returns the branch at the given <paramref name="y"/> client
-		/// coordinate e.g. following a click event.
-		/// </summary>
-		/// <param name="y">Client Y position in the controls bounds.</param>
-		/// <returns>The clicked branch or null if outside of tree region.</returns>
-		private Branch<T> HitTest (int y)
-		{
-			var map = BuildLineMap ();
-
-			var idx = y + ScrollOffsetVertical;
+	/// <summary>
+	/// Returns the branch at the given <paramref name="y"/> client
+	/// coordinate e.g. following a click event.
+	/// </summary>
+	/// <param name="y">Client Y position in the controls bounds.</param>
+	/// <returns>The clicked branch or null if outside of tree region.</returns>
+	Branch<T> HitTest (int y)
+	{
+		var map = BuildLineMap ();
 
-			// click is outside any visible nodes
-			if (idx < 0 || idx >= map.Count) {
-				return null;
-			}
+		var idx = y + ScrollOffsetVertical;
 
-			// The line they clicked on
-			return map.ElementAt (idx);
+		// click is outside any visible nodes
+		if (idx < 0 || idx >= map.Count) {
+			return null;
 		}
 
-		/// <summary>
-		/// Positions the cursor at the start of the selected objects line (if visible).
-		/// </summary>
-		public override void PositionCursor ()
-		{
-			if (CanFocus && HasFocus && Visible && SelectedObject != null) {
+		// The line they clicked on
+		return map.ElementAt (idx);
+	}
 
-				var map = BuildLineMap ();
-				var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
+	/// <summary>
+	/// Positions the cursor at the start of the selected objects line (if visible).
+	/// </summary>
+	public override void PositionCursor ()
+	{
+		if (CanFocus && HasFocus && Visible && SelectedObject != null) {
 
-				// if currently selected line is visible
-				if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Bounds.Height) {
-					Move (0, idx - ScrollOffsetVertical);
-				} else {
-					base.PositionCursor ();
-				}
+			var map = BuildLineMap ();
+			var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
 
+			// if currently selected line is visible
+			if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Bounds.Height) {
+				Move (0, idx - ScrollOffsetVertical);
 			} else {
 				base.PositionCursor ();
 			}
+
+		} else {
+			base.PositionCursor ();
 		}
+	}
 
-		/// <summary>
-		/// Determines systems behaviour when the left arrow key is pressed. Default behaviour is
-		/// to collapse the current tree node if possible otherwise changes selection to current 
-		/// branches parent.
-		/// </summary>
-		protected virtual void CursorLeft (bool ctrl)
-		{
-			if (IsExpanded (SelectedObject)) {
+	/// <summary>
+	/// Determines systems behaviour when the left arrow key is pressed. Default behaviour is
+	/// to collapse the current tree node if possible otherwise changes selection to current
+	/// branches parent.
+	/// </summary>
+	protected virtual void CursorLeft (bool ctrl)
+	{
+		if (IsExpanded (SelectedObject)) {
 
-				if (ctrl) {
-					CollapseAll (SelectedObject);
-				} else {
-					Collapse (SelectedObject);
-				}
+			if (ctrl) {
+				CollapseAll (SelectedObject);
 			} else {
-				var parent = GetParent (SelectedObject);
+				Collapse (SelectedObject);
+			}
+		} else {
+			var parent = GetParent (SelectedObject);
 
-				if (parent != null) {
-					SelectedObject = parent;
-					AdjustSelection (0);
-					SetNeedsDisplay ();
-				}
+			if (parent != null) {
+				SelectedObject = parent;
+				AdjustSelection (0);
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Changes the <see cref="SelectedObject"/> to the first root object and resets 
-		/// the <see cref="ScrollOffsetVertical"/> to 0.
-		/// </summary>
-		public void GoToFirst ()
-		{
-			ScrollOffsetVertical = 0;
-			SelectedObject = roots.Keys.FirstOrDefault ();
+	/// <summary>
+	/// Changes the <see cref="SelectedObject"/> to the first root object and resets
+	/// the <see cref="ScrollOffsetVertical"/> to 0.
+	/// </summary>
+	public void GoToFirst ()
+	{
+		ScrollOffsetVertical = 0;
+		SelectedObject = roots.Keys.FirstOrDefault ();
 
-			SetNeedsDisplay ();
-		}
+		SetNeedsDisplay ();
+	}
 
-		/// <summary>
-		/// Changes the <see cref="SelectedObject"/> to the last object in the tree and scrolls so
-		/// that it is visible.
-		/// </summary>
-		public void GoToEnd ()
-		{
-			var map = BuildLineMap ();
-			ScrollOffsetVertical = Math.Max (0, map.Count - Bounds.Height + 1);
-			SelectedObject = map.LastOrDefault ()?.Model;
+	/// <summary>
+	/// Changes the <see cref="SelectedObject"/> to the last object in the tree and scrolls so
+	/// that it is visible.
+	/// </summary>
+	public void GoToEnd ()
+	{
+		var map = BuildLineMap ();
+		ScrollOffsetVertical = Math.Max (0, map.Count - Bounds.Height + 1);
+		SelectedObject = map.LastOrDefault ()?.Model;
 
-			SetNeedsDisplay ();
+		SetNeedsDisplay ();
+	}
+
+	/// <summary>
+	/// Changes the <see cref="SelectedObject"/> to <paramref name="toSelect"/> and scrolls to ensure
+	/// it is visible. Has no effect if <paramref name="toSelect"/> is not exposed in the tree (e.g.
+	/// its parents are collapsed).
+	/// </summary>
+	/// <param name="toSelect"></param>
+	public void GoTo (T toSelect)
+	{
+		if (ObjectToBranch (toSelect) == null) {
+			return;
 		}
 
-		/// <summary>
-		/// Changes the <see cref="SelectedObject"/> to <paramref name="toSelect"/> and scrolls to ensure
-		/// it is visible. Has no effect if <paramref name="toSelect"/> is not exposed in the tree (e.g. 
-		/// its parents are collapsed).
-		/// </summary>
-		/// <param name="toSelect"></param>
-		public void GoTo (T toSelect)
-		{
-			if (ObjectToBranch (toSelect) == null) {
-				return;
-			}
+		SelectedObject = toSelect;
+		EnsureVisible (toSelect);
+		SetNeedsDisplay ();
+	}
 
-			SelectedObject = toSelect;
-			EnsureVisible (toSelect);
-			SetNeedsDisplay ();
+	/// <summary>
+	/// The number of screen lines to move the currently selected object by. Supports negative values.
+	/// <paramref name="offset"/>. Each branch occupies 1 line on screen.
+	/// </summary>
+	/// <remarks>
+	/// If nothing is currently selected or the selected object is no longer in the tree
+	/// then the first object in the tree is selected instead.
+	/// </remarks>
+	/// <param name="offset">Positive to move the selection down the screen, negative to move it up</param>
+	/// <param name="expandSelection">
+	/// True to expand the selection (assuming
+	/// <see cref="MultiSelect"/> is enabled). False to replace.
+	/// </param>
+	public void AdjustSelection (int offset, bool expandSelection = false)
+	{
+		// if it is not a shift click or we don't allow multi select
+		if (!expandSelection || !MultiSelect) {
+			multiSelectedRegions.Clear ();
 		}
 
-		/// <summary>
-		/// The number of screen lines to move the currently selected object by. Supports negative values.
-		/// <paramref name="offset"/>. Each branch occupies 1 line on screen.
-		/// </summary>
-		/// <remarks>If nothing is currently selected or the selected object is no longer in the tree
-		/// then the first object in the tree is selected instead.</remarks>
-		/// <param name="offset">Positive to move the selection down the screen, negative to move it up</param>
-		/// <param name="expandSelection">True to expand the selection (assuming 
-		/// <see cref="MultiSelect"/> is enabled). False to replace.</param>
-		public void AdjustSelection (int offset, bool expandSelection = false)
-		{
-			// if it is not a shift click or we don't allow multi select
-			if (!expandSelection || !MultiSelect) {
-				multiSelectedRegions.Clear ();
-			}
+		if (SelectedObject == null) {
+			SelectedObject = roots.Keys.FirstOrDefault ();
+		} else {
+			var map = BuildLineMap ();
 
-			if (SelectedObject == null) {
+			var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
+
+			if (idx == -1) {
+				// The current selection has disapeared!
 				SelectedObject = roots.Keys.FirstOrDefault ();
 			} else {
-				var map = BuildLineMap ();
+				var newIdx = Math.Min (Math.Max (0, idx + offset), map.Count - 1);
 
-				var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
+				var newBranch = map.ElementAt (newIdx);
 
-				if (idx == -1) {
-					// The current selection has disapeared!
-					SelectedObject = roots.Keys.FirstOrDefault ();
-				} else {
-					var newIdx = Math.Min (Math.Max (0, idx + offset), map.Count - 1);
-
-					var newBranch = map.ElementAt (newIdx);
-
-					// If it is a multi selection
-					if (expandSelection && MultiSelect) {
-						if (multiSelectedRegions.Any ()) {
-							// expand the existing head selection
-							var head = multiSelectedRegions.Pop ();
-							multiSelectedRegions.Push (new TreeSelection<T> (head.Origin, newIdx, map));
-						} else {
-							// or start a new multi selection region
-							multiSelectedRegions.Push (new TreeSelection<T> (map.ElementAt (idx), newIdx, map));
-						}
+				// If it is a multi selection
+				if (expandSelection && MultiSelect) {
+					if (multiSelectedRegions.Any ()) {
+						// expand the existing head selection
+						var head = multiSelectedRegions.Pop ();
+						multiSelectedRegions.Push (new TreeSelection<T> (head.Origin, newIdx, map));
+					} else {
+						// or start a new multi selection region
+						multiSelectedRegions.Push (new TreeSelection<T> (map.ElementAt (idx), newIdx, map));
 					}
+				}
 
-					SelectedObject = newBranch.Model;
+				SelectedObject = newBranch.Model;
 
-					EnsureVisible (SelectedObject);
-				}
+				EnsureVisible (SelectedObject);
 			}
-			SetNeedsDisplay ();
 		}
+		SetNeedsDisplay ();
+	}
 
-		/// <summary>
-		/// Moves the selection to the first child in the currently selected level.
-		/// </summary>
-		public void AdjustSelectionToBranchStart ()
-		{
-			var o = SelectedObject;
-			if (o == null) {
-				return;
-			}
-
-			var map = BuildLineMap ();
+	/// <summary>
+	/// Moves the selection to the first child in the currently selected level.
+	/// </summary>
+	public void AdjustSelectionToBranchStart ()
+	{
+		var o = SelectedObject;
+		if (o == null) {
+			return;
+		}
 
-			int currentIdx = map.IndexOf (b => Equals (b.Model, o));
+		var map = BuildLineMap ();
 
-			if (currentIdx == -1) {
-				return;
-			}
+		var currentIdx = map.IndexOf (b => Equals (b.Model, o));
 
-			var currentBranch = map.ElementAt (currentIdx);
-			var next = currentBranch;
+		if (currentIdx == -1) {
+			return;
+		}
 
-			for (; currentIdx >= 0; currentIdx--) {
-				//if it is the beginning of the current depth of branch
-				if (currentBranch.Depth != next.Depth) {
+		var currentBranch = map.ElementAt (currentIdx);
+		var next = currentBranch;
 
-					SelectedObject = currentBranch.Model;
-					EnsureVisible (currentBranch.Model);
-					SetNeedsDisplay ();
-					return;
-				}
+		for (; currentIdx >= 0; currentIdx--) {
+			//if it is the beginning of the current depth of branch
+			if (currentBranch.Depth != next.Depth) {
 
-				// look at next branch up for consideration
-				currentBranch = next;
-				next = map.ElementAt (currentIdx);
+				SelectedObject = currentBranch.Model;
+				EnsureVisible (currentBranch.Model);
+				SetNeedsDisplay ();
+				return;
 			}
 
-			// We ran all the way to top of tree
-			GoToFirst ();
+			// look at next branch up for consideration
+			currentBranch = next;
+			next = map.ElementAt (currentIdx);
 		}
 
-		/// <summary>
-		/// Moves the selection to the last child in the currently selected level.
-		/// </summary>
-		public void AdjustSelectionToBranchEnd ()
-		{
-			var o = SelectedObject;
-			if (o == null) {
-				return;
-			}
+		// We ran all the way to top of tree
+		GoToFirst ();
+	}
 
-			var map = BuildLineMap ();
+	/// <summary>
+	/// Moves the selection to the last child in the currently selected level.
+	/// </summary>
+	public void AdjustSelectionToBranchEnd ()
+	{
+		var o = SelectedObject;
+		if (o == null) {
+			return;
+		}
 
-			int currentIdx = map.IndexOf (b => Equals (b.Model, o));
+		var map = BuildLineMap ();
 
-			if (currentIdx == -1) {
-				return;
-			}
+		var currentIdx = map.IndexOf (b => Equals (b.Model, o));
 
-			var currentBranch = map.ElementAt (currentIdx);
-			var next = currentBranch;
+		if (currentIdx == -1) {
+			return;
+		}
 
-			for (; currentIdx < map.Count; currentIdx++) {
-				//if it is the end of the current depth of branch
-				if (currentBranch.Depth != next.Depth) {
+		var currentBranch = map.ElementAt (currentIdx);
+		var next = currentBranch;
 
-					SelectedObject = currentBranch.Model;
-					EnsureVisible (currentBranch.Model);
-					SetNeedsDisplay ();
-					return;
-				}
+		for (; currentIdx < map.Count; currentIdx++) {
+			//if it is the end of the current depth of branch
+			if (currentBranch.Depth != next.Depth) {
 
-				// look at next branch for consideration
-				currentBranch = next;
-				next = map.ElementAt (currentIdx);
+				SelectedObject = currentBranch.Model;
+				EnsureVisible (currentBranch.Model);
+				SetNeedsDisplay ();
+				return;
 			}
-			GoToEnd ();
+
+			// look at next branch for consideration
+			currentBranch = next;
+			next = map.ElementAt (currentIdx);
 		}
+		GoToEnd ();
+	}
 
-		/// <summary>
-		/// Sets the selection to the next branch that matches the <paramref name="predicate"/>.
-		/// </summary>
-		/// <param name="predicate"></param>
-		private void AdjustSelectionToNext (Func<Branch<T>, bool> predicate)
-		{
-			var map = BuildLineMap ();
+	/// <summary>
+	/// Sets the selection to the next branch that matches the <paramref name="predicate"/>.
+	/// </summary>
+	/// <param name="predicate"></param>
+	void AdjustSelectionToNext (Func<Branch<T>, bool> predicate)
+	{
+		var map = BuildLineMap ();
 
-			// empty map means we can't select anything anyway
-			if (map.Count == 0) {
-				return;
-			}
+		// empty map means we can't select anything anyway
+		if (map.Count == 0) {
+			return;
+		}
 
-			// Start searching from the first element in the map
-			var idxStart = 0;
+		// Start searching from the first element in the map
+		var idxStart = 0;
 
-			// or the current selected branch
-			if (SelectedObject != null) {
-				idxStart = map.IndexOf (b => Equals (b.Model, SelectedObject));
-			}
+		// or the current selected branch
+		if (SelectedObject != null) {
+			idxStart = map.IndexOf (b => Equals (b.Model, SelectedObject));
+		}
 
-			// if currently selected object mysteriously vanished, search from beginning
-			if (idxStart == -1) {
-				idxStart = 0;
-			}
+		// if currently selected object mysteriously vanished, search from beginning
+		if (idxStart == -1) {
+			idxStart = 0;
+		}
 
-			// loop around all indexes and back to first index
-			for (int idxCur = (idxStart + 1) % map.Count; idxCur != idxStart; idxCur = (idxCur + 1) % map.Count) {
-				if (predicate (map.ElementAt (idxCur))) {
-					SelectedObject = map.ElementAt (idxCur).Model;
-					EnsureVisible (map.ElementAt (idxCur).Model);
-					SetNeedsDisplay ();
-					return;
-				}
+		// loop around all indexes and back to first index
+		for (var idxCur = (idxStart + 1) % map.Count; idxCur != idxStart; idxCur = (idxCur + 1) % map.Count) {
+			if (predicate (map.ElementAt (idxCur))) {
+				SelectedObject = map.ElementAt (idxCur).Model;
+				EnsureVisible (map.ElementAt (idxCur).Model);
+				SetNeedsDisplay ();
+				return;
 			}
 		}
+	}
 
-		/// <summary>
-		/// Adjusts the <see cref="ScrollOffsetVertical"/> to ensure the given
-		/// <paramref name="model"/> is visible. Has no effect if already visible.
-		/// </summary>
-		public void EnsureVisible (T model)
-		{
-			var map = BuildLineMap ();
+	/// <summary>
+	/// Adjusts the <see cref="ScrollOffsetVertical"/> to ensure the given
+	/// <paramref name="model"/> is visible. Has no effect if already visible.
+	/// </summary>
+	public void EnsureVisible (T model)
+	{
+		var map = BuildLineMap ();
 
-			var idx = map.IndexOf (b => Equals (b.Model, model));
+		var idx = map.IndexOf (b => Equals (b.Model, model));
 
-			if (idx == -1) {
-				return;
-			}
+		if (idx == -1) {
+			return;
+		}
 
-			/*this -1 allows for possible horizontal scroll bar in the last row of the control*/
-			int leaveSpace = Style.LeaveLastRow ? 1 : 0;
+		/*this -1 allows for possible horizontal scroll bar in the last row of the control*/
+		var leaveSpace = Style.LeaveLastRow ? 1 : 0;
 
-			if (idx < ScrollOffsetVertical) {
-				//if user has scrolled up too far to see their selection
-				ScrollOffsetVertical = idx;
-			} else if (idx >= ScrollOffsetVertical + Bounds.Height - leaveSpace) {
+		if (idx < ScrollOffsetVertical) {
+			//if user has scrolled up too far to see their selection
+			ScrollOffsetVertical = idx;
+		} else if (idx >= ScrollOffsetVertical + Bounds.Height - leaveSpace) {
 
-				//if user has scrolled off bottom of visible tree
-				ScrollOffsetVertical = Math.Max (0, (idx + 1) - (Bounds.Height - leaveSpace));
-			}
+			//if user has scrolled off bottom of visible tree
+			ScrollOffsetVertical = Math.Max (0, idx + 1 - (Bounds.Height - leaveSpace));
 		}
+	}
+
+	/// <summary>
+	/// Expands the currently <see cref="SelectedObject"/>.
+	/// </summary>
+	public void Expand () => Expand (SelectedObject);
 
-		/// <summary>
-		/// Expands the currently <see cref="SelectedObject"/>.
-		/// </summary>
-		public void Expand ()
-		{
-			Expand (SelectedObject);
+	/// <summary>
+	/// Expands the supplied object if it is contained in the tree (either as a root object or
+	/// as an exposed branch object).
+	/// </summary>
+	/// <param name="toExpand">The object to expand.</param>
+	public void Expand (T toExpand)
+	{
+		if (toExpand == null) {
+			return;
 		}
 
-		/// <summary>
-		/// Expands the supplied object if it is contained in the tree (either as a root object or 
-		/// as an exposed branch object).
-		/// </summary>
-		/// <param name="toExpand">The object to expand.</param>
-		public void Expand (T toExpand)
-		{
-			if (toExpand == null) {
-				return;
-			}
+		ObjectToBranch (toExpand)?.Expand ();
+		InvalidateLineMap ();
+		SetNeedsDisplay ();
+	}
 
-			ObjectToBranch (toExpand)?.Expand ();
-			InvalidateLineMap ();
-			SetNeedsDisplay ();
+	/// <summary>
+	/// Expands the supplied object and all child objects.
+	/// </summary>
+	/// <param name="toExpand">The object to expand.</param>
+	public void ExpandAll (T toExpand)
+	{
+		if (toExpand == null) {
+			return;
 		}
 
-		/// <summary>
-		/// Expands the supplied object and all child objects.
-		/// </summary>
-		/// <param name="toExpand">The object to expand.</param>
-		public void ExpandAll (T toExpand)
-		{
-			if (toExpand == null) {
-				return;
-			}
+		ObjectToBranch (toExpand)?.ExpandAll ();
+		InvalidateLineMap ();
+		SetNeedsDisplay ();
+	}
 
-			ObjectToBranch (toExpand)?.ExpandAll ();
-			InvalidateLineMap ();
-			SetNeedsDisplay ();
+	/// <summary>
+	/// Fully expands all nodes in the tree, if the tree is very big and built dynamically this
+	/// may take a while (e.g. for file system).
+	/// </summary>
+	public void ExpandAll ()
+	{
+		foreach (var item in roots) {
+			item.Value.ExpandAll ();
 		}
-		/// <summary>
-		/// Fully expands all nodes in the tree, if the tree is very big and built dynamically this
-		/// may take a while (e.g. for file system).
-		/// </summary>
-		public void ExpandAll ()
-		{
-			foreach (var item in roots) {
-				item.Value.ExpandAll ();
-			}
 
-			InvalidateLineMap ();
-			SetNeedsDisplay ();
-		}
-		/// <summary>
-		/// Returns true if the given object <paramref name="o"/> is exposed in the tree and can be
-		/// expanded otherwise false.
-		/// </summary>
-		/// <param name="o"></param>
-		/// <returns></returns>
-		public bool CanExpand (T o)
-		{
-			return ObjectToBranch (o)?.CanExpand () ?? false;
-		}
-
-		/// <summary>
-		/// Returns true if the given object <paramref name="o"/> is exposed in the tree and 
-		/// expanded otherwise false.
-		/// </summary>
-		/// <param name="o"></param>
-		/// <returns></returns>
-		public bool IsExpanded (T o)
-		{
-			return ObjectToBranch (o)?.IsExpanded ?? false;
-		}
-
-		/// <summary>
-		/// Collapses the <see cref="SelectedObject"/>
-		/// </summary>
-		public void Collapse ()
-		{
-			Collapse (selectedObject);
-		}
-
-		/// <summary>
-		/// Collapses the supplied object if it is currently expanded .
-		/// </summary>
-		/// <param name="toCollapse">The object to collapse.</param>
-		public void Collapse (T toCollapse)
-		{
-			CollapseImpl (toCollapse, false);
-		}
-
-		/// <summary>
-		/// Collapses the supplied object if it is currently expanded. Also collapses all children
-		/// branches (this will only become apparent when/if the user expands it again).
-		/// </summary>
-		/// <param name="toCollapse">The object to collapse.</param>
-		public void CollapseAll (T toCollapse)
-		{
-			CollapseImpl (toCollapse, true);
-		}
-
-		/// <summary>
-		/// Collapses all root nodes in the tree.
-		/// </summary>
-		public void CollapseAll ()
-		{
-			foreach (var item in roots) {
-				item.Value.Collapse ();
-			}
+		InvalidateLineMap ();
+		SetNeedsDisplay ();
+	}
 
-			InvalidateLineMap ();
-			SetNeedsDisplay ();
-		}
+	/// <summary>
+	/// Returns true if the given object <paramref name="o"/> is exposed in the tree and can be
+	/// expanded otherwise false.
+	/// </summary>
+	/// <param name="o"></param>
+	/// <returns></returns>
+	public bool CanExpand (T o) => ObjectToBranch (o)?.CanExpand () ?? false;
 
-		/// <summary>
-		/// Implementation of <see cref="Collapse(T)"/> and <see cref="CollapseAll(T)"/>. Performs
-		/// operation and updates selection if disapeared.
-		/// </summary>
-		/// <param name="toCollapse"></param>
-		/// <param name="all"></param>
-		protected void CollapseImpl (T toCollapse, bool all)
-		{
-			if (toCollapse == null) {
-				return;
-			}
+	/// <summary>
+	/// Returns true if the given object <paramref name="o"/> is exposed in the tree and
+	/// expanded otherwise false.
+	/// </summary>
+	/// <param name="o"></param>
+	/// <returns></returns>
+	public bool IsExpanded (T o) => ObjectToBranch (o)?.IsExpanded ?? false;
 
-			var branch = ObjectToBranch (toCollapse);
+	/// <summary>
+	/// Collapses the <see cref="SelectedObject"/>
+	/// </summary>
+	public void Collapse () => Collapse (selectedObject);
 
-			// Nothing to collapse
-			if (branch == null) {
-				return;
-			}
+	/// <summary>
+	/// Collapses the supplied object if it is currently expanded .
+	/// </summary>
+	/// <param name="toCollapse">The object to collapse.</param>
+	public void Collapse (T toCollapse) => CollapseImpl (toCollapse, false);
 
-			if (all) {
-				branch.CollapseAll ();
-			} else {
-				branch.Collapse ();
-			}
+	/// <summary>
+	/// Collapses the supplied object if it is currently expanded. Also collapses all children
+	/// branches (this will only become apparent when/if the user expands it again).
+	/// </summary>
+	/// <param name="toCollapse">The object to collapse.</param>
+	public void CollapseAll (T toCollapse) => CollapseImpl (toCollapse, true);
 
-			if (SelectedObject != null && ObjectToBranch (SelectedObject) == null) {
-				// If the old selection suddenly became invalid then clear it
-				SelectedObject = null;
-			}
+	/// <summary>
+	/// Collapses all root nodes in the tree.
+	/// </summary>
+	public void CollapseAll ()
+	{
+		foreach (var item in roots) {
+			item.Value.Collapse ();
+		}
 
-			InvalidateLineMap ();
-			SetNeedsDisplay ();
+		InvalidateLineMap ();
+		SetNeedsDisplay ();
+	}
+
+	/// <summary>
+	/// Implementation of <see cref="Collapse(T)"/> and <see cref="CollapseAll(T)"/>. Performs
+	/// operation and updates selection if disapeared.
+	/// </summary>
+	/// <param name="toCollapse"></param>
+	/// <param name="all"></param>
+	protected void CollapseImpl (T toCollapse, bool all)
+	{
+		if (toCollapse == null) {
+			return;
 		}
 
-		/// <summary>
-		/// Clears any cached results of the tree state.
-		/// </summary>
-		public void InvalidateLineMap ()
-		{
-			cachedLineMap = null;
-		}
-
-		/// <summary>
-		/// Returns the corresponding <see cref="Branch{T}"/> in the tree for
-		/// <paramref name="toFind"/>. This will not work for objects hidden
-		/// by their parent being collapsed.
-		/// </summary>
-		/// <param name="toFind"></param>
-		/// <returns>The branch for <paramref name="toFind"/> or null if it is not currently 
-		/// exposed in the tree.</returns>
-		private Branch<T> ObjectToBranch (T toFind)
-		{
-			return BuildLineMap ().FirstOrDefault (o => o.Model.Equals (toFind));
-		}
-
-		/// <summary>
-		/// Returns true if the <paramref name="model"/> is either the 
-		/// <see cref="SelectedObject"/> or part of a <see cref="MultiSelect"/>.
-		/// </summary>
-		/// <param name="model"></param>
-		/// <returns></returns>
-		public bool IsSelected (T model)
-		{
-			return Equals (SelectedObject, model) ||
-				(MultiSelect && multiSelectedRegions.Any (s => s.Contains (model)));
-		}
-
-		/// <summary>
-		/// Returns <see cref="SelectedObject"/> (if not null) and all multi selected objects if 
-		/// <see cref="MultiSelect"/> is true
-		/// </summary>
-		/// <returns></returns>
-		public IEnumerable<T> GetAllSelectedObjects ()
-		{
-			var map = BuildLineMap ();
+		var branch = ObjectToBranch (toCollapse);
 
-			// To determine multi selected objects, start with the line map, that avoids yielding 
-			// hidden nodes that were selected then the parent collapsed e.g. programmatically or
-			// with mouse click
-			if (MultiSelect) {
-				foreach (var m in map.Select (b => b.Model).Where (IsSelected)) {
-					yield return m;
-				}
-			} else {
-				if (SelectedObject != null) {
-					yield return SelectedObject;
-				}
-			}
+		// Nothing to collapse
+		if (branch == null) {
+			return;
 		}
 
-		/// <summary>
-		/// Selects all objects in the tree when <see cref="MultiSelect"/> is enabled otherwise 
-		/// does nothing.
-		/// </summary>
-		public void SelectAll ()
-		{
-			if (!MultiSelect) {
-				return;
-			}
+		if (all) {
+			branch.CollapseAll ();
+		} else {
+			branch.Collapse ();
+		}
 
-			multiSelectedRegions.Clear ();
+		if (SelectedObject != null && ObjectToBranch (SelectedObject) == null) {
+			// If the old selection suddenly became invalid then clear it
+			SelectedObject = null;
+		}
 
-			var map = BuildLineMap ();
+		InvalidateLineMap ();
+		SetNeedsDisplay ();
+	}
 
-			if (map.Count == 0) {
-				return;
-			}
+	/// <summary>
+	/// Clears any cached results of the tree state.
+	/// </summary>
+	public void InvalidateLineMap () => cachedLineMap = null;
 
-			multiSelectedRegions.Push (new TreeSelection<T> (map.ElementAt (0), map.Count, map));
-			SetNeedsDisplay ();
+	/// <summary>
+	/// Returns the corresponding <see cref="Branch{T}"/> in the tree for
+	/// <paramref name="toFind"/>. This will not work for objects hidden
+	/// by their parent being collapsed.
+	/// </summary>
+	/// <param name="toFind"></param>
+	/// <returns>
+	/// The branch for <paramref name="toFind"/> or null if it is not currently
+	/// exposed in the tree.
+	/// </returns>
+	Branch<T> ObjectToBranch (T toFind) => BuildLineMap ().FirstOrDefault (o => o.Model.Equals (toFind));
 
-			OnSelectionChanged (new SelectionChangedEventArgs<T> (this, SelectedObject, SelectedObject));
-		}
+	/// <summary>
+	/// Returns true if the <paramref name="model"/> is either the
+	/// <see cref="SelectedObject"/> or part of a <see cref="MultiSelect"/>.
+	/// </summary>
+	/// <param name="model"></param>
+	/// <returns></returns>
+	public bool IsSelected (T model) => Equals (SelectedObject, model) ||
+	                                    MultiSelect && multiSelectedRegions.Any (s => s.Contains (model));
 
-		/// <summary>
-		/// Raises the SelectionChanged event.
-		/// </summary>
-		/// <param name="e"></param>
-		protected virtual void OnSelectionChanged (SelectionChangedEventArgs<T> e)
-		{
-			SelectionChanged?.Invoke (this, e);
+	/// <summary>
+	/// Returns <see cref="SelectedObject"/> (if not null) and all multi selected objects if
+	/// <see cref="MultiSelect"/> is true
+	/// </summary>
+	/// <returns></returns>
+	public IEnumerable<T> GetAllSelectedObjects ()
+	{
+		var map = BuildLineMap ();
+
+		// To determine multi selected objects, start with the line map, that avoids yielding 
+		// hidden nodes that were selected then the parent collapsed e.g. programmatically or
+		// with mouse click
+		if (MultiSelect) {
+			foreach (var m in map.Select (b => b.Model).Where (IsSelected)) {
+				yield return m;
+			}
+		} else {
+			if (SelectedObject != null) {
+				yield return SelectedObject;
+			}
 		}
+	}
 
-		/// <summary>
-		/// Raises the DrawLine event
-		/// </summary>
-		/// <param name="e"></param>
-		internal void OnDrawLine (DrawTreeViewLineEventArgs<T> e)
-		{
-			DrawLine?.Invoke (this, e);
+	/// <summary>
+	/// Selects all objects in the tree when <see cref="MultiSelect"/> is enabled otherwise
+	/// does nothing.
+	/// </summary>
+	public void SelectAll ()
+	{
+		if (!MultiSelect) {
+			return;
 		}
 
-		/// <inheritdoc/>
-		protected override void Dispose (bool disposing)
-		{
-			base.Dispose (disposing);
+		multiSelectedRegions.Clear ();
 
-			ColorGetter = null;
+		var map = BuildLineMap ();
+
+		if (map.Count == 0) {
+			return;
 		}
+
+		multiSelectedRegions.Push (new TreeSelection<T> (map.ElementAt (0), map.Count, map));
+		SetNeedsDisplay ();
+
+		OnSelectionChanged (new SelectionChangedEventArgs<T> (this, SelectedObject, SelectedObject));
 	}
-	class TreeSelection<T> where T : class {
 
-		public Branch<T> Origin { get; }
+	/// <summary>
+	/// Raises the SelectionChanged event.
+	/// </summary>
+	/// <param name="e"></param>
+	protected virtual void OnSelectionChanged (SelectionChangedEventArgs<T> e) => SelectionChanged?.Invoke (this, e);
 
-		private HashSet<T> included = new HashSet<T> ();
+	/// <summary>
+	/// Raises the DrawLine event
+	/// </summary>
+	/// <param name="e"></param>
+	internal void OnDrawLine (DrawTreeViewLineEventArgs<T> e) => DrawLine?.Invoke (this, e);
 
-		/// <summary>
-		/// Creates a new selection between two branches in the tree
-		/// </summary>
-		/// <param name="from"></param>
-		/// <param name="toIndex"></param>
-		/// <param name="map"></param>
-		public TreeSelection (Branch<T> from, int toIndex, IReadOnlyCollection<Branch<T>> map)
-		{
-			Origin = from;
-			included.Add (Origin.Model);
+	/// <inheritdoc/>
+	protected override void Dispose (bool disposing)
+	{
+		base.Dispose (disposing);
 
-			var oldIdx = map.IndexOf (from);
+		ColorGetter = null;
+	}
+}
 
-			var lowIndex = Math.Min (oldIdx, toIndex);
-			var highIndex = Math.Max (oldIdx, toIndex);
+class TreeSelection<T> where T : class {
 
-			// Select everything between the old and new indexes
-			foreach (var alsoInclude in map.Skip (lowIndex).Take (highIndex - lowIndex)) {
-				included.Add (alsoInclude.Model);
-			}
+	readonly HashSet<T> included = new ();
 
+	/// <summary>
+	/// Creates a new selection between two branches in the tree
+	/// </summary>
+	/// <param name="from"></param>
+	/// <param name="toIndex"></param>
+	/// <param name="map"></param>
+	public TreeSelection (Branch<T> from, int toIndex, IReadOnlyCollection<Branch<T>> map)
+	{
+		Origin = from;
+		included.Add (Origin.Model);
+
+		var oldIdx = map.IndexOf (from);
+
+		var lowIndex = Math.Min (oldIdx, toIndex);
+		var highIndex = Math.Max (oldIdx, toIndex);
+
+		// Select everything between the old and new indexes
+		foreach (var alsoInclude in map.Skip (lowIndex).Take (highIndex - lowIndex)) {
+			included.Add (alsoInclude.Model);
 		}
-		public bool Contains (T model)
-		{
-			return included.Contains (model);
-		}
+
 	}
+
+	public Branch<T> Origin { get; }
+
+	public bool Contains (T model) => included.Contains (model);
 }

+ 4 - 3
Terminal.Gui/Views/Window.cs

@@ -34,7 +34,7 @@ public class Window : Toplevel {
 	///// This property can be set in a Theme to change the default <see cref="LineStyle"/> for all <see cref="Window"/>s. 
 	///// </remarks>
 	/////[SerializableConfigurationProperty (Scope = typeof (ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
-	////public static ColorScheme DefaultColorScheme { get; set; } = Colors.Base;
+	////public static ColorScheme DefaultColorScheme { get; set; } = Colors.ColorSchemes ["Base"];
 
 	/// <summary>
 	/// The default <see cref="LineStyle"/> for <see cref="Window"/>'s border. The default is
@@ -44,13 +44,14 @@ public class Window : Toplevel {
 	/// This property can be set in a Theme to change the default <see cref="LineStyle"/> for all
 	/// <see cref="Window"/>s.
 	/// </remarks>
-	[SerializableConfigurationProperty (Scope = typeof (ThemeScope))] [JsonConverter (typeof (JsonStringEnumConverter))]
+	[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+	[JsonConverter (typeof (JsonStringEnumConverter))]
 	public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
 
 	void SetInitialProperties ()
 	{
 		CanFocus = true;
-		ColorScheme = Colors.Base; // TODO: make this a theme property
+		ColorScheme = Colors.ColorSchemes ["Base"]; // TODO: make this a theme property
 		BorderStyle = DefaultBorderStyle;
 
 		// This enables the default button to be activated by the Enter key.

+ 2 - 2
Terminal.Gui/Views/Wizard/Wizard.cs

@@ -512,13 +512,13 @@ public class Wizard : Dialog {
 				SizeStep (step);
 			}
 			if (base.Modal) {
-				ColorScheme = Colors.Dialog;
+				ColorScheme = Colors.ColorSchemes ["Dialog"];
 				BorderStyle = LineStyle.Rounded;
 			} else {
 				if (SuperView != null) {
 					ColorScheme = SuperView.ColorScheme;
 				} else {
-					ColorScheme = Colors.Base;
+					ColorScheme = Colors.ColorSchemes ["Base"];
 				}
 				CanFocus = true;
 				BorderStyle = LineStyle.None;

+ 0 - 1
Terminal.sln

@@ -26,7 +26,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 		nuget.config = nuget.config
 		.github\workflows\publish.yml = .github\workflows\publish.yml
 		README.md = README.md
-		Terminal.Gui\.vscode\settings.json = Terminal.Gui\.vscode\settings.json
 		Terminal.sln.DotSettings = Terminal.sln.DotSettings
 		testenvironments.json = testenvironments.json
 	EndProjectSection

+ 4 - 0
UICatalog/Properties/launchSettings.json

@@ -69,6 +69,10 @@
     "ListView & ComboBox": {
       "commandName": "Project",
       "commandLineArgs": "\"ListView & ComboBox\""
+    },
+    "Frames Demo": {
+      "commandName": "Project",
+      "commandLineArgs": "\"Frames Demo\""
     }
   }
 }

+ 257 - 232
UICatalog/Scenario.cs

@@ -1,271 +1,296 @@
-using System.Text;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using Terminal.Gui;
 
-namespace UICatalog {
+namespace UICatalog;
+
+/// <summary>
+///         <para>Base class for each demo/scenario.</para>
+///         <para>
+///         To define a new scenario:
+///         <list type="number">
+///                 <item>
+///                         <description>
+///                         Create a new <c>.cs</c> file in the <cs>Scenarios</cs> directory that derives from
+///                         <see cref="Scenario"/>.
+///                         </description>
+///                 </item>
+///                 <item>
+///                         <description>
+///                         Annotate the <see cref="Scenario"/> derived class with a
+///                         <see cref="Scenario.ScenarioMetadata"/> attribute specifying the scenario's name and
+///                         description.
+///                         </description>
+///                 </item>
+///                 <item>
+///                         <description>
+///                         Add one or more <see cref="Scenario.ScenarioCategory"/> attributes to the class specifying
+///                         which categories the scenario belongs to. If you don't specify a category the scenario will
+///                         show up in "_All".
+///                         </description>
+///                 </item>
+///                 <item>
+///                         <description>
+///                         Implement the <see cref="Setup"/> override which will be called when a user
+///                         selects the scenario to run.
+///                         </description>
+///                 </item>
+///                 <item>
+///                         <description>
+///                         Optionally, implement the <see cref="Init()"/> and/or <see cref="Run"/> overrides
+///                         to provide a custom implementation.
+///                         </description>
+///                 </item>
+///         </list>
+///         </para>
+///         <para>
+///         The UI Catalog program uses reflection to find all scenarios and adds them to the
+///         ListViews. Press ENTER to run the selected scenario. Press the default quit key to quit.	/
+///         </para>
+/// </summary>
+/// <example>
+/// The example below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named:
+/// <code>
+/// using Terminal.Gui;
+/// 
+/// namespace UICatalog {
+/// 	[ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")]
+/// 	[ScenarioCategory ("Controls")]
+/// 	class MyScenario : Scenario {
+/// 		public override void Setup ()
+/// 		{
+/// 			// Put your scenario code here, e.g.
+/// 			Win.Add (new Button ("Press me!") {
+/// 				X = Pos.Center (),
+/// 				Y = Pos.Center (),
+/// 				Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No")
+/// 			});
+/// 		}
+/// 	}
+/// }
+/// </code>
+/// </example>
+public class Scenario : IDisposable {
+
+	static int _maxScenarioNameLen = 30;
+	bool _disposedValue;
+
+	public string Theme = "Default";
+	public string TopLevelColorScheme = "Base";
+
 	/// <summary>
-	/// <para>Base class for each demo/scenario.</para>
-	/// <para>
-	///  To define a new scenario:
-	///  <list type="number">
-	///  <item><description>Create a new <c>.cs</c> file in the <cs>Scenarios</cs> directory that derives from <see cref="Scenario"/>.</description></item>
-	///  <item><description>Annotate the <see cref="Scenario"/> derived class with a <see cref="Scenario.ScenarioMetadata"/> attribute specifying the scenario's name and description.</description></item>
-	///  <item><description>Add one or more <see cref="Scenario.ScenarioCategory"/> attributes to the class specifying which categories the scenario belongs to. If you don't specify a category the scenario will show up in "_All".</description></item>
-	///  <item><description>Implement the <see cref="Setup"/> override which will be called when a user selects the scenario to run.</description></item>
-	///  <item><description>Optionally, implement the <see cref="Init()"/> and/or <see cref="Run"/> overrides to provide a custom implementation.</description></item>
-	///  </list>
-	/// </para>
-	/// <para>
-	/// The UI Catalog program uses reflection to find all scenarios and adds them to the
-	/// ListViews. Press ENTER to run the selected scenario. Press the default quit key to quit.	/
-	/// </para>
+	/// The Window for the <see cref="Scenario"/>. This should be set to <see cref="Terminal.Gui.Application.Top"/> in most
+	/// cases.
 	/// </summary>
-	/// <example>
-	/// The example below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named:
-	/// <code>
-	/// using Terminal.Gui;
-	/// 
-	/// namespace UICatalog {
-	/// 	[ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")]
-	/// 	[ScenarioCategory ("Controls")]
-	/// 	class MyScenario : Scenario {
-	/// 		public override void Setup ()
-	/// 		{
-	/// 			// Put your scenario code here, e.g.
-	/// 			Win.Add (new Button ("Press me!") {
-	/// 				X = Pos.Center (),
-	/// 				Y = Pos.Center (),
-	/// 				Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No")
-	/// 			});
-	/// 		}
-	/// 	}
-	/// }
-	/// </code>
-	/// </example>
-	public class Scenario : IDisposable {
-		private bool _disposedValue;
-
-		public string Theme = "Default";
-		public string TopLevelColorScheme = "Base";
+	public Window Win { get; set; }
 
-		/// <summary>
-		/// The Window for the <see cref="Scenario"/>. This should be set to <see cref="Terminal.Gui.Application.Top"/> in most cases.
-		/// </summary>
-		public Window Win { get; set; }
+	public void Dispose ()
+	{
+		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+		Dispose (disposing: true);
+		GC.SuppressFinalize (this);
+	}
 
-		/// <summary>
-		/// Helper that provides the default <see cref="Terminal.Gui.Window"/> implementation with a frame and 
-		/// label showing the name of the <see cref="Scenario"/> and logic to exit back to 
-		/// the Scenario picker UI.
-		/// Override <see cref="Init"/> to provide any <see cref="Terminal.Gui.Toplevel"/> behavior needed.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// The base implementation calls <see cref="Application.Init"/> and creates a <see cref="Window"/> for <see cref="Win"/> 
-		/// and adds it to <see cref="Application.Top"/>.
-		/// </para>
-		/// <para>
-		/// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Init"/> 
-		/// before creating any views or calling other Terminal.Gui APIs.
-		/// </para>
-		/// </remarks>
-		public virtual void Init ()
-		{
-			Application.Init ();
-			
-			ConfigurationManager.Themes.Theme = Theme;
-			ConfigurationManager.Apply ();
-
-			Win = new Window () {
-				Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-				ColorScheme = Colors.ColorSchemes [TopLevelColorScheme],
-			};
-			Application.Top.Add (Win);
+	/// <summary>
+	/// Helper that provides the default <see cref="Terminal.Gui.Window"/> implementation with a frame and
+	/// label showing the name of the <see cref="Scenario"/> and logic to exit back to
+	/// the Scenario picker UI.
+	/// Override <see cref="Init"/> to provide any <see cref="Terminal.Gui.Toplevel"/> behavior needed.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         The base implementation calls <see cref="Application.Init"/> and creates a <see cref="Window"/> for
+	///         <see cref="Win"/>
+	///         and adds it to <see cref="Application.Top"/>.
+	///         </para>
+	///         <para>
+	///         Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Init"/>
+	///         before creating any views or calling other Terminal.Gui APIs.
+	///         </para>
+	/// </remarks>
+	public virtual void Init ()
+	{
+		Application.Init ();
+
+		ConfigurationManager.Themes.Theme = Theme;
+		ConfigurationManager.Apply ();
+
+		Win = new Window {
+			Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+			X = 0,
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill (),
+			ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]
+		};
+		Application.Top.Add (Win);
+	}
+
+	/// <summary>
+	/// Helper to get the <see cref="Scenario"/> Name (defined in <see cref="ScenarioMetadata"/>)
+	/// </summary>
+	/// <returns></returns>
+	public string GetName () => ScenarioMetadata.GetName (GetType ());
+
+	/// <summary>
+	/// Helper to get the <see cref="Scenario"/> Description (defined in <see cref="ScenarioMetadata"/>)
+	/// </summary>
+	/// <returns></returns>
+	public string GetDescription () => ScenarioMetadata.GetDescription (GetType ());
+
+	/// <summary>
+	/// Helper function to get the list of categories a <see cref="Scenario"/> belongs to (defined in
+	/// <see cref="ScenarioCategory"/>)
+	/// </summary>
+	/// <returns>list of category names</returns>
+	public List<string> GetCategories () => ScenarioCategory.GetCategories (GetType ());
+
+	/// <summary>
+	/// Gets the Scenario Name + Description with the Description padded
+	/// based on the longest known Scenario name.
+	/// </summary>
+	/// <returns></returns>
+	public override string ToString () => $"{GetName ().PadRight (_maxScenarioNameLen)}{GetDescription ()}";
+
+	/// <summary>
+	/// Override this to implement the <see cref="Scenario"/> setup logic (create controls, etc...).
+	/// </summary>
+	/// <remarks>This is typically the best place to put scenario logic code.</remarks>
+	public virtual void Setup () { }
+
+	/// <summary>
+	/// Runs the <see cref="Scenario"/>. Override to start the <see cref="Scenario"/>
+	/// using a <see cref="Toplevel"/> different than `Top`.
+	/// </summary>
+	/// <remarks>
+	/// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Shutdown"/> before returning.
+	/// </remarks>
+	public virtual void Run () =>
+		// Must explicit call Application.Shutdown method to shutdown.
+		Application.Run (Application.Top);
+
+	/// <summary>
+	/// Stops the scenario. Override to change shutdown behavior for the <see cref="Scenario"/>.
+	/// </summary>
+	public virtual void RequestStop () => Application.RequestStop ();
+
+	/// <summary>
+	/// Returns a list of all Categories set by all of the <see cref="Scenario"/>s defined in the project.
+	/// </summary>
+	internal static List<string> GetAllCategories ()
+	{
+		var categories = new List<string> ();
+		foreach (var type in typeof (Scenario).Assembly.GetTypes ()
+			.Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
+			var attrs = System.Attribute.GetCustomAttributes (type).ToList ();
+			categories = categories.Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name)).ToList ();
 		}
 
-		/// <summary>
-		/// Defines the metadata (Name and Description) for a <see cref="Scenario"/>
-		/// </summary>
-		[System.AttributeUsage (System.AttributeTargets.Class)]
-		public class ScenarioMetadata : System.Attribute {
-			/// <summary>
-			/// <see cref="Scenario"/> Name
-			/// </summary>
-			public string Name { get; set; }
-
-			/// <summary>
-			/// <see cref="Scenario"/> Description
-			/// </summary>
-			public string Description { get; set; }
-
-			public ScenarioMetadata (string Name, string Description)
-			{
-				this.Name = Name;
-				this.Description = Description;
+		// Sort
+		categories = categories.OrderBy (c => c).ToList ();
+
+		// Put "All" at the top
+		categories.Insert (0, "All Scenarios");
+		return categories;
+	}
+
+	/// <summary>
+	/// Returns a list of all <see cref="Scenario"/> instanaces defined in the project, sorted by
+	/// <see cref="ScenarioMetadata.Name"/>.
+	/// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
+	/// </summary>
+	public static List<Scenario> GetScenarios ()
+	{
+		var objects = new List<Scenario> ();
+		foreach (var type in typeof (Scenario).Assembly.ExportedTypes
+			.Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
+			var scenario = (Scenario)Activator.CreateInstance (type);
+			objects.Add (scenario);
+			_maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
+		}
+		return objects.OrderBy (s => s.GetName ()).ToList ();
+	}
+
+	protected virtual void Dispose (bool disposing)
+	{
+		if (!_disposedValue) {
+			if (disposing) {
+				// TODO: dispose managed state (managed objects)
 			}
 
-			/// <summary>
-			/// Static helper function to get the <see cref="Scenario"/> Name given a Type
-			/// </summary>
-			/// <param name="t"></param>
-			/// <returns></returns>
-			public static string GetName (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Name;
-
-			/// <summary>
-			/// Static helper function to get the <see cref="Scenario"/> Description given a Type
-			/// </summary>
-			/// <param name="t"></param>
-			/// <returns></returns>
-			public static string GetDescription (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Description;
+			// TODO: free unmanaged resources (unmanaged objects) and override finalizer
+			// TODO: set large fields to null
+			_disposedValue = true;
 		}
+	}
 
-		/// <summary>
-		/// Helper to get the <see cref="Scenario"/> Name (defined in <see cref="ScenarioMetadata"/>)
-		/// </summary>
-		/// <returns></returns>
-		public string GetName () => ScenarioMetadata.GetName (this.GetType ());
+	/// <summary>
+	/// Defines the metadata (Name and Description) for a <see cref="Scenario"/>
+	/// </summary>
+	[AttributeUsage (AttributeTargets.Class)]
+	public class ScenarioMetadata : System.Attribute {
 
-		/// <summary>
-		/// Helper to get the <see cref="Scenario"/> Description (defined in <see cref="ScenarioMetadata"/>)
-		/// </summary>
-		/// <returns></returns>
-		public string GetDescription () => ScenarioMetadata.GetDescription (this.GetType ());
+		public ScenarioMetadata (string Name, string Description)
+		{
+			this.Name = Name;
+			this.Description = Description;
+		}
 
 		/// <summary>
-		/// Defines the category names used to catagorize a <see cref="Scenario"/>
+		/// <see cref="Scenario"/> Name
 		/// </summary>
-		[System.AttributeUsage (System.AttributeTargets.Class, AllowMultiple = true)]
-		public class ScenarioCategory : System.Attribute {
-			/// <summary>
-			/// Category Name
-			/// </summary>
-			public string Name { get; set; }
-
-			public ScenarioCategory (string Name) => this.Name = Name;
-
-			/// <summary>
-			/// Static helper function to get the <see cref="Scenario"/> Name given a Type
-			/// </summary>
-			/// <param name="t"></param>
-			/// <returns>Name of the category</returns>
-			public static string GetName (Type t) => ((ScenarioCategory)System.Attribute.GetCustomAttributes (t) [0]).Name;
-
-			/// <summary>
-			/// Static helper function to get the <see cref="Scenario"/> Categories given a Type
-			/// </summary>
-			/// <param name="t"></param>
-			/// <returns>list of category names</returns>
-			public static List<string> GetCategories (Type t) => System.Attribute.GetCustomAttributes (t)
-				.ToList ()
-				.Where (a => a is ScenarioCategory)
-				.Select (a => ((ScenarioCategory)a).Name)
-				.ToList ();
-		}
+		public string Name { get; set; }
 
 		/// <summary>
-		/// Helper function to get the list of categories a <see cref="Scenario"/> belongs to (defined in <see cref="ScenarioCategory"/>)
+		/// <see cref="Scenario"/> Description
 		/// </summary>
-		/// <returns>list of category names</returns>
-		public List<string> GetCategories () => ScenarioCategory.GetCategories (this.GetType ());
-
-		private static int _maxScenarioNameLen = 30;
+		public string Description { get; set; }
 
 		/// <summary>
-		/// Gets the Scenario Name + Description with the Description padded
-		/// based on the longest known Scenario name.
+		/// Static helper function to get the <see cref="Scenario"/> Name given a Type
 		/// </summary>
+		/// <param name="t"></param>
 		/// <returns></returns>
-		public override string ToString () => $"{GetName ().PadRight(_maxScenarioNameLen)}{GetDescription ()}";
+		public static string GetName (Type t) => ((ScenarioMetadata)GetCustomAttributes (t) [0]).Name;
 
 		/// <summary>
-		/// Override this to implement the <see cref="Scenario"/> setup logic (create controls, etc...). 
+		/// Static helper function to get the <see cref="Scenario"/> Description given a Type
 		/// </summary>
-		/// <remarks>This is typically the best place to put scenario logic code.</remarks>
-		public virtual void Setup ()
-		{
-		}
+		/// <param name="t"></param>
+		/// <returns></returns>
+		public static string GetDescription (Type t) => ((ScenarioMetadata)GetCustomAttributes (t) [0]).Description;
+	}
 
-		/// <summary>
-		/// Runs the <see cref="Scenario"/>. Override to start the <see cref="Scenario"/> 
-		/// using a <see cref="Toplevel"/> different than `Top`.
-		/// </summary>
-		/// <remarks>
-		/// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Shutdown"/> before returning.
-		/// </remarks>
-		public virtual void Run ()
-		{
-			// Must explicit call Application.Shutdown method to shutdown.
-			Application.Run (Application.Top);
-		}
+	/// <summary>
+	/// Defines the category names used to catagorize a <see cref="Scenario"/>
+	/// </summary>
+	[AttributeUsage (AttributeTargets.Class, AllowMultiple = true)]
+	public class ScenarioCategory : System.Attribute {
+
+		public ScenarioCategory (string Name) => this.Name = Name;
 
 		/// <summary>
-		/// Stops the scenario. Override to change shutdown behavior for the <see cref="Scenario"/>.
+		/// Category Name
 		/// </summary>
-		public virtual void RequestStop ()
-		{
-			Application.RequestStop ();
-		}
+		public string Name { get; set; }
 
 		/// <summary>
-		/// Returns a list of all Categories set by all of the <see cref="Scenario"/>s defined in the project.
+		/// Static helper function to get the <see cref="Scenario"/> Name given a Type
 		/// </summary>
-		internal static List<string> GetAllCategories ()
-		{
-			List<string> categories = new List<string> ();
-			foreach (Type type in typeof (Scenario).Assembly.GetTypes ()
-			 .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
-				List<System.Attribute> attrs = System.Attribute.GetCustomAttributes (type).ToList ();
-				categories = categories.Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name)).ToList ();
-			}
-
-			// Sort
-			categories = categories.OrderBy (c => c).ToList ();
-
-			// Put "All" at the top
-			categories.Insert (0, "All Scenarios");
-			return categories;
-		}
+		/// <param name="t"></param>
+		/// <returns>Name of the category</returns>
+		public static string GetName (Type t) => ((ScenarioCategory)GetCustomAttributes (t) [0]).Name;
 
 		/// <summary>
-		/// Returns a list of all <see cref="Scenario"/> instanaces defined in the project, sorted by <see cref="ScenarioMetadata.Name"/>.
-		/// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
+		/// Static helper function to get the <see cref="Scenario"/> Categories given a Type
 		/// </summary>
-		public static List<Scenario> GetScenarios ()
-		{
-			List<Scenario> objects = new List<Scenario> ();
-			foreach (Type type in typeof (Scenario).Assembly.ExportedTypes
-			 .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
-				var scenario = (Scenario)Activator.CreateInstance (type);
-				objects.Add (scenario);
-				_maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
-			}
-			return objects.OrderBy (s => s.GetName ()).ToList ();
-		}
-
-		protected virtual void Dispose (bool disposing)
-		{
-			if (!_disposedValue) {
-				if (disposing) {
-					// TODO: dispose managed state (managed objects)
-				}
-
-				// TODO: free unmanaged resources (unmanaged objects) and override finalizer
-				// TODO: set large fields to null
-				_disposedValue = true;
-			}
-		}
-
-		public void Dispose ()
-		{
-			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
-			Dispose (disposing: true);
-			GC.SuppressFinalize (this);
-		}
+		/// <param name="t"></param>
+		/// <returns>list of category names</returns>
+		public static List<string> GetCategories (Type t) => GetCustomAttributes (t)
+			.ToList ()
+			.Where (a => a is ScenarioCategory)
+			.Select (a => ((ScenarioCategory)a).Name)
+			.ToList ();
 	}
-}
+}

+ 1 - 1
UICatalog/Scenarios/ASCIICustomButton.cs

@@ -77,7 +77,7 @@ namespace UICatalog.Scenarios {
 				};
 
 				AutoSize = false;
-				LayoutStyle = LayoutStyle.Absolute;
+				//LayoutStyle = LayoutStyle.Absolute;
 
 				var fillText = new System.Text.StringBuilder ();
 				for (int i = 0; i < Bounds.Height; i++) {

+ 440 - 0
UICatalog/Scenarios/Adornments.cs

@@ -0,0 +1,440 @@
+using System;
+using System.Linq;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Adornments Demo", "Demonstrates Margin, Border, and Padding on Views.")]
+[ScenarioCategory ("Layout"), ScenarioCategory ("Borders")]
+public class Adornments : Scenario {
+
+	public override void Init ()
+	{
+		Application.Init ();
+		ConfigurationManager.Themes.Theme = Theme;
+		ConfigurationManager.Apply ();
+		Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
+
+		var view = new Window { Title = "The Window" };
+		var tf1 = new TextField ("TextField") { Width = 10 };
+		var color = new ColorPicker () { Title = "BG", BoxHeight = 1, BoxWidth =1, X = Pos.AnchorEnd(11) };
+		color.BorderStyle = LineStyle.RoundedDotted;
+		color.ColorChanged += (s, e) => {
+			color.SuperView.ColorScheme = new ColorScheme (color.SuperView.ColorScheme) {
+				Normal = new Attribute(color.SuperView.ColorScheme.Normal.Foreground, e.Color)
+			};
+		};
+
+		var button = new Button ("Press me!") {
+			X = Pos.Center (),
+			Y = Pos.Center ()
+		};
+		button.Clicked += (s, e) => MessageBox.Query (20, 7, "Hi", $"Am I a {view.GetType ().Name}?", "Yes", "No");
+
+		var label = new TextView () {
+			X = Pos.Center (),
+			Y = Pos.Bottom (button),
+			Title = "Title",
+			Text = "I have a 3 row top border.\nMy border inherits from the SuperView.",
+			Width = 40,
+			Height = 6 // TODO: Use Dim.Auto
+		};
+		label.Border.Thickness = new Thickness (1, 3, 1, 1);
+
+		var tf2 = new Button ("Button") {
+			X = Pos.AnchorEnd (10),
+			Y = Pos.AnchorEnd (1),
+			Width = 10
+		};
+		var tv = new Label {
+			Y = Pos.AnchorEnd (3),
+			Width = 25,
+			Height = Dim.Fill (),
+			Text = "Label\nY=AnchorEnd(3),Height=Dim.Fill()"
+		};
+
+		view.Margin.Data = "Margin";
+		view.Margin.Thickness = new Thickness (3);
+
+		view.Border.Data = "Border";
+		view.Border.Thickness = new Thickness (3);
+
+		view.Padding.Data = "Padding";
+		view.Padding.Thickness = new Thickness (3);
+
+		view.Add (tf1, color, button, label, tf2, tv);
+
+		var editor = new AdornmentsEditor {
+			Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+			ColorScheme = Colors.ColorSchemes [TopLevelColorScheme],
+		};
+		view.X = 36;
+		view.Y = 0;
+		view.Width = Dim.Fill ();
+		view.Height = Dim.Fill ();
+
+		editor.Initialized += (s, e) => {
+			editor.ViewToEdit = view;
+		};
+		//view.Margin.ColorScheme = new ColorScheme (Colors.ColorSchemes ["Dialog"]);
+		//view.Border.ColorScheme = new ColorScheme (Colors.ColorSchemes ["Error"]);
+		//view.Padding.ColorScheme = new ColorScheme (Colors.ColorSchemes ["Menu"]);
+
+		Application.Run (editor);
+		Application.Shutdown ();
+	}
+
+	public override void Run () { }
+
+	public class AdornmentEditor : View {
+		readonly ColorPicker _backgroundColorPicker = new () {
+			Title = "BG",
+			BoxWidth = 1,
+			BoxHeight = 1,
+			BorderStyle = LineStyle.Single,
+			SuperViewRendersLineCanvas = true
+		};
+
+		readonly ColorPicker _foregroundColorPicker = new () {
+			Title = "FG",
+			BoxWidth = 1,
+			BoxHeight = 1,
+			BorderStyle = LineStyle.Single,
+			SuperViewRendersLineCanvas = true
+		};
+
+		TextField _bottomEdit;
+		bool _isUpdating;
+		TextField _leftEdit;
+		TextField _rightEdit;
+		Thickness _thickness;
+		TextField _topEdit;
+
+		public AdornmentEditor ()
+		{
+			Margin.Thickness = new Thickness (0);
+			BorderStyle = LineStyle.Double;
+			Initialized += AdornmentEditor_Initialized;
+		}
+
+		public Attribute Color {
+			get => new (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor);
+			set {
+				_foregroundColorPicker.SelectedColor = value.Foreground.ColorName;
+				_backgroundColorPicker.SelectedColor = value.Background.ColorName;
+			}
+		}
+
+		public Thickness Thickness {
+			get => _thickness;
+			set {
+				if (_isUpdating) {
+					return;
+				}
+				_thickness = value;
+				ThicknessChanged?.Invoke (this, new ThicknessEventArgs { Thickness = Thickness });
+				if (IsInitialized) {
+					_isUpdating = true;
+					if (_topEdit.Text != _thickness.Top.ToString ()) {
+						_topEdit.Text = _thickness.Top.ToString ();
+					}
+					if (_leftEdit.Text != _thickness.Left.ToString ()) {
+						_leftEdit.Text = _thickness.Left.ToString ();
+					}
+					if (_rightEdit.Text != _thickness.Right.ToString ()) {
+						_rightEdit.Text = _thickness.Right.ToString ();
+					}
+					if (_bottomEdit.Text != _thickness.Bottom.ToString ()) {
+						_bottomEdit.Text = _thickness.Bottom.ToString ();
+					}
+					_isUpdating = false;
+				}
+			}
+		}
+
+		public event EventHandler<ThicknessEventArgs> ThicknessChanged;
+
+		public event EventHandler<Attribute> AttributeChanged;
+
+		void AdornmentEditor_Initialized (object sender, EventArgs e)
+		{
+			var editWidth = 3;
+
+			_topEdit = new TextField ("") {
+				X = Pos.Center (),
+				Y = 0,
+				Width = editWidth
+			};
+			_topEdit.TextChanging += Edit_TextChanging;
+			Add (_topEdit);
+
+			_leftEdit = new TextField ("") {
+				X = Pos.Left (_topEdit) - editWidth,
+				Y = Pos.Bottom (_topEdit),
+				Width = editWidth
+			};
+			_leftEdit.TextChanging += Edit_TextChanging;
+			Add (_leftEdit);
+
+			_rightEdit = new TextField ("") {
+				X = Pos.Right (_topEdit),
+				Y = Pos.Bottom (_topEdit),
+				Width = editWidth
+			};
+			_rightEdit.TextChanging += Edit_TextChanging;
+			Add (_rightEdit);
+
+			_bottomEdit = new TextField ("") {
+				X = Pos.Center (),
+				Y = Pos.Bottom (_leftEdit),
+				Width = editWidth
+			};
+			_bottomEdit.TextChanging += Edit_TextChanging;
+			Add (_bottomEdit);
+
+			var copyTop = new Button ("Cop_y Top") {
+				X = Pos.Center () + 1,
+				Y = Pos.Bottom (_bottomEdit)
+			};
+			copyTop.Clicked += (s, e) => {
+				Thickness = new Thickness (Thickness.Top);
+				if (string.IsNullOrEmpty (_topEdit.Text)) {
+					_topEdit.Text = "0";
+				}
+				_bottomEdit.Text = _leftEdit.Text = _rightEdit.Text = _topEdit.Text;
+			};
+			Add (copyTop);
+
+			// Foreground ColorPicker.
+			_foregroundColorPicker.X = -1;
+			_foregroundColorPicker.Y = Pos.Bottom (copyTop) + 1;
+			_foregroundColorPicker.SelectedColor = Color.Foreground.ColorName;
+			_foregroundColorPicker.ColorChanged += (o, a) =>
+				AttributeChanged?.Invoke (this,
+					new Attribute (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor));
+			Add (_foregroundColorPicker);
+
+			// Background ColorPicker.
+			_backgroundColorPicker.X = Pos.Right (_foregroundColorPicker) - 1;
+			_backgroundColorPicker.Y = Pos.Top (_foregroundColorPicker);
+			_backgroundColorPicker.SelectedColor = Color.Background.ColorName;
+			_backgroundColorPicker.ColorChanged += (o, a) =>
+				AttributeChanged?.Invoke (this,
+					new Attribute (
+						_foregroundColorPicker.SelectedColor,
+						_backgroundColorPicker.SelectedColor));
+			Add (_backgroundColorPicker);
+
+			_topEdit.Text = $"{Thickness.Top}";
+			_leftEdit.Text = $"{Thickness.Left}";
+			_rightEdit.Text = $"{Thickness.Right}";
+			_bottomEdit.Text = $"{Thickness.Bottom}";
+
+			LayoutSubviews ();
+			Height = GetAdornmentsThickness ().Vertical + 4 + 4;
+			Width = GetAdornmentsThickness ().Horizontal + _foregroundColorPicker.Frame.Width * 2 - 3;
+		}
+
+		void Edit_TextChanging (object sender, TextChangingEventArgs e)
+		{
+			try {
+				if (string.IsNullOrEmpty (e.NewText)) {
+					e.Cancel = true;
+					((TextField)sender).Text = "0";
+					return;
+				}
+				switch (sender.ToString ()) {
+				case var s when s == _topEdit.ToString ():
+					Thickness = new Thickness (Thickness.Left,
+						int.Parse (e.NewText), Thickness.Right,
+						Thickness.Bottom);
+					break;
+				case var s when s == _leftEdit.ToString ():
+					Thickness = new Thickness (int.Parse (e.NewText),
+						Thickness.Top, Thickness.Right,
+						Thickness.Bottom);
+					break;
+				case var s when s == _rightEdit.ToString ():
+					Thickness = new Thickness (Thickness.Left,
+						Thickness.Top, int.Parse (e.NewText),
+						Thickness.Bottom);
+					break;
+				case var s when s == _bottomEdit.ToString ():
+					Thickness = new Thickness (Thickness.Left,
+						Thickness.Top, Thickness.Right,
+						int.Parse (e.NewText));
+					break;
+				}
+			} catch {
+				if (!string.IsNullOrEmpty (e.NewText)) {
+					e.Cancel = true;
+				}
+			}
+		}
+	}
+
+	public class AdornmentsEditor : Window {
+		AdornmentEditor _borderEditor;
+		CheckBox _diagCheckBox;
+		AdornmentEditor _marginEditor;
+		String _origTitle = string.Empty;
+		AdornmentEditor _paddingEditor;
+		View _viewToEdit;
+
+		public View ViewToEdit {
+			get => _viewToEdit;
+			set {
+				_origTitle = value.Title;
+				_viewToEdit = value;
+
+				_marginEditor = new AdornmentEditor {
+					X = 0,
+					Y = 0,
+					Title = "Margin",
+					Thickness = _viewToEdit.Margin.Thickness,
+					Color = new Attribute (_viewToEdit.Margin.ColorScheme.Normal),
+					SuperViewRendersLineCanvas = true
+				};
+				_marginEditor.ThicknessChanged += Editor_ThicknessChanged;
+				_marginEditor.AttributeChanged += Editor_AttributeChanged;
+				Add (_marginEditor);
+
+				_borderEditor = new AdornmentEditor {
+					X = Pos.Left (_marginEditor),
+					Y = Pos.Bottom (_marginEditor),
+					Title = "Border",
+					Thickness = _viewToEdit.Border.Thickness,
+					Color = new Attribute (_viewToEdit.Border.ColorScheme.Normal),
+					SuperViewRendersLineCanvas = true
+				};
+				_borderEditor.ThicknessChanged += Editor_ThicknessChanged;
+				_borderEditor.AttributeChanged += Editor_AttributeChanged;
+				Add (_borderEditor);
+
+
+				var borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast<LineStyle> ().ToList ();
+				var rbBorderStyle = new RadioGroup (borderStyleEnum.Select (
+					e => e.ToString ()).ToArray ()) {
+
+					X = Pos.Right (_borderEditor) - 1,
+					Y = Pos.Top (_borderEditor),
+					SelectedItem = (int)_viewToEdit.Border.LineStyle,
+					BorderStyle = LineStyle.Double,
+					Title = "Border Style",
+					SuperViewRendersLineCanvas = true
+				};
+				Add (rbBorderStyle);
+
+				rbBorderStyle.SelectedItemChanged += (s, e) => {
+					var prevBorderStyle = _viewToEdit.BorderStyle;
+					_viewToEdit.Border.LineStyle = (LineStyle)e.SelectedItem;
+					if (_viewToEdit.Border.LineStyle == LineStyle.None) {
+						_viewToEdit.Border.Thickness = new Thickness (0);
+					} else if (prevBorderStyle == LineStyle.None && _viewToEdit.Border.LineStyle != LineStyle.None) {
+						_viewToEdit.Border.Thickness = new Thickness (1);
+					}
+					_borderEditor.Thickness = new Thickness (_viewToEdit.Border.Thickness.Left, _viewToEdit.Border.Thickness.Top,
+						_viewToEdit.Border.Thickness.Right, _viewToEdit.Border.Thickness.Bottom);
+					_viewToEdit.SetNeedsDisplay ();
+					LayoutSubviews ();
+				};
+
+				var ckbTitle = new CheckBox ("Show Title") {
+					BorderStyle = LineStyle.Double,
+					X = Pos.Left (_borderEditor),
+					Y = Pos.Bottom (_borderEditor) - 1,
+					Width = Dim.Width (_borderEditor),
+					Checked = true,
+					SuperViewRendersLineCanvas = true
+				};
+				ckbTitle.Toggled += (sender, args) => {
+					if (ckbTitle.Checked == true) {
+						_viewToEdit.Title = _origTitle;
+					} else {
+						_viewToEdit.Title = string.Empty;
+					}
+				};
+				Add (ckbTitle);
+
+				_paddingEditor = new AdornmentEditor {
+					X = Pos.Left (_borderEditor),
+					Y = Pos.Bottom (rbBorderStyle),
+					Title = "Padding",
+					Thickness = _viewToEdit.Padding.Thickness,
+					Color = new Attribute (_viewToEdit.Padding.ColorScheme.Normal),
+					SuperViewRendersLineCanvas = true
+				};
+				_paddingEditor.ThicknessChanged += Editor_ThicknessChanged;
+				_paddingEditor.AttributeChanged += Editor_AttributeChanged;
+				Add (_paddingEditor);
+
+				_diagCheckBox = new CheckBox {
+					Text = "_Diagnostics",
+					Y = Pos.Bottom (_paddingEditor)
+				};
+				_diagCheckBox.Toggled += (s, e) => {
+					if (e.NewValue == true) {
+						ConsoleDriver.Diagnostics = ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler;
+					} else {
+						ConsoleDriver.Diagnostics = ConsoleDriver.DiagnosticFlags.Off;
+					}
+				};
+
+				Add (_diagCheckBox);
+				Add (_viewToEdit);
+
+				_viewToEdit.LayoutComplete += (s, e) => {
+					if (ckbTitle.Checked == true) {
+						_viewToEdit.Title = _origTitle;
+					} else {
+						_viewToEdit.Title = string.Empty;
+					}
+				};
+			}
+		}
+
+		void Editor_AttributeChanged (object sender, Attribute attr)
+		{
+			switch (sender.ToString ()) {
+			case var s when s == _marginEditor.ToString ():
+				_viewToEdit.Margin.ColorScheme = new ColorScheme (_viewToEdit.Margin.ColorScheme) { Normal = attr };
+				break;
+			case var s when s == _borderEditor.ToString ():
+				_viewToEdit.Border.ColorScheme = new ColorScheme (_viewToEdit.Border.ColorScheme) { Normal = attr };
+				break;
+			case var s when s == _paddingEditor.ToString ():
+				_viewToEdit.Padding.ColorScheme = new ColorScheme (_viewToEdit.Padding.ColorScheme) { Normal = attr };
+				break;
+			}
+		}
+
+		void Editor_ThicknessChanged (object sender, ThicknessEventArgs e)
+		{
+			try {
+				switch (sender.ToString ()) {
+				case var s when s == _marginEditor.ToString ():
+					_viewToEdit.Margin.Thickness = e.Thickness;
+					break;
+				case var s when s == _borderEditor.ToString ():
+					_viewToEdit.Border.Thickness = e.Thickness;
+					break;
+				case var s when s == _paddingEditor.ToString ():
+					_viewToEdit.Padding.Thickness = e.Thickness;
+					break;
+				}
+			} catch {
+				switch (sender.ToString ()) {
+				case var s when s == _marginEditor.ToString ():
+					_viewToEdit.Margin.Thickness = e.PreviousThickness;
+					break;
+				case var s when s == _borderEditor.ToString ():
+					_viewToEdit.Border.Thickness = e.PreviousThickness;
+					break;
+				case var s when s == _paddingEditor.ToString ():
+					_viewToEdit.Padding.Thickness = e.PreviousThickness;
+					break;
+				}
+			}
+		}
+	}
+}

+ 47 - 53
UICatalog/Scenarios/AllViewsTester.cs

@@ -7,35 +7,36 @@ using Terminal.Gui;
 namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("All Views Tester", "Provides a test UI for all classes derived from View.")]
-[ScenarioCategory ("Layout")]
-[ScenarioCategory ("Tests")]
-[ScenarioCategory ("Top Level Windows")]
+[ScenarioCategory ("Layout")] [ScenarioCategory ("Tests")] [ScenarioCategory ("Top Level Windows")]
 public class AllViewsTester : Scenario {
-	FrameView _leftPane;
 	ListView _classListView;
+	CheckBox _computedCheckBox;
+	View _curView;
+	readonly List<string> _dimNames = new () { "Factor", "Fill", "Absolute" };
 	FrameView _hostPane;
+	RadioGroup _hRadioGroup;
+	TextField _hText;
+	int _hVal;
+	FrameView _leftPane;
+	FrameView _locationFrame;
 
-	Dictionary<string, Type> _viewClasses;
-	View _curView = null;
+	// TODO: This is missing some
+	readonly List<string> _posNames = new () { "Factor", "AnchorEnd", "Center", "Absolute" };
 
 	// Settings
 	FrameView _settingsPane;
-	CheckBox _computedCheckBox;
-	FrameView _locationFrame;
+	FrameView _sizeFrame;
+
+	Dictionary<string, Type> _viewClasses;
+	RadioGroup _wRadioGroup;
+	TextField _wText;
+	int _wVal;
 	RadioGroup _xRadioGroup;
 	TextField _xText;
-	int _xVal = 0;
+	int _xVal;
 	RadioGroup _yRadioGroup;
 	TextField _yText;
-	int _yVal = 0;
-
-	FrameView _sizeFrame;
-	RadioGroup _wRadioGroup;
-	TextField _wText;
-	int _wVal = 0;
-	RadioGroup _hRadioGroup;
-	TextField _hText;
-	int _hVal = 0;
+	int _yVal;
 
 	public override void Init ()
 	{
@@ -62,9 +63,9 @@ public class AllViewsTester : Scenario {
 		Application.Top.Add (statusBar);
 
 		_viewClasses = GetAllViewClassesCollection ()
-				.OrderBy (t => t.Name)
-				.Select (t => new KeyValuePair<string, Type> (t.Name, t))
-				.ToDictionary (t => t.Key, t => t.Value);
+			.OrderBy (t => t.Name)
+			.Select (t => new KeyValuePair<string, Type> (t.Name, t))
+			.ToDictionary (t => t.Key, t => t.Value);
 
 		_leftPane = new FrameView ("Classes") {
 			X = 0,
@@ -72,16 +73,16 @@ public class AllViewsTester : Scenario {
 			Width = 15,
 			Height = Dim.Fill (1), // for status bar
 			CanFocus = false,
-			ColorScheme = Colors.TopLevel
+			ColorScheme = Colors.ColorSchemes ["TopLevel"]
 		};
 
 		_classListView = new ListView (_viewClasses.Keys.ToList ()) {
 			X = 0,
 			Y = 0,
-			Width = Dim.Fill (0),
-			Height = Dim.Fill (0),
+			Width = Dim.Fill (),
+			Height = Dim.Fill (),
 			AllowsMarking = false,
-			ColorScheme = Colors.TopLevel,
+			ColorScheme = Colors.ColorSchemes ["TopLevel"],
 			SelectedItem = 0
 		};
 		_classListView.OpenSelectedItem += (s, a) => {
@@ -106,18 +107,17 @@ public class AllViewsTester : Scenario {
 			Width = Dim.Fill (),
 			Height = 10,
 			CanFocus = false,
-			ColorScheme = Colors.TopLevel
+			ColorScheme = Colors.ColorSchemes ["TopLevel"]
 		};
-		_computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 };
+		_computedCheckBox = new CheckBox ("_Computed Layout", true) { X = 0, Y = 0 };
 		_computedCheckBox.Toggled += (s, e) => {
 			if (_curView != null) {
-				_curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed;
 				_hostPane.LayoutSubviews ();
 			}
 		};
 		_settingsPane.Add (_computedCheckBox);
 
-		string [] radioItems = new string [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" };
+		string [] radioItems = { "_Percent(x)", "_AnchorEnd(x)", "_Center", "A_t(x)" };
 		_locationFrame = new FrameView ("Location (Pos)") {
 			X = Pos.Left (_computedCheckBox),
 			Y = Pos.Bottom (_computedCheckBox),
@@ -126,7 +126,7 @@ public class AllViewsTester : Scenario {
 		};
 		_settingsPane.Add (_locationFrame);
 
-		var label = new Label ("x:") { X = 0, Y = 0 };
+		var label = new Label ("X:") { X = 0, Y = 0 };
 		_locationFrame.Add (label);
 		_xRadioGroup = new RadioGroup (radioItems) {
 			X = 0,
@@ -144,8 +144,8 @@ public class AllViewsTester : Scenario {
 
 		_locationFrame.Add (_xRadioGroup);
 
-		radioItems = new string [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" };
-		label = new Label ("y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 };
+		radioItems = new [] { "P_ercent(y)", "A_nchorEnd(y)", "C_enter", "At(_y)" };
+		label = new Label ("Y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 };
 		_locationFrame.Add (label);
 		_yText = new TextField ($"{_yVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
 		_yText.TextChanged += (s, args) => {
@@ -169,8 +169,8 @@ public class AllViewsTester : Scenario {
 			Width = 40
 		};
 
-		radioItems = new string [] { "Percent(width)", "Fill(width)", "Sized(width)" };
-		label = new Label ("width:") { X = 0, Y = 0 };
+		radioItems = new [] { "_Percent(width)", "_Fill(width)", "_Sized(width)" };
+		label = new Label ("Width:") { X = 0, Y = 0 };
 		_sizeFrame.Add (label);
 		_wRadioGroup = new RadioGroup (radioItems) {
 			X = 0,
@@ -195,8 +195,8 @@ public class AllViewsTester : Scenario {
 		_sizeFrame.Add (_wText);
 		_sizeFrame.Add (_wRadioGroup);
 
-		radioItems = new string [] { "Percent(height)", "Fill(height)", "Sized(height)" };
-		label = new Label ("height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 };
+		radioItems = new [] { "P_ercent(height)", "F_ill(height)", "Si_zed(height)" };
+		label = new Label ("Height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 };
 		_sizeFrame.Add (label);
 		_hText = new TextField ($"{_hVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
 		_hText.TextChanged += (s, args) => {
@@ -229,7 +229,7 @@ public class AllViewsTester : Scenario {
 			Y = Pos.Bottom (_settingsPane),
 			Width = Dim.Fill (),
 			Height = Dim.Fill (1), // + 1 for status bar
-			ColorScheme = Colors.Dialog
+			ColorScheme = Colors.ColorSchemes ["Dialog"]
 		};
 
 		Application.Top.Add (_leftPane, _settingsPane, _hostPane);
@@ -246,7 +246,7 @@ public class AllViewsTester : Scenario {
 		var layout = view.LayoutStyle;
 
 		try {
-			view.LayoutStyle = LayoutStyle.Absolute;
+			//view.LayoutStyle = LayoutStyle.Absolute;
 
 			view.X = _xRadioGroup.SelectedItem switch {
 				0 => Pos.Percent (_xVal),
@@ -279,27 +279,21 @@ public class AllViewsTester : Scenario {
 			};
 		} catch (Exception e) {
 			MessageBox.ErrorQuery ("Exception", e.Message, "Ok");
-		} finally {
-			view.LayoutStyle = layout;
 		}
 		UpdateTitle (view);
 	}
 
-	// TODO: This is missing some
-	List<string> _posNames = new() { "Factor", "AnchorEnd", "Center", "Absolute" };
-	List<string> _dimNames = new() { "Factor", "Fill", "Absolute" };
-
 	void UpdateSettings (View view)
 	{
-		string x = view.X.ToString ();
-		string y = view.Y.ToString ();
+		var x = view.X.ToString ();
+		var y = view.Y.ToString ();
 		_xRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.Where (s => x.Contains (s)).First ());
 		_yRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.Where (s => y.Contains (s)).First ());
 		_xText.Text = $"{view.Frame.X}";
 		_yText.Text = $"{view.Frame.Y}";
 
-		string w = view.Width.ToString ();
-		string h = view.Height.ToString ();
+		var w = view.Width.ToString ();
+		var h = view.Height.ToString ();
 		_wRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.Where (s => w.Contains (s)).First ());
 		_hRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.Where (s => h.Contains (s)).First ());
 		_wText.Text = $"{view.Frame.Width}";
@@ -312,7 +306,7 @@ public class AllViewsTester : Scenario {
 	{
 		var types = new List<Type> ();
 		foreach (var type in typeof (View).Assembly.GetTypes ()
-						.Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) {
+			.Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) {
 			types.Add (type);
 		}
 		types.Add (typeof (View));
@@ -341,7 +335,7 @@ public class AllViewsTester : Scenario {
 
 		// Set the colorscheme to make it stand out if is null by default
 		if (view.ColorScheme == null) {
-			view.ColorScheme = Colors.Base;
+			view.ColorScheme = Colors.ColorSchemes ["Base"];
 		}
 
 		// If the view supports a Text property, set it so we have something to look at
@@ -365,7 +359,7 @@ public class AllViewsTester : Scenario {
 
 		// If the view supports a Source property, set it so we have something to look at
 		if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource)) {
-			var source = new ListWrapper (new List<string> () { "Test Text #1", "Test Text #2", "Test Text #3" });
+			var source = new ListWrapper (new List<string> { "Test Text #1", "Test Text #2", "Test Text #3" });
 			view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
 		}
 
@@ -388,10 +382,10 @@ public class AllViewsTester : Scenario {
 		//view.X = Pos.Center ();
 		//view.Y = Pos.Center ();
 		if (view.Width == null || view.Frame.Width == 0) {
-			view.Width = Dim.Fill();
+			view.Width = Dim.Fill ();
 		}
 		if (view.Height == null || view.Frame.Height == 0) {
-			view.Height = Dim.Fill();
+			view.Height = Dim.Fill ();
 		}
 		UpdateSettings (view);
 		UpdateTitle (view);

+ 1 - 1
UICatalog/Scenarios/AutoSizeAndDirectionText.cs

@@ -8,7 +8,7 @@ namespace UICatalog.Scenarios {
 		{
 			var text = "Hello World";
 			var wideText = "Hello World 你";
-			var color = Colors.Dialog;
+			var color = Colors.ColorSchemes ["Dialog"];
 
 			var labelH = new Label (text, TextDirection.LeftRight_TopBottom) {
 				X = 1,

+ 3 - 3
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -169,7 +169,7 @@ namespace UICatalog.Scenarios {
 				Width = Dim.Percent (80);
 				Height = Dim.Percent (50);
 
-				ColorScheme = Colors.Base;
+				ColorScheme = Colors.ColorSchemes ["Base"];
 
 				var label = new Label ("Worker collection Log") {
 					X = Pos.Center (),
@@ -308,14 +308,14 @@ namespace UICatalog.Scenarios {
 				Width = Dim.Percent (85);
 				Height = Dim.Percent (85);
 
-				ColorScheme = Colors.Dialog;
+				ColorScheme = Colors.ColorSchemes ["Dialog"];
 
 				Title = "Run Worker";
 
 				label = new Label ("Press start to do the work or close to quit.") {
 					X = Pos.Center (),
 					Y = 1,
-					ColorScheme = Colors.Dialog
+					ColorScheme = Colors.ColorSchemes ["Dialog"]
 				};
 				Add (label);
 

+ 3 - 2
UICatalog/Scenarios/BasicColors.cs

@@ -91,9 +91,10 @@ namespace UICatalog.Scenarios {
 					var fore = e.MouseEvent.View.GetNormalColor ().Foreground;
 					var back = e.MouseEvent.View.GetNormalColor ().Background;
 					lblForeground.Text = $"#{fore.R:X2}{fore.G:X2}{fore.B:X2} {fore.ColorName} ";
-					viewForeground.ColorScheme.Normal = new Attribute (fore, fore);
+					viewForeground.ColorScheme = new ColorScheme (viewForeground.ColorScheme) { Normal = new Attribute (fore, fore) };
+
 					lblBackground.Text = $"#{back.R:X2}{back.G:X2}{back.B:X2} {back.ColorName} ";
-					viewBackground.ColorScheme.Normal = new Attribute (back, back);
+					viewBackground.ColorScheme = new ColorScheme (viewBackground.ColorScheme) { Normal = new Attribute (back, back) };
 				}
 			};
 		}

+ 12 - 13
UICatalog/Scenarios/Buttons.cs

@@ -3,8 +3,7 @@ using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
 [ScenarioMetadata (Name: "Buttons", Description: "Demonstrates all sorts of Buttons.")]
-[ScenarioCategory ("Controls")]
-[ScenarioCategory ("Layout")]
+[ScenarioCategory ("Controls"), ScenarioCategory ("Layout")]
 public class Buttons : Scenario {
 	public override void Setup ()
 	{
@@ -32,7 +31,7 @@ public class Buttons : Scenario {
 		defaultButton.Clicked += (s, e) => Application.RequestStop ();
 		Win.Add (defaultButton);
 
-		var swapButton = new Button (50, 0, "Swap Default (Absolute Layout)");
+		var swapButton = new Button (50, 0, "S_wap Default (Absolute Layout)");
 		swapButton.Clicked += (s, e) => {
 			defaultButton.IsDefault = !defaultButton.IsDefault;
 			swapButton.IsDefault = !swapButton.IsDefault;
@@ -58,7 +57,7 @@ public class Buttons : Scenario {
 		//With this method there is no need to call Application.TopReady += () => Application.TopRedraw (Top.Bounds);
 		var x = Pos.Right (colorButtonsLabel) + 2;
 		foreach (var colorScheme in Colors.ColorSchemes) {
-			var colorButton = new Button ($"{colorScheme.Key}") {
+			var colorButton = new Button ($"_{colorScheme.Key}") {
 				ColorScheme = colorScheme.Value,
 				//X = Pos.Right (prev) + 2,
 				X = x,
@@ -99,7 +98,7 @@ public class Buttons : Scenario {
 		var removeButton = new Button ("Remove this button") {
 			X = 2,
 			Y = Pos.Bottom (button) + 1,
-			ColorScheme = Colors.Error
+			ColorScheme = Colors.ColorSchemes ["Error"]
 		};
 		Win.Add (removeButton);
 		// This in interesting test case because `moveBtn` and below are laid out relative to this one!
@@ -119,11 +118,11 @@ public class Buttons : Scenario {
 		Win.Add (computedFrame);
 
 		// Demonstrates how changing the View.Frame property can move Views
-		var moveBtn = new Button ("Move This \u263b Button _via Pos") {
+		var moveBtn = new Button ("Move This \u263b Button v_ia Pos") {
 			X = 0,
 			Y = Pos.Center () - 1,
 			Width = 30,
-			ColorScheme = Colors.Error,
+			ColorScheme = Colors.ColorSchemes ["Error"],
 		};
 		moveBtn.Clicked += (s, e) => {
 			moveBtn.X = moveBtn.Frame.X + 5;
@@ -137,7 +136,7 @@ public class Buttons : Scenario {
 			X = 0,
 			Y = Pos.Center () + 1,
 			Width = 30,
-			ColorScheme = Colors.Error,
+			ColorScheme = Colors.ColorSchemes ["Error"],
 		};
 		sizeBtn.Clicked += (s, e) => {
 			sizeBtn.Width = sizeBtn.Frame.Width + 5;
@@ -155,7 +154,7 @@ public class Buttons : Scenario {
 
 		// Demonstrates how changing the View.Frame property can move Views
 		var moveBtnA = new Button (0, 0, "Move This Button via Frame") {
-			ColorScheme = Colors.Error,
+			ColorScheme = Colors.ColorSchemes ["Error"],
 		};
 		moveBtnA.Clicked += (s, e) => {
 			moveBtnA.Frame = new Rect (moveBtnA.Frame.X + 5, moveBtnA.Frame.Y, moveBtnA.Frame.Width, moveBtnA.Frame.Height);
@@ -163,8 +162,8 @@ public class Buttons : Scenario {
 		absoluteFrame.Add (moveBtnA);
 
 		// Demonstrates how changing the View.Frame property can SIZE Views (#583)
-		var sizeBtnA = new Button (0, 2, " ~  s  gui.cs   master ↑10 = Со_хранить") {
-			ColorScheme = Colors.Error,
+		var sizeBtnA = new Button (0, 2, " ~  s  gui.cs   master ↑_10 = Сохранить") {
+			ColorScheme = Colors.ColorSchemes ["Error"],
 		};
 		sizeBtnA.Clicked += (s, e) => {
 			sizeBtnA.Frame = new Rect (sizeBtnA.Frame.X, sizeBtnA.Frame.Y, sizeBtnA.Frame.Width + 5, sizeBtnA.Frame.Height);
@@ -215,7 +214,7 @@ public class Buttons : Scenario {
 			X = 2,
 			Y = Pos.Bottom (radioGroup) + 1,
 			Width = Dim.Width (computedFrame) - 2,
-			ColorScheme = Colors.TopLevel,
+			ColorScheme = Colors.ColorSchemes ["TopLevel"],
 		};
 		moveHotKeyBtn.Clicked += (s, e) => {
 			moveHotKeyBtn.Text = MoveHotkey (moveHotKeyBtn.Text);
@@ -227,7 +226,7 @@ public class Buttons : Scenario {
 			X = Pos.Left (absoluteFrame) + 1,
 			Y = Pos.Bottom (radioGroup) + 1,
 			Width = Dim.Width (absoluteFrame) - 2, // BUGBUG: Not always the width isn't calculated correctly.
-			ColorScheme = Colors.TopLevel,
+			ColorScheme = Colors.ColorSchemes ["TopLevel"],
 		};
 		moveUnicodeHotKeyBtn.Clicked += (s, e) => {
 			moveUnicodeHotKeyBtn.Text = MoveHotkey (moveUnicodeHotKeyBtn.Text);

+ 2 - 2
UICatalog/Scenarios/CharacterMap.cs

@@ -36,7 +36,7 @@ public class CharacterMap : Scenario {
 	public override void Init ()
 	{
 		Application.Init ();
-		Application.Top.ColorScheme = Colors.Base;
+		Application.Top.ColorScheme = Colors.ColorSchemes ["Base"];
 	}
 
 	public override void Setup ()
@@ -330,7 +330,7 @@ class CharMap : ScrollView {
 
 	public CharMap ()
 	{
-		ColorScheme = Colors.Dialog;
+		ColorScheme = Colors.ColorSchemes ["Dialog"];
 		CanFocus = true;
 		ContentSize = new Size (RowWidth, (int)((MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)) * _rowHeight));
 

+ 6 - 6
UICatalog/Scenarios/Clipping.cs

@@ -10,7 +10,7 @@ namespace UICatalog.Scenarios {
 		public override void Init ()
 		{
 			Application.Init ();
-			Application.Top.ColorScheme = Colors.Base;
+			Application.Top.ColorScheme = Colors.ColorSchemes ["Base"];
 		}
 
 		public override void Setup ()
@@ -21,12 +21,12 @@ namespace UICatalog.Scenarios {
 			//Win.Height = Dim.Fill () - 2;
 			var label = new Label ("ScrollView (new Rect (3, 3, 50, 20)) with a 200, 100 ContentSize...") {
 				X = 0, Y = 0,
-				//ColorScheme = Colors.Dialog
+				//ColorScheme = Colors.ColorSchemes ["Dialog"]
 			};
 			Application.Top.Add (label);
 
 			var scrollView = new ScrollView (new Rect (3, 3, 50, 20));
-			scrollView.ColorScheme = Colors.Menu;
+			scrollView.ColorScheme = Colors.ColorSchemes ["Menu"];
 			scrollView.ContentSize = new Size (200, 100);
 			//ContentOffset = new Point (0, 0),
 			//scrollView.ShowVerticalScrollIndicator = true;
@@ -38,7 +38,7 @@ namespace UICatalog.Scenarios {
 				Y = 3,
 				Width = Dim.Fill (3),
 				Height = Dim.Fill (3),
-				ColorScheme = Colors.Dialog,
+				ColorScheme = Colors.ColorSchemes ["Dialog"],
 				Id = "1"
 			};
 
@@ -48,7 +48,7 @@ namespace UICatalog.Scenarios {
 				Y = 3,
 				Width = Dim.Fill (3),
 				Height = Dim.Fill (3),
-				ColorScheme = Colors.Error,
+				ColorScheme = Colors.ColorSchemes ["Error"],
 				Id = "2"
 			};
 			embedded1.Add (embedded2);
@@ -59,7 +59,7 @@ namespace UICatalog.Scenarios {
 				Y = 3,
 				Width = Dim.Fill (3),
 				Height = Dim.Fill (3),
-				ColorScheme = Colors.TopLevel,
+				ColorScheme = Colors.ColorSchemes ["TopLevel"],
 				Id = "3"
 			};
 

+ 1 - 1
UICatalog/Scenarios/CollectionNavigatorTester.cs

@@ -16,7 +16,7 @@ namespace UICatalog.Scenarios {
 		public override void Init ()
 		{
 			Application.Init ();
-			Application.Top.ColorScheme = Colors.Base;
+			Application.Top.ColorScheme = Colors.ColorSchemes ["Base"];
 		}
 
 		System.Collections.Generic.List<string> _items = new string [] {

+ 126 - 129
UICatalog/Scenarios/ColorPicker.cs

@@ -1,132 +1,129 @@
-using Terminal.Gui;
-using System;
-
-namespace UICatalog.Scenarios {
-	[ScenarioMetadata (Name: "Color Picker", Description: "Color Picker.")]
-	[ScenarioCategory ("Colors")]
-	[ScenarioCategory ("Controls")]
-	public class ColorPickers : Scenario {
-		/// <summary>
-		/// Foreground ColorPicker.
-		/// </summary>
-		private ColorPicker foregroundColorPicker;
-
-		/// <summary>
-		/// Background ColorPicker.
-		/// </summary>
-		private ColorPicker backgroundColorPicker;
-
-		/// <summary>
-		/// Foreground color label.
-		/// </summary>
-		private Label _foregroundColorLabel;
-
-		/// <summary>
-		/// Background color Label.
-		/// </summary>
-		private Label _backgroundColorLabel;
-
-		/// <summary>
-		/// Demo label.
-		/// </summary>
-		private View _demoView;
-
-		/// <summary>
-		/// Setup the scenario.
-		/// </summary>
-		public override void Setup ()
-		{
-			// Scenario Window's.
-			Win.Title = this.GetName ();
-
-			// Foreground ColorPicker.
-			foregroundColorPicker = new ColorPicker () {
-				Title = "Foreground Color",
-				X = 0,
-				Y = 0,
-				BoxHeight = 3,
-				BoxWidth = 6,
-				BorderStyle = LineStyle.Single
-			};
-			foregroundColorPicker.ColorChanged += ForegroundColor_ColorChanged;
-			Win.Add (foregroundColorPicker);
-
-			_foregroundColorLabel = new Label {
-				X = Pos.Left (foregroundColorPicker),
-				Y = Pos.Bottom (foregroundColorPicker) + 1
-			};
-			Win.Add (_foregroundColorLabel);
-
-			// Background ColorPicker.
-			backgroundColorPicker = new ColorPicker () { 
-				Title = "Background Color",
-				Y = 0,
-				X = 0,
-				BoxHeight = 1,
-				BoxWidth = 4,
-				BorderStyle = LineStyle.Single
-			};
-			backgroundColorPicker.X = Pos.AnchorEnd () - (Pos.Right (backgroundColorPicker) - Pos.Left (backgroundColorPicker));
-			backgroundColorPicker.ColorChanged += BackgroundColor_ColorChanged;
-			Win.Add (backgroundColorPicker);
-			_backgroundColorLabel = new Label ();
-			_backgroundColorLabel.X = Pos.AnchorEnd () - (Pos.Right (_backgroundColorLabel) - Pos.Left (_backgroundColorLabel));
-			_backgroundColorLabel.Y = Pos.Bottom (backgroundColorPicker) + 1;
-			Win.Add (_backgroundColorLabel);
-
-			// Demo Label.
-			_demoView = new View () {
-				Title = "Color Sample",
-				Text = "Lorem Ipsum",
-				TextAlignment = TextAlignment.Centered,
-				VerticalTextAlignment = VerticalTextAlignment.Middle,
-				BorderStyle = LineStyle.Heavy,
-				X = Pos.Center (),
-				Y = Pos.Center (),
-				Height = 5,
-				Width = 20
-			};
-			Win.Add (_demoView);
-
-			// Set default colors.
-			foregroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Foreground.ColorName;
-			backgroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Background.ColorName;
-			Win.Initialized += (s, e) => Win.LayoutSubviews ();
-		}
-
-		/// <summary>
-		/// Fired when foreground color is changed.
-		/// </summary>
-		private void ForegroundColor_ColorChanged (object sender, EventArgs e)
-		{
-			UpdateColorLabel (_foregroundColorLabel, foregroundColorPicker);
-			UpdateDemoLabel ();
-		}
-
-		/// <summary>
-		/// Fired when background color is changed.
-		/// </summary>
-		private void BackgroundColor_ColorChanged (object sender, EventArgs e)
-		{
-			UpdateColorLabel (_backgroundColorLabel, backgroundColorPicker);
-			UpdateDemoLabel ();
-		}
-
-		/// <summary>
-		/// Update a color label from his ColorPicker.
-		/// </summary>
-		private void UpdateColorLabel (Label label, ColorPicker colorPicker)
-		{
-			label.Clear ();
-			var color = new Color (colorPicker.SelectedColor);
-			label.Text = $"{colorPicker.SelectedColor} ({(int)colorPicker.SelectedColor}) #{color.R:X2}{color.G:X2}{color.B:X2}";
-		}
-
-		/// <summary>
-		/// Update Demo Label.
-		/// </summary>
-		private void UpdateDemoLabel () => _demoView.ColorScheme = new ColorScheme () {
-			Normal = new Attribute (foregroundColorPicker.SelectedColor, backgroundColorPicker.SelectedColor)
+using System;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Color Picker", "Color Picker.")]
+[ScenarioCategory ("Colors")]
+[ScenarioCategory ("Controls")]
+public class ColorPickers : Scenario {
+
+	/// <summary>
+	/// Background color Label.
+	/// </summary>
+	Label _backgroundColorLabel;
+
+	/// <summary>
+	/// Demo label.
+	/// </summary>
+	View _demoView;
+
+	/// <summary>
+	/// Foreground color label.
+	/// </summary>
+	Label _foregroundColorLabel;
+
+	/// <summary>
+	/// Background ColorPicker.
+	/// </summary>
+	ColorPicker backgroundColorPicker;
+
+	/// <summary>
+	/// Foreground ColorPicker.
+	/// </summary>
+	ColorPicker foregroundColorPicker;
+
+	/// <summary>
+	/// Setup the scenario.
+	/// </summary>
+	public override void Setup ()
+	{
+		// Scenario Window's.
+		Win.Title = GetName ();
+
+		// Foreground ColorPicker.
+		foregroundColorPicker = new ColorPicker {
+			Title = "Foreground Color",
+			BorderStyle = LineStyle.Single
 		};
+		foregroundColorPicker.ColorChanged += ForegroundColor_ColorChanged;
+		Win.Add (foregroundColorPicker);
+
+		_foregroundColorLabel = new Label {
+			X = Pos.Left (foregroundColorPicker),
+			Y = Pos.Bottom (foregroundColorPicker) + 1
+		};
+		Win.Add (_foregroundColorLabel);
+
+		// Background ColorPicker.
+		backgroundColorPicker = new ColorPicker {
+			Title = "Background Color",
+			Y = Pos.Center (),
+			X = Pos.Center (),
+			BoxHeight = 1,
+			BoxWidth = 4,
+			BorderStyle = LineStyle.Single
+		};
+		//backgroundColorPicker.X = Pos.AnchorEnd () - (Pos.Right (backgroundColorPicker) - Pos.Left (backgroundColorPicker));
+		backgroundColorPicker.ColorChanged += BackgroundColor_ColorChanged;
+		Win.Add (backgroundColorPicker);
+		_backgroundColorLabel = new Label ();
+		_backgroundColorLabel.X = Pos.AnchorEnd () - (Pos.Right (_backgroundColorLabel) - Pos.Left (_backgroundColorLabel));
+		_backgroundColorLabel.Y = Pos.Bottom (backgroundColorPicker) + 1;
+		Win.Add (_backgroundColorLabel);
+
+		// Demo Label.
+		_demoView = new View {
+			Title = "Color Sample",
+			Text = "Lorem Ipsum",
+			TextAlignment = TextAlignment.Centered,
+			VerticalTextAlignment = VerticalTextAlignment.Middle,
+			BorderStyle = LineStyle.Heavy,
+			X = Pos.Center (),
+			Y = Pos.Center (),
+			Height = 5,
+			Width = 20
+		};
+		Win.Add (_demoView);
+
+		// Set default colors.
+		foregroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Foreground.ColorName;
+		backgroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Background.ColorName;
+		Win.Initialized += (s, e) => Win.LayoutSubviews ();
+	}
+
+	/// <summary>
+	/// Fired when foreground color is changed.
+	/// </summary>
+	void ForegroundColor_ColorChanged (object sender, EventArgs e)
+	{
+		UpdateColorLabel (_foregroundColorLabel, foregroundColorPicker);
+		UpdateDemoLabel ();
 	}
+
+	/// <summary>
+	/// Fired when background color is changed.
+	/// </summary>
+	void BackgroundColor_ColorChanged (object sender, EventArgs e)
+	{
+		UpdateColorLabel (_backgroundColorLabel, backgroundColorPicker);
+		UpdateDemoLabel ();
+	}
+
+	/// <summary>
+	/// Update a color label from his ColorPicker.
+	/// </summary>
+	void UpdateColorLabel (Label label, ColorPicker colorPicker)
+	{
+		label.Clear ();
+		var color = new Color (colorPicker.SelectedColor);
+		label.Text = $"{colorPicker.SelectedColor} ({(int)colorPicker.SelectedColor}) #{color.R:X2}{color.G:X2}{color.B:X2}";
+	}
+
+	/// <summary>
+	/// Update Demo Label.
+	/// </summary>
+	void UpdateDemoLabel () => _demoView.ColorScheme = new ColorScheme {
+		Normal = new Attribute (foregroundColorPicker.SelectedColor, backgroundColorPicker.SelectedColor)
+	};
 }

+ 1 - 1
UICatalog/Scenarios/ComboBoxIteration.cs

@@ -24,7 +24,7 @@ namespace UICatalog.Scenarios {
 			Win.Add (listview);
 
 			var lbComboBox = new Label () {
-				ColorScheme = Colors.TopLevel,
+				ColorScheme = Colors.ColorSchemes ["TopLevel"],
 				X = Pos.Right (lbListView) + 1,
 				Width = Dim.Percent (40)
 			};

+ 13 - 13
UICatalog/Scenarios/ComputedLayout.cs

@@ -33,7 +33,7 @@ namespace UICatalog.Scenarios {
 				Y = 0,
 				Width = Dim.Fill (),
 				Height = 1,
-				ColorScheme = Colors.Error
+				ColorScheme = Colors.ColorSchemes ["Error"]
 			};
 
 			Application.Top.Add (horizontalRuler);
@@ -47,7 +47,7 @@ namespace UICatalog.Scenarios {
 				Y = 0,
 				Width = 1,
 				Height = Dim.Fill (),
-				ColorScheme = Colors.Error
+				ColorScheme = Colors.ColorSchemes ["Error"]
 			};
 
 			Application.Top.LayoutComplete += (s, a) => {
@@ -88,10 +88,10 @@ namespace UICatalog.Scenarios {
 			string txt = "Resize the terminal to see computed layout in action.";
 			var labelList = new List<Label> ();
 			labelList.Add (new Label ($"The lines below show different TextAlignments"));
-			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
-			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
-			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
-			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.ColorSchemes ["Dialog"] });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.ColorSchemes ["Dialog"] });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.ColorSchemes ["Dialog"] });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.ColorSchemes ["Dialog"] });
 			subWin.Add (labelList.ToArray ());
 
 			var frameView = new FrameView () {
@@ -107,10 +107,10 @@ namespace UICatalog.Scenarios {
 			i = 1;
 			labelList = new List<Label> ();
 			labelList.Add (new Label ($"The lines below show different TextAlignments"));
-			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
-			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
-			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
-			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.ColorSchemes ["Dialog"] });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.ColorSchemes ["Dialog"] });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.ColorSchemes ["Dialog"] });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.ColorSchemes ["Dialog"] });
 			frameView.Add (labelList.ToArray ());
 			Application.Top.Add (frameView);
 
@@ -132,7 +132,7 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Percent (50),
 				Width = Dim.Percent (80),
 				Height = Dim.Percent (10),
-				ColorScheme = Colors.TopLevel,
+				ColorScheme = Colors.ColorSchemes ["TopLevel"],
 			};
 			textView.Text = $"This TextView should horizontally & vertically centered and \n10% of the screeen height, and 80% of its width.";
 			Application.Top.Add (textView);
@@ -242,7 +242,7 @@ namespace UICatalog.Scenarios {
 			// This is intentionally convoluted to illustrate potential bugs.
 			var anchorEndLabel1 = new Label ("This Label should be the 2nd to last line (AnchorEnd (2)).") {
 				TextAlignment = Terminal.Gui.TextAlignment.Centered,
-				ColorScheme = Colors.Menu,
+				ColorScheme = Colors.ColorSchemes ["Menu"],
 				Width = Dim.Fill (5),
 				X = 5,
 				Y = Pos.AnchorEnd (2)
@@ -253,7 +253,7 @@ namespace UICatalog.Scenarios {
 			// This is intentionally convoluted to illustrate potential bugs.
 			var anchorEndLabel2 = new TextField ("This TextField should be the 3rd to last line (AnchorEnd (2) - 1).") {
 				TextAlignment = Terminal.Gui.TextAlignment.Left,
-				ColorScheme = Colors.Menu,
+				ColorScheme = Colors.ColorSchemes ["Menu"],
 				Width = Dim.Fill (5),
 				X = 5,
 				Y = Pos.AnchorEnd (2) - 1 // Pos.Combine

+ 21 - 0
UICatalog/Scenarios/DatePickers.cs

@@ -0,0 +1,21 @@
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+[ScenarioMetadata (Name: "Date Picker", Description: "Demonstrates how to use DatePicker class")]
+[ScenarioCategory ("Controls")]
+[ScenarioCategory ("DateTime")]
+public class DatePickers : Scenario {
+
+
+	public override void Setup ()
+	{
+		var datePicker = new DatePicker () {
+			Y = Pos.Center (),
+			X = Pos.Center ()
+		};
+
+
+		Win.Add (datePicker);
+	}
+}
+

+ 6 - 7
UICatalog/Scenarios/Dialogs.cs

@@ -108,14 +108,14 @@ namespace UICatalog.Scenarios {
 			};
 			frame.Add (label);
 
-			var styleRadioGroup = new RadioGroup (new string [] { "Center", "Justify", "Left", "Right" }) {
+			var styleRadioGroup = new RadioGroup (new string [] { "_Center", "_Justify", "_Left", "_Right" }) {
 				X = Pos.Right (label) + 1,
 				Y = Pos.Top (label),
 			};
 			frame.Add (styleRadioGroup);
 
 			frame.ValidatePosDim = true;
-			void Top_Loaded (object sender, EventArgs args)
+			void Top_LayoutComplete (object sender, EventArgs args)
 			{
 				frame.Height =
 					widthEdit.Frame.Height +
@@ -123,10 +123,9 @@ namespace UICatalog.Scenarios {
 					titleEdit.Frame.Height +
 					numButtonsEdit.Frame.Height +
 					glyphsNotWords.Frame.Height +
-					styleRadioGroup.Frame.Height;
-				Application.Top.Loaded -= Top_Loaded;
+					styleRadioGroup.Frame.Height + frame.GetAdornmentsThickness().Vertical;
 			}
-			Application.Top.Loaded += Top_Loaded;
+			Application.Top.LayoutComplete += Top_LayoutComplete;
 
 			Win.Add (frame);
 
@@ -143,14 +142,14 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Bottom (frame) + 5,
 				Width = 25,
 				Height = 1,
-				ColorScheme = Colors.Error,
+				ColorScheme = Colors.ColorSchemes ["Error"],
 			};
 			// glyphsNotWords
 			// false:var btnText = new [] { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" };
 			// true: var btnText = new [] { "0", "\u2780", "➁", "\u2783", "\u2784", "\u2785", "\u2786", "\u2787", "\u2788", "\u2789" };
 			// \u2781 is ➁ dingbats \ufb70 is	
 
-			var showDialogButton = new Button ("Show Dialog") {
+			var showDialogButton = new Button ("_Show Dialog") {
 				X = Pos.Center (),
 				Y = Pos.Bottom (frame) + 2,
 				IsDefault = true,

+ 2 - 2
UICatalog/Scenarios/DynamicMenuBar.cs

@@ -140,7 +140,7 @@ namespace UICatalog.Scenarios {
 				_frmMenu.Add (_btnNext);
 
 				var _lblMenuBar = new Label () {
-					ColorScheme = Colors.Dialog,
+					ColorScheme = Colors.ColorSchemes ["Dialog"],
 					TextAlignment = TextAlignment.Centered,
 					X = Pos.Right (_btnPrevious) + 1,
 					Y = Pos.Top (_btnPrevious),
@@ -166,7 +166,7 @@ namespace UICatalog.Scenarios {
 				_frmMenu.Add (_btnPreviowsParent);
 
 				_lstMenus = new ListView (new List<DynamicMenuItemList> ()) {
-					ColorScheme = Colors.Dialog,
+					ColorScheme = Colors.ColorSchemes ["Dialog"],
 					X = Pos.Right (_btnPrevious) + 1,
 					Y = Pos.Top (_btnPrevious) + 2,
 					Width = _lblMenuBar.Width,

+ 1 - 1
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -105,7 +105,7 @@ public class DynamicStatusBar : Scenario {
 			_frmStatusBar.Add (_btnAdd);
 
 			_lstItems = new ListView (new List<DynamicStatusItemList> ()) {
-				ColorScheme = Colors.Dialog,
+				ColorScheme = Colors.ColorSchemes ["Dialog"],
 				Y = Pos.Top (_btnAddStatusBar) + 2,
 				Width = Dim.Fill () - Dim.Width (_btnAdd) - 1,
 				Height = Dim.Fill (),

+ 3 - 3
UICatalog/Scenarios/Editor.cs

@@ -741,7 +741,7 @@ namespace UICatalog.Scenarios {
 				Title = isFind ? "Find" : "Replace",
 				X = Win.Bounds.Width / 2 - 30,
 				Y = Win.Bounds.Height / 2 - 10,
-				ColorScheme = Colors.TopLevel
+				ColorScheme = Colors.ColorSchemes ["TopLevel"]
 			};
 
 			_tabView = new TabView () {
@@ -751,9 +751,9 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill ()
 			};
 
-			_tabView.AddTab (new Tab ("Find", FindTab ()), isFind);
+			_tabView.AddTab (new Tab () { DisplayText = "Find", View = FindTab () }, isFind);
 			var replace = ReplaceTab ();
-			_tabView.AddTab (new Tab ("Replace", replace), !isFind);
+			_tabView.AddTab (new Tab () { DisplayText = "Replace", View = replace }, !isFind);
 			_tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusFirst ();
 			_winDialog.Add (_tabView);
 

+ 0 - 399
UICatalog/Scenarios/Frames.cs

@@ -1,399 +0,0 @@
-using System;
-using System.Linq;
-using Terminal.Gui;
-
-namespace UICatalog.Scenarios {
-	[ScenarioMetadata (Name: "Frames Demo", Description: "Demonstrates Margin, Border, and Padding on Views.")]
-	[ScenarioCategory ("Layout")]
-	[ScenarioCategory ("Borders")]
-	public class Frames : Scenario {
-		public class FrameEditor : View {
-			private Thickness _thickness;
-			private TextField _topEdit;
-			private TextField _leftEdit;
-			private TextField _rightEdit;
-			private TextField _bottomEdit;
-			private bool _isUpdating;
-
-			private ColorPicker _foregroundColorPicker;
-			private ColorPicker _backgroundColorPicker;
-
-			public Terminal.Gui.Attribute Color { get; set; }
-
-			public Thickness Thickness {
-				get => _thickness;
-				set {
-					if (_isUpdating) {
-						return;
-					}
-					_thickness = value;
-					ThicknessChanged?.Invoke (this, new ThicknessEventArgs () { Thickness = Thickness });
-					if (IsInitialized) {
-						_isUpdating = true;
-						if (_topEdit.Text != _thickness.Top.ToString ()) {
-							_topEdit.Text = _thickness.Top.ToString ();
-						}
-						if (_leftEdit.Text != _thickness.Left.ToString ()) {
-							_leftEdit.Text = _thickness.Left.ToString ();
-						}
-						if (_rightEdit.Text != _thickness.Right.ToString ()) {
-							_rightEdit.Text = _thickness.Right.ToString ();
-						}
-						if (_bottomEdit.Text != _thickness.Bottom.ToString ()) {
-							_bottomEdit.Text = _thickness.Bottom.ToString ();
-						}
-						_isUpdating = false;
-					}
-				}
-			}
-
-			public event EventHandler<ThicknessEventArgs> ThicknessChanged;
-			public event EventHandler<Terminal.Gui.Attribute> AttributeChanged;
-
-			public FrameEditor ()
-			{
-				Margin.Thickness = new Thickness (0);
-				BorderStyle = LineStyle.Double;
-				Initialized += FrameEditor_Initialized; ;
-			}
-
-			void FrameEditor_Initialized (object sender, EventArgs e)
-			{
-				var editWidth = 3;
-
-				_topEdit = new TextField ("") {
-					X = Pos.Center (),
-					Y = 0,
-					Width = editWidth
-				};
-				_topEdit.TextChanging += Edit_TextChanging;
-				Add (_topEdit);
-
-				_leftEdit = new TextField ("") {
-					X = Pos.Left (_topEdit) - editWidth,
-					Y = Pos.Bottom (_topEdit),
-					Width = editWidth
-				};
-				_leftEdit.TextChanging += Edit_TextChanging;
-				Add (_leftEdit);
-
-				_rightEdit = new TextField ("") {
-					X = Pos.Right (_topEdit),
-					Y = Pos.Bottom (_topEdit),
-					Width = editWidth
-				};
-				_rightEdit.TextChanging += Edit_TextChanging;
-				Add (_rightEdit);
-
-				_bottomEdit = new TextField ("") {
-					X = Pos.Center (),
-					Y = Pos.Bottom (_leftEdit),
-					Width = editWidth
-				};
-				_bottomEdit.TextChanging += Edit_TextChanging;
-				Add (_bottomEdit);
-
-				var copyTop = new Button ("Copy Top") {
-					X = Pos.Center () + 1,
-					Y = Pos.Bottom (_bottomEdit)
-				};
-				copyTop.Clicked += (s, e) => {
-					Thickness = new Thickness (Thickness.Top);
-					if (string.IsNullOrEmpty (_topEdit.Text)) {
-						_topEdit.Text = "0";
-					}
-					_bottomEdit.Text = _leftEdit.Text = _rightEdit.Text = _topEdit.Text;
-				};
-				Add (copyTop);
-
-				// Foreground ColorPicker.
-				_foregroundColorPicker = new ColorPicker () {
-					Title = "FG",
-					BoxWidth = 1,
-					BoxHeight = 1,
-					X = -1,
-					Y = Pos.Bottom (copyTop) + 1,
-					BorderStyle = LineStyle.Single,
-					SuperViewRendersLineCanvas = true
-				};
-				_foregroundColorPicker.ColorChanged += (o, a) =>
-					AttributeChanged?.Invoke (this,
-						new Attribute (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor));
-				Add (_foregroundColorPicker);
-
-				// Background ColorPicker.
-				_backgroundColorPicker = new ColorPicker () {
-					Title = "BG",
-					BoxWidth = 1,
-					BoxHeight = 1,
-					X = Pos.Right (_foregroundColorPicker) - 1,
-					Y = Pos.Top (_foregroundColorPicker),
-					BorderStyle = LineStyle.Single,
-					SuperViewRendersLineCanvas = true
-				};
-
-				_backgroundColorPicker.ColorChanged += (o, a) =>
-					AttributeChanged?.Invoke (this,
-						new Terminal.Gui.Attribute (
-							_foregroundColorPicker.SelectedColor,
-							_backgroundColorPicker.SelectedColor));
-				Add (_backgroundColorPicker);
-
-				_topEdit.Text = $"{Thickness.Top}";
-				_leftEdit.Text = $"{Thickness.Left}";
-				_rightEdit.Text = $"{Thickness.Right}";
-				_bottomEdit.Text = $"{Thickness.Bottom}";
-
-				LayoutSubviews ();
-				Height = GetFramesThickness ().Vertical + 4 + 4;
-				Width = GetFramesThickness ().Horizontal + _foregroundColorPicker.Frame.Width * 2 - 3;
-			}
-
-			private void Edit_TextChanging (object sender, TextChangingEventArgs e)
-			{
-				try {
-					if (string.IsNullOrEmpty (e.NewText.ToString ())) {
-						e.Cancel = true;
-						((TextField)sender).Text = "0";
-						return;
-					}
-					switch (sender.ToString ()) {
-					case var s when s == _topEdit.ToString ():
-						Thickness = new Thickness (Thickness.Left,
-							int.Parse (e.NewText), Thickness.Right,
-							Thickness.Bottom);
-						break;
-					case var s when s == _leftEdit.ToString ():
-						Thickness = new Thickness (int.Parse (e.NewText),
-							Thickness.Top, Thickness.Right,
-							Thickness.Bottom);
-						break;
-					case var s when s == _rightEdit.ToString ():
-						Thickness = new Thickness (Thickness.Left,
-							Thickness.Top, int.Parse (e.NewText),
-							Thickness.Bottom);
-						break;
-					case var s when s == _bottomEdit.ToString ():
-						Thickness = new Thickness (Thickness.Left,
-							Thickness.Top, Thickness.Right,
-							int.Parse (e.NewText));
-						break;
-					}
-				} catch {
-					if (!string.IsNullOrEmpty (e.NewText)) {
-						e.Cancel = true;
-					}
-				}
-			}
-		}
-
-		public class FramesEditor : Window {
-			private View _viewToEdit;
-			private FrameEditor _marginEditor;
-			private FrameEditor _borderEditor;
-			private FrameEditor _paddingEditor;
-			private String _origTitle = string.Empty;
-
-			public FramesEditor ()
-			{
-			}
-
-			public View ViewToEdit {
-				get {
-					return _viewToEdit;
-				}
-				set {
-					_origTitle = value.Title;
-					_viewToEdit = value;
-
-					_viewToEdit.Margin.ColorScheme = new ColorScheme (Colors.ColorSchemes ["Toplevel"]);
-					_marginEditor = new FrameEditor () {
-						X = 0,
-						Y = 0,
-						Title = "Margin",
-						Thickness = _viewToEdit.Margin.Thickness,
-						SuperViewRendersLineCanvas = true
-					};
-					_marginEditor.ThicknessChanged += Editor_ThicknessChanged;
-					_marginEditor.AttributeChanged += Editor_AttributeChanged;
-					Add (_marginEditor);
-
-					_viewToEdit.Border.ColorScheme = new ColorScheme (Colors.ColorSchemes ["Base"]);
-					_borderEditor = new FrameEditor () {
-						X = Pos.Left (_marginEditor),
-						Y = Pos.Bottom (_marginEditor),
-						Title = "Border",
-						Thickness = _viewToEdit.Border.Thickness,
-						SuperViewRendersLineCanvas = true
-					};
-					_borderEditor.ThicknessChanged += Editor_ThicknessChanged;
-					_borderEditor.AttributeChanged += Editor_AttributeChanged;
-					Add (_borderEditor);
-
-					_viewToEdit.Padding.ColorScheme = new ColorScheme (Colors.ColorSchemes ["Error"]);
-
-					var borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast<LineStyle> ().ToList ();
-					var rbBorderStyle = new RadioGroup (borderStyleEnum.Select (
-						e => e.ToString ()).ToArray ()) {
-
-						X = Pos.Right (_borderEditor) - 1,
-						Y = Pos.Top (_borderEditor),
-						SelectedItem = (int)_viewToEdit.Border.BorderStyle,
-						BorderStyle = LineStyle.Double,
-						Title = "Border Style",
-						SuperViewRendersLineCanvas = true
-					};
-					Add (rbBorderStyle);
-
-					rbBorderStyle.SelectedItemChanged += (s, e) => {
-						var prevBorderStyle = _viewToEdit.BorderStyle;
-						_viewToEdit.Border.BorderStyle = (LineStyle)e.SelectedItem;
-						if (_viewToEdit.Border.BorderStyle == LineStyle.None) {
-							_viewToEdit.Border.Thickness = new Thickness (0);
-						} else if (prevBorderStyle == LineStyle.None && _viewToEdit.Border.BorderStyle != LineStyle.None) {
-							_viewToEdit.Border.Thickness = new Thickness (1);
-						}
-						_borderEditor.Thickness = new Thickness (_viewToEdit.Border.Thickness.Left, _viewToEdit.Border.Thickness.Top,
-							_viewToEdit.Border.Thickness.Right, _viewToEdit.Border.Thickness.Bottom);
-						_viewToEdit.SetNeedsDisplay ();
-						LayoutSubviews ();
-					};
-
-					var ckbTitle = new CheckBox ("Show Title") {
-						BorderStyle = LineStyle.Double,
-						X = Pos.Left (_borderEditor),
-						Y = Pos.Bottom (_borderEditor) - 1,
-						Width = Dim.Width (_borderEditor),
-						Checked = true,
-						SuperViewRendersLineCanvas = true
-					};
-					ckbTitle.Toggled += (sender, args) => {
-						if (ckbTitle.Checked == true) {
-							_viewToEdit.Title = _origTitle;
-						} else {
-							_viewToEdit.Title = string.Empty;
-						}
-					};
-					Add (ckbTitle);
-
-					_paddingEditor = new FrameEditor () {
-						X = Pos.Left (_borderEditor),
-						Y = Pos.Bottom (rbBorderStyle),
-						Title = "Padding",
-						Thickness = _viewToEdit.Padding.Thickness,
-						SuperViewRendersLineCanvas = true
-					};
-					_paddingEditor.ThicknessChanged += Editor_ThicknessChanged;
-					_paddingEditor.AttributeChanged += Editor_AttributeChanged;
-					Add (_paddingEditor);
-					Add (_viewToEdit);
-
-					_viewToEdit.LayoutComplete += (s, e) => {
-						if (ckbTitle.Checked == true) {
-							_viewToEdit.Title = _origTitle;
-						} else {
-							_viewToEdit.Title = string.Empty;
-						}
-					};
-				}
-			}
-
-			private void Editor_AttributeChanged (object sender, Terminal.Gui.Attribute attr)
-			{
-				switch (sender.ToString ()) {
-				case var s when s == _marginEditor.ToString ():
-					_viewToEdit.Margin.ColorScheme = new ColorScheme (_viewToEdit.Margin.ColorScheme) { Normal = attr };
-					break;
-				case var s when s == _borderEditor.ToString ():
-					_viewToEdit.Border.ColorScheme = new ColorScheme (_viewToEdit.Border.ColorScheme) { Normal = attr };
-					break;
-				case var s when s == _paddingEditor.ToString ():
-					_viewToEdit.Padding.ColorScheme = new ColorScheme (_viewToEdit.Padding.ColorScheme) { Normal = attr };
-					break;
-				}
-			}
-
-			private void Editor_ThicknessChanged (object sender, ThicknessEventArgs e)
-			{
-				try {
-					switch (sender.ToString ()) {
-					case var s when s == _marginEditor.ToString ():
-						_viewToEdit.Margin.Thickness = e.Thickness;
-						break;
-					case var s when s == _borderEditor.ToString ():
-						_viewToEdit.Border.Thickness = e.Thickness;
-						break;
-					case var s when s == _paddingEditor.ToString ():
-						_viewToEdit.Padding.Thickness = e.Thickness;
-						break;
-					}
-				} catch {
-					switch (sender.ToString ()) {
-					case var s when s == _marginEditor.ToString ():
-						_viewToEdit.Margin.Thickness = e.PreviousThickness;
-						break;
-					case var s when s == _borderEditor.ToString ():
-						_viewToEdit.Border.Thickness = e.PreviousThickness;
-						break;
-					case var s when s == _paddingEditor.ToString ():
-						_viewToEdit.Padding.Thickness = e.PreviousThickness;
-						break;
-					}
-				}
-			}
-		}
-
-		public override void Init ()
-		{
-			Application.Init ();
-			ConfigurationManager.Themes.Theme = Theme;
-			ConfigurationManager.Apply ();
-			Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
-
-			var view = new Window ();
-			var tf1 = new TextField ("TextField") { Width = 10 };
-
-			var button = new Button ("Press me!") {
-				X = Pos.Center (),
-				Y = Pos.Center (),
-			};
-			button.Clicked += (s, e) => MessageBox.Query (20, 7, "Hi", $"Am I a {view.GetType ().Name}?", "Yes", "No");
-			var label = new Label ($"I'm a {view.GetType ().Name}") {
-				X = Pos.Center (),
-				Y = Pos.Center () - 1,
-			};
-			var tf2 = new Button ("Button") {
-				X = Pos.AnchorEnd (10),
-				Y = Pos.AnchorEnd (1),
-				Width = 10
-			};
-			var tv = new Label () {
-				Y = Pos.AnchorEnd (2),
-				Width = 25,
-				Height = Dim.Fill (),
-				Text = "Label\nY=AnchorEnd(2),Height=Dim.Fill()"
-			};
-
-			view.Margin.Thickness = new Thickness (3);
-			view.Padding.Thickness = new Thickness (1);
-
-			view.Add (tf1, button, label, tf2, tv);
-
-			var editor = new FramesEditor () {
-				Title =$"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
-				ViewToEdit = view
-			};
-			view.X = 36;
-			view.Y = 0;
-			view.Width = Dim.Fill ();
-			view.Height = Dim.Fill ();
-
-			Application.Run (editor);
-			Application.Shutdown ();
-		}
-
-		public override void Run ()
-		{
-		}
-	}
-}

+ 568 - 519
UICatalog/Scenarios/GraphViewExample.cs

@@ -3,263 +3,307 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using Terminal.Gui;
-using Color = Terminal.Gui.Color;
 
-namespace UICatalog.Scenarios {
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Graph View", "Demos the GraphView control.")]
+[ScenarioCategory ("Controls")]
+[ScenarioCategory ("Drawing")]
+public class GraphViewExample : Scenario {
+	TextView _about;
+
+	int _currentGraph;
+	Action [] _graphs;
+
+	GraphView _graphView;
+	MenuItem _miDiags;
+	MenuItem _miShowBorder;
+	Thickness _thickness = new Thickness (1, 1, 1, 1);
+
+	public override void Setup ()
+	{
+		Win.Title = GetName ();
+		Win.Y = 1; // menu
+		Win.Height = Dim.Fill (1); // status bar
+
+		_graphs = new [] {
+			() => SetupPeriodicTableScatterPlot (), //0
+			() => SetupLifeExpectancyBarGraph (true), //1
+			() => SetupLifeExpectancyBarGraph (false), //2
+			() => SetupPopulationPyramid (), //3
+			() => SetupLineGraph (), //4
+			() => SetupSineWave (), //5
+			() => SetupDisco (), //6
+			() => MultiBarGraph () //7
+		};
+
+		var menu = new MenuBar (new MenuBarItem [] {
+			new ("_File", new MenuItem [] {
+				new ("Scatter _Plot", "", () => _graphs [_currentGraph = 0] ()),
+				new ("_V Bar Graph", "", () => _graphs [_currentGraph = 1] ()),
+				new ("_H Bar Graph", "", () => _graphs [_currentGraph = 2] ()),
+				new ("P_opulation Pyramid", "", () => _graphs [_currentGraph = 3] ()),
+				new ("_Line Graph", "", () => _graphs [_currentGraph = 4] ()),
+				new ("Sine _Wave", "", () => _graphs [_currentGraph = 5] ()),
+				new ("Silent _Disco", "", () => _graphs [_currentGraph = 6] ()),
+				new ("_Multi Bar Graph", "", () => _graphs [_currentGraph = 7] ()),
+				new ("_Quit", "", () => Quit ())
+			}),
+			new ("_View", new [] {
+				new ("Zoom _In", "", () => Zoom (0.5f)),
+				new ("Zoom _Out", "", () => Zoom (2f)),
+				new ("MarginLeft++", "", () => Margin (true, true)),
+				new ("MarginLeft--", "", () => Margin (true, false)),
+				new ("MarginBottom++", "", () => Margin (false, true)),
+				new ("MarginBottom--", "", () => Margin (false, false)),
+				_miShowBorder = new MenuItem ("_Enable Margin, Border, and Padding", "", () => ShowBorder ()) {
+					Checked = true,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miDiags = new MenuItem ("Dri_ver Diagnostics", "", () => EnableDiagnostics ()) {
+					Checked = ConsoleDriver.Diagnostics == (ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler),
+					CheckType = MenuItemCheckStyle.Checked
+				}
+			})
+		});
+		Application.Top.Add (menu);
+
+		_graphView = new GraphView {
+			X = 0,
+			Y = 0,
+			Width = Dim.Percent (70),
+			Height = Dim.Fill (),
+			BorderStyle = LineStyle.Single
+		};
+		_graphView.Border.Thickness = _thickness;
+		_graphView.Margin.Thickness = _thickness;
+		_graphView.Padding.Thickness = _thickness;
+
+		Win.Add (_graphView);
+
+		var frameRight = new FrameView ("About") {
+			X = Pos.Right (_graphView) + 1,
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
+
+		frameRight.Add (_about = new TextView {
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		});
+
+		Win.Add (frameRight);
+
+		var statusBar = new StatusBar (new StatusItem [] {
+			new (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ()),
+			new (KeyCode.CtrlMask | KeyCode.G, "~^G~ Next", () => _graphs [_currentGraph++ % _graphs.Length] ())
+		});
+		Application.Top.Add (statusBar);
+	}
 
-	[ScenarioMetadata (Name: "Graph View", Description: "Demos the GraphView control.")]
-	[ScenarioCategory ("Controls")]
-	[ScenarioCategory ("Drawing")]
-	public class GraphViewExample : Scenario {
+	void ShowBorder ()
+	{
+		_miShowBorder.Checked = !_miShowBorder.Checked;
+
+		if (_miShowBorder.Checked == true) {
+			_graphView.BorderStyle = LineStyle.Single;
+			_graphView.Border.Thickness = _thickness;
+			_graphView.Margin.Thickness = _thickness;
+			_graphView.Padding.Thickness = _thickness;
+		} else {
+			_graphView.BorderStyle = LineStyle.None;
+			_graphView.Margin.Thickness = Thickness.Empty;
+			_graphView.Padding.Thickness = Thickness.Empty;
+		}
 
-		GraphView graphView;
-		private TextView about;
+	}
 
-		int currentGraph = 0;
-		Action [] graphs;
+	void EnableDiagnostics ()
+	{
+		_miDiags.Checked = !_miDiags.Checked;
 
-		public override void Setup ()
-		{
-			Win.Title = this.GetName ();
-			Win.Y = 1; // menu
-			Win.Height = Dim.Fill (1); // status bar
-
-			graphs = new Action [] {
-				 ()=>SetupPeriodicTableScatterPlot(),    //0
-				 ()=>SetupLifeExpectancyBarGraph(true),  //1
-				 ()=>SetupLifeExpectancyBarGraph(false), //2
-				 ()=>SetupPopulationPyramid(),           //3
-				 ()=>SetupLineGraph(),                   //4
-				 ()=>SetupSineWave(),                    //5
-				 ()=>SetupDisco(),                       //6
-				 ()=>MultiBarGraph()                     //7
-			};
-
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("Scatter _Plot", "",()=>graphs[currentGraph = 0]()),
-					new MenuItem ("_V Bar Graph", "", ()=>graphs[currentGraph = 1]()),
-					new MenuItem ("_H Bar Graph", "", ()=>graphs[currentGraph = 2]()) ,
-					new MenuItem ("P_opulation Pyramid","",()=>graphs[currentGraph = 3]()),
-					new MenuItem ("_Line Graph","",()=>graphs[currentGraph = 4]()),
-					new MenuItem ("Sine _Wave","",()=>graphs[currentGraph = 5]()),
-					new MenuItem ("Silent _Disco","",()=>graphs[currentGraph = 6]()),
-					new MenuItem ("_Multi Bar Graph","",()=>graphs[currentGraph = 7]()),
-					new MenuItem ("_Quit", "", () => Quit()),
-				}),
-				new MenuBarItem ("_View", new MenuItem [] {
-					new MenuItem ("Zoom _In", "", () => Zoom(0.5f)),
-					 new MenuItem ("Zoom _Out", "", () =>  Zoom(2f)),
-					new MenuItem ("MarginLeft++", "", () => Margin(true,true)),
-					new MenuItem ("MarginLeft--", "", () => Margin(true,false)),
-					new MenuItem ("MarginBottom++", "", () => Margin(false,true)),
-					new MenuItem ("MarginBottom--", "", () => Margin(false,false)),
-				}),
-
-				});
-			Application.Top.Add (menu);
-
-			graphView = new GraphView () {
-				X = 1,
-				Y = 1,
-				Width = 60,
-				Height = 20,
-			};
-
-			Win.Add (graphView);
-
-			var frameRight = new FrameView ("About") {
-				X = Pos.Right (graphView) + 1,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-			};
-
-			frameRight.Add (about = new TextView () {
-				Width = Dim.Fill (),
-				Height = Dim.Fill ()
-			});
+		ConsoleDriver.Diagnostics = _miDiags.Checked == true ? ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler : ConsoleDriver.DiagnosticFlags.Off;
+		Application.Refresh ();
+	}
 
-			Win.Add (frameRight);
+	void MultiBarGraph ()
+	{
+		_graphView.Reset ();
 
-			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
-				new StatusItem(KeyCode.CtrlMask | KeyCode.G, "~^G~ Next", ()=>graphs[currentGraph++%graphs.Length]()),
-			});
-			Application.Top.Add (statusBar);
-		}
+		_graphView.Title = "Multi Bar";
 
-		private void MultiBarGraph ()
-		{
-			graphView.Reset ();
+		_about.Text = "Housing Expenditures by income thirds 1996-2003";
 
-			about.Text = "Housing Expenditures by income thirds 1996-2003";
+		var fore = _graphView.ColorScheme.Normal.Foreground == new Color (ColorName.Black) ? new Color (ColorName.White) : _graphView.ColorScheme.Normal.Foreground;
+		var black = new Attribute (fore, Color.Black);
+		var cyan = new Attribute (Color.BrightCyan, Color.Black);
+		var magenta = new Attribute (Color.BrightMagenta, Color.Black);
+		var red = new Attribute (Color.BrightRed, Color.Black);
 
-			var fore = graphView.ColorScheme.Normal.Foreground == new Color(ColorName.Black) ? new Color(ColorName.White) : graphView.ColorScheme.Normal.Foreground;
-			var black = new Attribute (fore, Color.Black);
-			var cyan = new Attribute (Color.BrightCyan, Color.Black);
-			var magenta = new Attribute (Color.BrightMagenta, Color.Black);
-			var red = new Attribute (Color.BrightRed, Color.Black);
+		_graphView.GraphColor = black;
 
-			graphView.GraphColor = black;
+		var series = new MultiBarSeries (3, 1, 0.25f, new [] { magenta, cyan, red });
 
-			var series = new MultiBarSeries (3, 1, 0.25f, new [] { magenta, cyan, red });
+		var stiple = CM.Glyphs.Stipple;
 
-			var stiple = CM.Glyphs.Stipple;
+		series.AddBars ("'96", stiple, 5900, 9000, 14000);
+		series.AddBars ("'97", stiple, 6100, 9200, 14800);
+		series.AddBars ("'98", stiple, 6000, 9300, 14600);
+		series.AddBars ("'99", stiple, 6100, 9400, 14950);
+		series.AddBars ("'00", stiple, 6200, 9500, 15200);
+		series.AddBars ("'01", stiple, 6250, 9900, 16000);
+		series.AddBars ("'02", stiple, 6600, 11000, 16700);
+		series.AddBars ("'03", stiple, 7000, 12000, 17000);
 
-			series.AddBars ("'96", stiple, 5900, 9000, 14000);
-			series.AddBars ("'97", stiple, 6100, 9200, 14800);
-			series.AddBars ("'98", stiple, 6000, 9300, 14600);
-			series.AddBars ("'99", stiple, 6100, 9400, 14950);
-			series.AddBars ("'00", stiple, 6200, 9500, 15200);
-			series.AddBars ("'01", stiple, 6250, 9900, 16000);
-			series.AddBars ("'02", stiple, 6600, 11000, 16700);
-			series.AddBars ("'03", stiple, 7000, 12000, 17000);
+		_graphView.CellSize = new PointF (0.25f, 1000);
+		_graphView.Series.Add (series);
+		_graphView.SetNeedsDisplay ();
 
-			graphView.CellSize = new PointF (0.25f, 1000);
-			graphView.Series.Add (series);
-			graphView.SetNeedsDisplay ();
+		_graphView.MarginLeft = 3;
+		_graphView.MarginBottom = 1;
 
-			graphView.MarginLeft = 3;
-			graphView.MarginBottom = 1;
+		_graphView.AxisY.LabelGetter = v => '$' + (v.Value / 1000f).ToString ("N0") + 'k';
 
-			graphView.AxisY.LabelGetter = (v) => '$' + (v.Value / 1000f).ToString ("N0") + 'k';
+		// Do not show x axis labels (bars draw their own labels)
+		_graphView.AxisX.Increment = 0;
+		_graphView.AxisX.ShowLabelsEvery = 0;
+		_graphView.AxisX.Minimum = 0;
 
-			// Do not show x axis labels (bars draw their own labels)
-			graphView.AxisX.Increment = 0;
-			graphView.AxisX.ShowLabelsEvery = 0;
-			graphView.AxisX.Minimum = 0;
+		_graphView.AxisY.Minimum = 0;
 
-			graphView.AxisY.Minimum = 0;
+		var legend = new LegendAnnotation (new Rect (_graphView.Bounds.Width - 20, 0, 20, 5));
+		legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (0).OverrideBarColor), "Lower Third");
+		legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (1).OverrideBarColor), "Middle Third");
+		legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (2).OverrideBarColor), "Upper Third");
+		_graphView.Annotations.Add (legend);
+	}
 
-			var legend = new LegendAnnotation (new Rect (graphView.Bounds.Width - 20, 0, 20, 5));
-			legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (0).OverrideBarColor), "Lower Third");
-			legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (1).OverrideBarColor), "Middle Third");
-			legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (2).OverrideBarColor), "Upper Third");
-			graphView.Annotations.Add (legend);
-		}
+	void SetupLineGraph ()
+	{
+		_graphView.Reset ();
 
-		private void SetupLineGraph ()
-		{
-			graphView.Reset ();
+		_graphView.Title = "Line";
 
-			about.Text = "This graph shows random points";
+		_about.Text = "This graph shows random points";
 
-			var black = new Attribute (graphView.ColorScheme.Normal.Foreground, Color.Black);
-			var cyan = new Attribute (Color.BrightCyan, Color.Black);
-			var magenta = new Attribute (Color.BrightMagenta, Color.Black);
-			var red = new Attribute (Color.BrightRed, Color.Black);
+		var black = new Attribute (_graphView.ColorScheme.Normal.Foreground, Color.Black);
+		var cyan = new Attribute (Color.BrightCyan, Color.Black);
+		var magenta = new Attribute (Color.BrightMagenta, Color.Black);
+		var red = new Attribute (Color.BrightRed, Color.Black);
 
-			graphView.GraphColor = black;
+		_graphView.GraphColor = black;
 
-			List<PointF> randomPoints = new List<PointF> ();
+		var randomPoints = new List<PointF> ();
 
-			Random r = new Random ();
+		var r = new Random ();
 
-			for (int i = 0; i < 10; i++) {
-				randomPoints.Add (new PointF (r.Next (100), r.Next (100)));
-			}
+		for (var i = 0; i < 10; i++) {
+			randomPoints.Add (new PointF (r.Next (100), r.Next (100)));
+		}
 
-			var points = new ScatterSeries () {
-				Points = randomPoints
-			};
+		var points = new ScatterSeries {
+			Points = randomPoints
+		};
 
-			var line = new PathAnnotation () {
-				LineColor = cyan,
-				Points = randomPoints.OrderBy (p => p.X).ToList (),
-				BeforeSeries = true,
-			};
+		var line = new PathAnnotation {
+			LineColor = cyan,
+			Points = randomPoints.OrderBy (p => p.X).ToList (),
+			BeforeSeries = true
+		};
 
-			graphView.Series.Add (points);
-			graphView.Annotations.Add (line);
+		_graphView.Series.Add (points);
+		_graphView.Annotations.Add (line);
 
-			randomPoints = new List<PointF> ();
+		randomPoints = new List<PointF> ();
 
-			for (int i = 0; i < 10; i++) {
-				randomPoints.Add (new PointF (r.Next (100), r.Next (100)));
-			}
+		for (var i = 0; i < 10; i++) {
+			randomPoints.Add (new PointF (r.Next (100), r.Next (100)));
+		}
 
-			var points2 = new ScatterSeries () {
-				Points = randomPoints,
-				Fill = new GraphCellToRender ((Rune)'x', red)
-			};
+		var points2 = new ScatterSeries {
+			Points = randomPoints,
+			Fill = new GraphCellToRender ((Rune)'x', red)
+		};
 
-			var line2 = new PathAnnotation () {
-				LineColor = magenta,
-				Points = randomPoints.OrderBy (p => p.X).ToList (),
-				BeforeSeries = true,
-			};
+		var line2 = new PathAnnotation {
+			LineColor = magenta,
+			Points = randomPoints.OrderBy (p => p.X).ToList (),
+			BeforeSeries = true
+		};
 
-			graphView.Series.Add (points2);
-			graphView.Annotations.Add (line2);
+		_graphView.Series.Add (points2);
+		_graphView.Annotations.Add (line2);
 
-			// How much graph space each cell of the console depicts
-			graphView.CellSize = new PointF (2, 5);
+		// How much graph space each cell of the console depicts
+		_graphView.CellSize = new PointF (2, 5);
 
-			// leave space for axis labels
-			graphView.MarginBottom = 2;
-			graphView.MarginLeft = 3;
+		// leave space for axis labels
+		_graphView.MarginBottom = 2;
+		_graphView.MarginLeft = 3;
 
-			// One axis tick/label per
-			graphView.AxisX.Increment = 20;
-			graphView.AxisX.ShowLabelsEvery = 1;
-			graphView.AxisX.Text = "X →";
+		// One axis tick/label per
+		_graphView.AxisX.Increment = 20;
+		_graphView.AxisX.ShowLabelsEvery = 1;
+		_graphView.AxisX.Text = "X →";
 
-			graphView.AxisY.Increment = 20;
-			graphView.AxisY.ShowLabelsEvery = 1;
-			graphView.AxisY.Text = "↑Y";
+		_graphView.AxisY.Increment = 20;
+		_graphView.AxisY.ShowLabelsEvery = 1;
+		_graphView.AxisY.Text = "↑Y";
 
-			var max = line.Points.Union (line2.Points).OrderByDescending (p => p.Y).First ();
-			graphView.Annotations.Add (new TextAnnotation () { Text = "(Max)", GraphPosition = new PointF (max.X + (2 * graphView.CellSize.X), max.Y) });
+		var max = line.Points.Union (line2.Points).OrderByDescending (p => p.Y).First ();
+		_graphView.Annotations.Add (new TextAnnotation { Text = "(Max)", GraphPosition = new PointF (max.X + 2 * _graphView.CellSize.X, max.Y) });
 
-			graphView.SetNeedsDisplay ();
-		}
+		_graphView.SetNeedsDisplay ();
+	}
 
-		private void SetupSineWave ()
-		{
-			graphView.Reset ();
+	void SetupSineWave ()
+	{
+		_graphView.Reset ();
 
-			about.Text = "This graph shows a sine wave";
+		_graphView.Title = "Sine Wave";
 
-			var points = new ScatterSeries ();
-			var line = new PathAnnotation ();
+		_about.Text = "This graph shows a sine wave";
 
-			// Draw line first so it does not draw over top of points or axis labels
-			line.BeforeSeries = true;
+		var points = new ScatterSeries ();
+		var line = new PathAnnotation ();
 
-			// Generate line graph with 2,000 points
-			for (float x = -500; x < 500; x += 0.5f) {
-				points.Points.Add (new PointF (x, (float)Math.Sin (x)));
-				line.Points.Add (new PointF (x, (float)Math.Sin (x)));
-			}
+		// Draw line first so it does not draw over top of points or axis labels
+		line.BeforeSeries = true;
 
-			graphView.Series.Add (points);
-			graphView.Annotations.Add (line);
+		// Generate line graph with 2,000 points
+		for (float x = -500; x < 500; x += 0.5f) {
+			points.Points.Add (new PointF (x, (float)Math.Sin (x)));
+			line.Points.Add (new PointF (x, (float)Math.Sin (x)));
+		}
 
-			// How much graph space each cell of the console depicts
-			graphView.CellSize = new PointF (0.1f, 0.1f);
+		_graphView.Series.Add (points);
+		_graphView.Annotations.Add (line);
 
-			// leave space for axis labels
-			graphView.MarginBottom = 2;
-			graphView.MarginLeft = 3;
+		// How much graph space each cell of the console depicts
+		_graphView.CellSize = new PointF (0.1f, 0.1f);
 
-			// One axis tick/label per
-			graphView.AxisX.Increment = 0.5f;
-			graphView.AxisX.ShowLabelsEvery = 2;
-			graphView.AxisX.Text = "X →";
-			graphView.AxisX.LabelGetter = (v) => v.Value.ToString ("N2");
+		// leave space for axis labels
+		_graphView.MarginBottom = 2;
+		_graphView.MarginLeft = 3;
 
-			graphView.AxisY.Increment = 0.2f;
-			graphView.AxisY.ShowLabelsEvery = 2;
-			graphView.AxisY.Text = "↑Y";
-			graphView.AxisY.LabelGetter = (v) => v.Value.ToString ("N2");
+		// One axis tick/label per
+		_graphView.AxisX.Increment = 0.5f;
+		_graphView.AxisX.ShowLabelsEvery = 2;
+		_graphView.AxisX.Text = "X →";
+		_graphView.AxisX.LabelGetter = v => v.Value.ToString ("N2");
 
-			graphView.ScrollOffset = new PointF (-2.5f, -1);
+		_graphView.AxisY.Increment = 0.2f;
+		_graphView.AxisY.ShowLabelsEvery = 2;
+		_graphView.AxisY.Text = "↑Y";
+		_graphView.AxisY.LabelGetter = v => v.Value.ToString ("N2");
 
-			graphView.SetNeedsDisplay ();
-		}
-		/*
-		Country,Both,Male,Female
+		_graphView.ScrollOffset = new PointF (-2.5f, -1);
+
+		_graphView.SetNeedsDisplay ();
+	}
+	/*
+	Country,Both,Male,Female
 
 "Switzerland",83.4,81.8,85.1
 "South Korea",83.3,80.3,86.1
@@ -291,95 +335,97 @@ namespace UICatalog.Scenarios {
 "Kuwait",81,79.3,83.9
 "Costa Rica",80.8,78.3,83.4*/
 
-		private void SetupLifeExpectancyBarGraph (bool verticalBars)
-		{
-			graphView.Reset ();
-
-			about.Text = "This graph shows the life expectancy at birth of a range of countries";
-
-			var softStiple = new GraphCellToRender ((Rune)'\u2591');
-			var mediumStiple = new GraphCellToRender ((Rune)'\u2592');
-
-			var barSeries = new BarSeries () {
-				Bars = new List<BarSeriesBar> () {
-					new BarSeriesBar ("Switzerland", softStiple, 83.4f),
-					new BarSeriesBar ("South Korea", !verticalBars?mediumStiple:softStiple, 83.3f),
-					new BarSeriesBar ("Singapore", softStiple, 83.2f),
-					new BarSeriesBar ("Spain", !verticalBars?mediumStiple:softStiple, 83.2f),
-					new BarSeriesBar ("Cyprus", softStiple, 83.1f),
-					new BarSeriesBar ("Australia", !verticalBars?mediumStiple:softStiple, 83),
-					new BarSeriesBar ("Italy", softStiple, 83),
-					new BarSeriesBar ("Norway", !verticalBars?mediumStiple:softStiple, 83),
-					new BarSeriesBar ("Israel", softStiple, 82.6f),
-					new BarSeriesBar ("France", !verticalBars?mediumStiple:softStiple, 82.5f),
-					new BarSeriesBar ("Luxembourg", softStiple, 82.4f),
-					new BarSeriesBar ("Sweden", !verticalBars?mediumStiple:softStiple, 82.4f),
-					new BarSeriesBar ("Iceland", softStiple, 82.3f),
-					new BarSeriesBar ("Canada", !verticalBars?mediumStiple:softStiple, 82.2f),
-					new BarSeriesBar ("New Zealand", softStiple, 82),
-					new BarSeriesBar ("Malta", !verticalBars?mediumStiple:softStiple, 81.9f),
-					new BarSeriesBar ("Ireland", softStiple, 81.8f)
-				}
-			};
-
-			graphView.Series.Add (barSeries);
-
-			if (verticalBars) {
-
-				barSeries.Orientation = Orientation.Vertical;
-
-				// How much graph space each cell of the console depicts
-				graphView.CellSize = new PointF (0.1f, 0.25f);
-				// No axis marks since Bar will add it's own categorical marks
-				graphView.AxisX.Increment = 0f;
-				graphView.AxisX.Text = "Country";
-				graphView.AxisX.Minimum = 0;
+	void SetupLifeExpectancyBarGraph (bool verticalBars)
+	{
+		_graphView.Reset ();
+
+		_graphView.Title = $"Life Expectancy - {(verticalBars ? "Vertical" : "Horizontal")}";
+
+		_about.Text = "This graph shows the life expectancy at birth of a range of countries";
+
+		var softStiple = new GraphCellToRender ((Rune)'\u2591');
+		var mediumStiple = new GraphCellToRender ((Rune)'\u2592');
+
+		var barSeries = new BarSeries {
+			Bars = new List<BarSeriesBar> {
+				new ("Switzerland", softStiple, 83.4f),
+				new ("South Korea", !verticalBars ? mediumStiple : softStiple, 83.3f),
+				new ("Singapore", softStiple, 83.2f),
+				new ("Spain", !verticalBars ? mediumStiple : softStiple, 83.2f),
+				new ("Cyprus", softStiple, 83.1f),
+				new ("Australia", !verticalBars ? mediumStiple : softStiple, 83),
+				new ("Italy", softStiple, 83),
+				new ("Norway", !verticalBars ? mediumStiple : softStiple, 83),
+				new ("Israel", softStiple, 82.6f),
+				new ("France", !verticalBars ? mediumStiple : softStiple, 82.5f),
+				new ("Luxembourg", softStiple, 82.4f),
+				new ("Sweden", !verticalBars ? mediumStiple : softStiple, 82.4f),
+				new ("Iceland", softStiple, 82.3f),
+				new ("Canada", !verticalBars ? mediumStiple : softStiple, 82.2f),
+				new ("New Zealand", softStiple, 82),
+				new ("Malta", !verticalBars ? mediumStiple : softStiple, 81.9f),
+				new ("Ireland", softStiple, 81.8f)
+			}
+		};
 
-				graphView.AxisY.Increment = 1f;
-				graphView.AxisY.ShowLabelsEvery = 1;
-				graphView.AxisY.LabelGetter = v => v.Value.ToString ("N2");
-				graphView.AxisY.Minimum = 0;
-				graphView.AxisY.Text = "Age";
+		_graphView.Series.Add (barSeries);
 
-				// leave space for axis labels and title
-				graphView.MarginBottom = 2;
-				graphView.MarginLeft = 6;
+		if (verticalBars) {
 
-				// Start the graph at 80 years because that is where most of our data is
-				graphView.ScrollOffset = new PointF (0, 80);
+			barSeries.Orientation = Orientation.Vertical;
 
-			} else {
-				barSeries.Orientation = Orientation.Horizontal;
+			// How much graph space each cell of the console depicts
+			_graphView.CellSize = new PointF (0.1f, 0.25f);
+			// No axis marks since Bar will add it's own categorical marks
+			_graphView.AxisX.Increment = 0f;
+			_graphView.AxisX.Text = "Country";
+			_graphView.AxisX.Minimum = 0;
 
-				// How much graph space each cell of the console depicts
-				graphView.CellSize = new PointF (0.1f, 1f);
-				// No axis marks since Bar will add it's own categorical marks
-				graphView.AxisY.Increment = 0f;
-				graphView.AxisY.ShowLabelsEvery = 1;
-				graphView.AxisY.Text = "Country";
-				graphView.AxisY.Minimum = 0;
+			_graphView.AxisY.Increment = 1f;
+			_graphView.AxisY.ShowLabelsEvery = 1;
+			_graphView.AxisY.LabelGetter = v => v.Value.ToString ("N2");
+			_graphView.AxisY.Minimum = 0;
+			_graphView.AxisY.Text = "Age";
 
-				graphView.AxisX.Increment = 1f;
-				graphView.AxisX.ShowLabelsEvery = 1;
-				graphView.AxisX.LabelGetter = v => v.Value.ToString ("N2");
-				graphView.AxisX.Text = "Age";
-				graphView.AxisX.Minimum = 0;
+			// leave space for axis labels and title
+			_graphView.MarginBottom = 2;
+			_graphView.MarginLeft = 6;
 
-				// leave space for axis labels and title
-				graphView.MarginBottom = 2;
-				graphView.MarginLeft = (uint)barSeries.Bars.Max (b => b.Text.Length) + 2;
+			// Start the graph at 80 years because that is where most of our data is
+			_graphView.ScrollOffset = new PointF (0, 80);
 
-				// Start the graph at 80 years because that is where most of our data is
-				graphView.ScrollOffset = new PointF (80, 0);
-			}
+		} else {
+			barSeries.Orientation = Orientation.Horizontal;
 
-			graphView.SetNeedsDisplay ();
+			// How much graph space each cell of the console depicts
+			_graphView.CellSize = new PointF (0.1f, 1f);
+			// No axis marks since Bar will add it's own categorical marks
+			_graphView.AxisY.Increment = 0f;
+			_graphView.AxisY.ShowLabelsEvery = 1;
+			_graphView.AxisY.Text = "Country";
+			_graphView.AxisY.Minimum = 0;
+
+			_graphView.AxisX.Increment = 1f;
+			_graphView.AxisX.ShowLabelsEvery = 1;
+			_graphView.AxisX.LabelGetter = v => v.Value.ToString ("N2");
+			_graphView.AxisX.Text = "Age";
+			_graphView.AxisX.Minimum = 0;
+
+			// leave space for axis labels and title
+			_graphView.MarginBottom = 2;
+			_graphView.MarginLeft = (uint)barSeries.Bars.Max (b => b.Text.Length) + 2;
+
+			// Start the graph at 80 years because that is where most of our data is
+			_graphView.ScrollOffset = new PointF (80, 0);
 		}
 
-		private void SetupPopulationPyramid ()
-		{
-			/*
-			Age,M,F
+		_graphView.SetNeedsDisplay ();
+	}
+
+	void SetupPopulationPyramid ()
+	{
+		/*
+		Age,M,F
 0-4,2009363,1915127
 5-9,2108550,2011016
 10-14,2022370,1933970
@@ -402,280 +448,283 @@ namespace UICatalog.Scenarios {
 95-99,34524,95559
 100+,3016,12818*/
 
-			about.Text = "This graph shows population of each age divided by gender";
+		_about.Text = "This graph shows population of each age divided by gender";
+
+		_graphView.Title = "Population Pyramid";
+
+		_graphView.Reset ();
+
+		// How much graph space each cell of the console depicts
+		_graphView.CellSize = new PointF (100_000, 1);
+
+		//center the x axis in middle of screen to show both sides
+		_graphView.ScrollOffset = new PointF (-3_000_000, 0);
+
+		_graphView.AxisX.Text = "Number Of People";
+		_graphView.AxisX.Increment = 500_000;
+		_graphView.AxisX.ShowLabelsEvery = 2;
+
+		// use Abs to make negative axis labels positive
+		_graphView.AxisX.LabelGetter = v => Math.Abs (v.Value / 1_000_000).ToString ("N2") + "M";
+
+		// leave space for axis labels
+		_graphView.MarginBottom = 2;
+		_graphView.MarginLeft = 1;
+
+		// do not show axis titles (bars have their own categories)
+		_graphView.AxisY.Increment = 0;
+		_graphView.AxisY.ShowLabelsEvery = 0;
+		_graphView.AxisY.Minimum = 0;
+
+		var stiple = new GraphCellToRender (CM.Glyphs.Stipple);
+
+		// Bars in 2 directions
+
+		// Males (negative to make the bars go left)
+		var malesSeries = new BarSeries {
+			Orientation = Orientation.Horizontal,
+			Bars = new List<BarSeriesBar> {
+				new ("0-4", stiple, -2009363),
+				new ("5-9", stiple, -2108550),
+				new ("10-14", stiple, -2022370),
+				new ("15-19", stiple, -1880611),
+				new ("20-24", stiple, -2072674),
+				new ("25-29", stiple, -2275138),
+				new ("30-34", stiple, -2361054),
+				new ("35-39", stiple, -2279836),
+				new ("40-44", stiple, -2148253),
+				new ("45-49", stiple, -2128343),
+				new ("50-54", stiple, -2281421),
+				new ("55-59", stiple, -2232388),
+				new ("60-64", stiple, -1919839),
+				new ("65-69", stiple, -1647391),
+				new ("70-74", stiple, -1624635),
+				new ("75-79", stiple, -1137438),
+				new ("80-84", stiple, -766956),
+				new ("85-89", stiple, -438663),
+				new ("90-94", stiple, -169952),
+				new ("95-99", stiple, -34524),
+				new ("100+", stiple, -3016)
 
-			graphView.Reset ();
+			}
+		};
+		_graphView.Series.Add (malesSeries);
+
+		// Females
+		var femalesSeries = new BarSeries {
+			Orientation = Orientation.Horizontal,
+			Bars = new List<BarSeriesBar> {
+				new ("0-4", stiple, 1915127),
+				new ("5-9", stiple, 2011016),
+				new ("10-14", stiple, 1933970),
+				new ("15-19", stiple, 1805522),
+				new ("20-24", stiple, 2001966),
+				new ("25-29", stiple, 2208929),
+				new ("30-34", stiple, 2345774),
+				new ("35-39", stiple, 2308360),
+				new ("40-44", stiple, 2159877),
+				new ("45-49", stiple, 2167778),
+				new ("50-54", stiple, 2353119),
+				new ("55-59", stiple, 2306537),
+				new ("60-64", stiple, 1985177),
+				new ("65-69", stiple, 1734370),
+				new ("70-74", stiple, 1763853),
+				new ("75-79", stiple, 1304709),
+				new ("80-84", stiple, 969611),
+				new ("85-89", stiple, 638892),
+				new ("90-94", stiple, 320625),
+				new ("95-99", stiple, 95559),
+				new ("100+", stiple, 12818)
+			}
+		};
 
-			// How much graph space each cell of the console depicts
-			graphView.CellSize = new PointF (100_000, 1);
-
-			//center the x axis in middle of screen to show both sides
-			graphView.ScrollOffset = new PointF (-3_000_000, 0);
-
-			graphView.AxisX.Text = "Number Of People";
-			graphView.AxisX.Increment = 500_000;
-			graphView.AxisX.ShowLabelsEvery = 2;
-
-			// use Abs to make negative axis labels positive
-			graphView.AxisX.LabelGetter = (v) => Math.Abs (v.Value / 1_000_000).ToString ("N2") + "M";
-
-			// leave space for axis labels
-			graphView.MarginBottom = 2;
-			graphView.MarginLeft = 1;
-
-			// do not show axis titles (bars have their own categories)
-			graphView.AxisY.Increment = 0;
-			graphView.AxisY.ShowLabelsEvery = 0;
-			graphView.AxisY.Minimum = 0;
-
-			var stiple = new GraphCellToRender (CM.Glyphs.Stipple);
-
-			// Bars in 2 directions
-
-			// Males (negative to make the bars go left)
-			var malesSeries = new BarSeries () {
-				Orientation = Orientation.Horizontal,
-				Bars = new List<BarSeriesBar> ()
-				{
-					new BarSeriesBar("0-4",stiple,-2009363),
-					new BarSeriesBar("5-9",stiple,-2108550),
-					new BarSeriesBar("10-14",stiple,-2022370),
-					new BarSeriesBar("15-19",stiple,-1880611),
-					new BarSeriesBar("20-24",stiple,-2072674),
-					new BarSeriesBar("25-29",stiple,-2275138),
-					new BarSeriesBar("30-34",stiple,-2361054),
-					new BarSeriesBar("35-39",stiple,-2279836),
-					new BarSeriesBar("40-44",stiple,-2148253),
-					new BarSeriesBar("45-49",stiple,-2128343),
-					new BarSeriesBar("50-54",stiple,-2281421),
-					new BarSeriesBar("55-59",stiple,-2232388),
-					new BarSeriesBar("60-64",stiple,-1919839),
-					new BarSeriesBar("65-69",stiple,-1647391),
-					new BarSeriesBar("70-74",stiple,-1624635),
-					new BarSeriesBar("75-79",stiple,-1137438),
-					new BarSeriesBar("80-84",stiple,-766956),
-					new BarSeriesBar("85-89",stiple,-438663),
-					new BarSeriesBar("90-94",stiple,-169952),
-					new BarSeriesBar("95-99",stiple,-34524),
-					new BarSeriesBar("100+",stiple,-3016)
+		var softStiple = new GraphCellToRender ((Rune)'\u2591');
+		var mediumStiple = new GraphCellToRender ((Rune)'\u2592');
 
-				}
-			};
-			graphView.Series.Add (malesSeries);
-
-			// Females
-			var femalesSeries = new BarSeries () {
-				Orientation = Orientation.Horizontal,
-				Bars = new List<BarSeriesBar> ()
-				{
-					new BarSeriesBar("0-4",stiple,1915127),
-					new BarSeriesBar("5-9",stiple,2011016),
-					new BarSeriesBar("10-14",stiple,1933970),
-					new BarSeriesBar("15-19",stiple,1805522),
-					new BarSeriesBar("20-24",stiple,2001966),
-					new BarSeriesBar("25-29",stiple,2208929),
-					new BarSeriesBar("30-34",stiple,2345774),
-					new BarSeriesBar("35-39",stiple,2308360),
-					new BarSeriesBar("40-44",stiple,2159877),
-					new BarSeriesBar("45-49",stiple,2167778),
-					new BarSeriesBar("50-54",stiple,2353119),
-					new BarSeriesBar("55-59",stiple,2306537),
-					new BarSeriesBar("60-64",stiple,1985177),
-					new BarSeriesBar("65-69",stiple,1734370),
-					new BarSeriesBar("70-74",stiple,1763853),
-					new BarSeriesBar("75-79",stiple,1304709),
-					new BarSeriesBar("80-84",stiple,969611),
-					new BarSeriesBar("85-89",stiple,638892),
-					new BarSeriesBar("90-94",stiple,320625),
-					new BarSeriesBar("95-99",stiple,95559),
-					new BarSeriesBar("100+",stiple,12818)
-				}
-			};
+		for (var i = 0; i < malesSeries.Bars.Count; i++) {
+			malesSeries.Bars [i].Fill = i % 2 == 0 ? softStiple : mediumStiple;
+			femalesSeries.Bars [i].Fill = i % 2 == 0 ? softStiple : mediumStiple;
+		}
 
-			var softStiple = new GraphCellToRender ((Rune)'\u2591');
-			var mediumStiple = new GraphCellToRender ((Rune)'\u2592');
+		_graphView.Series.Add (femalesSeries);
 
-			for (int i = 0; i < malesSeries.Bars.Count; i++) {
-				malesSeries.Bars [i].Fill = i % 2 == 0 ? softStiple : mediumStiple;
-				femalesSeries.Bars [i].Fill = i % 2 == 0 ? softStiple : mediumStiple;
-			}
+		_graphView.Annotations.Add (new TextAnnotation { Text = "M", ScreenPosition = new Point (0, 10) });
+		_graphView.Annotations.Add (new TextAnnotation { Text = "F", ScreenPosition = new Point (_graphView.Bounds.Width - 1, 10) });
 
-			graphView.Series.Add (femalesSeries);
+		_graphView.SetNeedsDisplay ();
 
-			graphView.Annotations.Add (new TextAnnotation () { Text = "M", ScreenPosition = new Terminal.Gui.Point (0, 10) });
-			graphView.Annotations.Add (new TextAnnotation () { Text = "F", ScreenPosition = new Terminal.Gui.Point (graphView.Bounds.Width - 1, 10) });
+	}
 
-			graphView.SetNeedsDisplay ();
+	void SetupDisco ()
+	{
+		_graphView.Reset ();
 
-		}
+		_graphView.Title = "Graphic Equalizer";
 
-		class DiscoBarSeries : BarSeries {
-			private Terminal.Gui.Attribute green;
-			private Terminal.Gui.Attribute brightgreen;
-			private Terminal.Gui.Attribute brightyellow;
-			private Terminal.Gui.Attribute red;
-			private Terminal.Gui.Attribute brightred;
-
-			public DiscoBarSeries ()
-			{
-
-				green = new Attribute (Color.BrightGreen, Color.Black);
-				brightgreen = new Attribute (Color.Green, Color.Black);
-				brightyellow = new Attribute (Color.BrightYellow, Color.Black);
-				red = new Attribute (Color.Red, Color.Black);
-				brightred = new Attribute (Color.BrightRed, Color.Black);
-			}
-			protected override void DrawBarLine (GraphView graph, Terminal.Gui.Point start, Terminal.Gui.Point end, BarSeriesBar beingDrawn)
-			{
-				var driver = Application.Driver;
-
-				int x = start.X;
-				for (int y = end.Y; y <= start.Y; y++) {
-
-					var height = graph.ScreenToGraphSpace (x, y).Y;
-
-					if (height >= 85) {
-						driver.SetAttribute (red);
-					} else if (height >= 66) {
-						driver.SetAttribute (brightred);
-					} else if (height >= 45) {
-						driver.SetAttribute (brightyellow);
-					} else if (height >= 25) {
-						driver.SetAttribute (brightgreen);
-					} else {
-						driver.SetAttribute (green);
-					}
-
-					graph.AddRune (x, y, beingDrawn.Fill.Rune);
-				}
-			}
-		}
+		_about.Text = "This graph shows a graphic equalizer for an imaginary song";
 
-		private void SetupDisco ()
-		{
-			graphView.Reset ();
+		_graphView.GraphColor = new Attribute (Color.White, Color.Black);
 
-			about.Text = "This graph shows a graphic equaliser for an imaginary song";
+		var stiple = new GraphCellToRender ((Rune)'\u2593');
 
-			graphView.GraphColor = new Attribute (Color.White, Color.Black);
+		var r = new Random ();
+		var series = new DiscoBarSeries ();
+		var bars = new List<BarSeriesBar> ();
 
-			var stiple = new GraphCellToRender ((Rune)'\u2593');
+		var genSample = () => {
 
-			Random r = new Random ();
-			var series = new DiscoBarSeries ();
-			var bars = new List<BarSeriesBar> ();
+			bars.Clear ();
+			// generate an imaginary sample
+			for (var i = 0; i < 31; i++) {
+				bars.Add (
+					new BarSeriesBar (null, stiple, r.Next (0, 100)) {
+						//ColorGetter = colorDelegate
+					});
+			}
+			_graphView.SetNeedsDisplay ();
 
-			Func<bool> genSample = () => {
+			// while the equaliser is showing
+			return _graphView.Series.Contains (series);
+		};
 
-				bars.Clear ();
-				// generate an imaginary sample
-				for (int i = 0; i < 31; i++) {
-					bars.Add (
-						new BarSeriesBar (null, stiple, r.Next (0, 100)) {
-							//ColorGetter = colorDelegate
-						});
-				}
-				graphView.SetNeedsDisplay ();
+		Application.AddTimeout (TimeSpan.FromMilliseconds (250), genSample);
 
-				// while the equaliser is showing
-				return graphView.Series.Contains (series);
-			};
+		series.Bars = bars;
 
-			Application.AddTimeout (TimeSpan.FromMilliseconds (250), genSample);
+		_graphView.Series.Add (series);
 
-			series.Bars = bars;
+		// How much graph space each cell of the console depicts
+		_graphView.CellSize = new PointF (1, 10);
+		_graphView.AxisX.Increment = 0; // No graph ticks
+		_graphView.AxisX.ShowLabelsEvery = 0; // no labels
 
-			graphView.Series.Add (series);
+		_graphView.AxisX.Visible = false;
+		_graphView.AxisY.Visible = false;
 
-			// How much graph space each cell of the console depicts
-			graphView.CellSize = new PointF (1, 10);
-			graphView.AxisX.Increment = 0; // No graph ticks
-			graphView.AxisX.ShowLabelsEvery = 0; // no labels
+		_graphView.SetNeedsDisplay ();
+	}
 
-			graphView.AxisX.Visible = false;
-			graphView.AxisY.Visible = false;
+	void SetupPeriodicTableScatterPlot ()
+	{
+		_graphView.Reset ();
+
+		_graphView.Title = "Scatter Plot";
+
+		_about.Text = "This graph shows the atomic weight of each element in the periodic table.\nStarting with Hydrogen (atomic Number 1 with a weight of 1.007)";
+
+		//AtomicNumber and AtomicMass of all elements in the periodic table
+		_graphView.Series.Add (
+			new ScatterSeries {
+				Points = new List<PointF> {
+					new (1, 1.007f), new (2, 4.002f), new (3, 6.941f), new (4, 9.012f), new (5, 10.811f), new (6, 12.011f),
+					new (7, 14.007f), new (8, 15.999f), new (9, 18.998f), new (10, 20.18f), new (11, 22.99f), new (12, 24.305f),
+					new (13, 26.982f), new (14, 28.086f), new (15, 30.974f), new (16, 32.065f), new (17, 35.453f), new (18, 39.948f),
+					new (19, 39.098f), new (20, 40.078f), new (21, 44.956f), new (22, 47.867f), new (23, 50.942f), new (24, 51.996f),
+					new (25, 54.938f), new (26, 55.845f), new (27, 58.933f), new (28, 58.693f), new (29, 63.546f), new (30, 65.38f),
+					new (31, 69.723f), new (32, 72.64f), new (33, 74.922f), new (34, 78.96f), new (35, 79.904f), new (36, 83.798f),
+					new (37, 85.468f), new (38, 87.62f), new (39, 88.906f), new (40, 91.224f), new (41, 92.906f), new (42, 95.96f),
+					new (43, 98f), new (44, 101.07f), new (45, 102.906f), new (46, 106.42f), new (47, 107.868f), new (48, 112.411f),
+					new (49, 114.818f), new (50, 118.71f), new (51, 121.76f), new (52, 127.6f), new (53, 126.904f), new (54, 131.293f),
+					new (55, 132.905f), new (56, 137.327f), new (57, 138.905f), new (58, 140.116f), new (59, 140.908f), new (60, 144.242f),
+					new (61, 145), new (62, 150.36f), new (63, 151.964f), new (64, 157.25f), new (65, 158.925f), new (66, 162.5f),
+					new (67, 164.93f), new (68, 167.259f), new (69, 168.934f), new (70, 173.054f), new (71, 174.967f), new (72, 178.49f),
+					new (73, 180.948f), new (74, 183.84f), new (75, 186.207f), new (76, 190.23f), new (77, 192.217f), new (78, 195.084f),
+					new (79, 196.967f), new (80, 200.59f), new (81, 204.383f), new (82, 207.2f), new (83, 208.98f), new (84, 210),
+					new (85, 210), new (86, 222), new (87, 223), new (88, 226), new (89, 227), new (90, 232.038f), new (91, 231.036f),
+					new (92, 238.029f), new (93, 237), new (94, 244), new (95, 243), new (96, 247), new (97, 247), new (98, 251),
+					new (99, 252), new (100, 257), new (101, 258), new (102, 259), new (103, 262), new (104, 261), new (105, 262),
+					new (106, 266), new (107, 264), new (108, 267), new (109, 268), new (113, 284), new (114, 289), new (115, 288),
+					new (116, 292), new (117, 295), new (118, 294)
+				}
+			});
 
-			graphView.SetNeedsDisplay ();
-		}
-		private void SetupPeriodicTableScatterPlot ()
-		{
-			graphView.Reset ();
-
-			about.Text = "This graph shows the atomic weight of each element in the periodic table.\nStarting with Hydrogen (atomic Number 1 with a weight of 1.007)";
-
-			//AtomicNumber and AtomicMass of all elements in the periodic table
-			graphView.Series.Add (
-				new ScatterSeries () {
-					Points = new List<PointF>{
-						new PointF(1,1.007f),new PointF(2,4.002f),new PointF(3,6.941f),new PointF(4,9.012f),new PointF(5,10.811f),new PointF(6,12.011f),
-						new PointF(7,14.007f),new PointF(8,15.999f),new PointF(9,18.998f),new PointF(10,20.18f),new PointF(11,22.99f),new PointF(12,24.305f),
-						new PointF(13,26.982f),new PointF(14,28.086f),new PointF(15,30.974f),new PointF(16,32.065f),new PointF(17,35.453f),new PointF(18,39.948f),
-						new PointF(19,39.098f),new PointF(20,40.078f),new PointF(21,44.956f),new PointF(22,47.867f),new PointF(23,50.942f),new PointF(24,51.996f),
-						new PointF(25,54.938f),new PointF(26,55.845f),new PointF(27,58.933f),new PointF(28,58.693f),new PointF(29,63.546f),new PointF(30,65.38f),
-						new PointF(31,69.723f),new PointF(32,72.64f),new PointF(33,74.922f),new PointF(34,78.96f),new PointF(35,79.904f),new PointF(36,83.798f),
-						new PointF(37,85.468f),new PointF(38,87.62f),new PointF(39,88.906f),new PointF(40,91.224f),new PointF(41,92.906f),new PointF(42,95.96f),
-						new PointF(43,98f),new PointF(44,101.07f),new PointF(45,102.906f),new PointF(46,106.42f),new PointF(47,107.868f),new PointF(48,112.411f),
-						new PointF(49,114.818f),new PointF(50,118.71f),new PointF(51,121.76f),new PointF(52,127.6f),new PointF(53,126.904f),new PointF(54,131.293f),
-						new PointF(55,132.905f),new PointF(56,137.327f),new PointF(57,138.905f),new PointF(58,140.116f),new PointF(59,140.908f),new PointF(60,144.242f),
-						new PointF(61,145),new PointF(62,150.36f),new PointF(63,151.964f),new PointF(64,157.25f),new PointF(65,158.925f),new PointF(66,162.5f),
-						new PointF(67,164.93f),new PointF(68,167.259f),new PointF(69,168.934f),new PointF(70,173.054f),new PointF(71,174.967f),new PointF(72,178.49f),
-						new PointF(73,180.948f),new PointF(74,183.84f),new PointF(75,186.207f),new PointF(76,190.23f),new PointF(77,192.217f),new PointF(78,195.084f),
-						new PointF(79,196.967f),new PointF(80,200.59f),new PointF(81,204.383f),new PointF(82,207.2f),new PointF(83,208.98f),new PointF(84,210),
-						new PointF(85,210),new PointF(86,222),new PointF(87,223),new PointF(88,226),new PointF(89,227),new PointF(90,232.038f),new PointF(91,231.036f),
-						new PointF(92,238.029f),new PointF(93,237),new PointF(94,244),new PointF(95,243),new PointF(96,247),new PointF(97,247),new PointF(98,251),
-						new PointF(99,252),new PointF(100,257),new PointF(101,258),new PointF(102,259),new PointF(103,262),new PointF(104,261),new PointF(105,262),
-						new PointF(106,266),new PointF(107,264),new PointF(108,267),new PointF(109,268),new PointF(113,284),new PointF(114,289),new PointF(115,288),
-						new PointF(116,292),new PointF(117,295),new PointF(118,294)
-			}
-				});
+		// How much graph space each cell of the console depicts
+		_graphView.CellSize = new PointF (1, 5);
 
-			// How much graph space each cell of the console depicts
-			graphView.CellSize = new PointF (1, 5);
+		// leave space for axis labels
+		_graphView.MarginBottom = 2;
+		_graphView.MarginLeft = 3;
 
-			// leave space for axis labels
-			graphView.MarginBottom = 2;
-			graphView.MarginLeft = 3;
+		// One axis tick/label per 5 atomic numbers
+		_graphView.AxisX.Increment = 5;
+		_graphView.AxisX.ShowLabelsEvery = 1;
+		_graphView.AxisX.Text = "Atomic Number";
+		_graphView.AxisX.Minimum = 0;
 
-			// One axis tick/label per 5 atomic numbers
-			graphView.AxisX.Increment = 5;
-			graphView.AxisX.ShowLabelsEvery = 1;
-			graphView.AxisX.Text = "Atomic Number";
-			graphView.AxisX.Minimum = 0;
+		// One label every 5 atomic weight
+		_graphView.AxisY.Increment = 5;
+		_graphView.AxisY.ShowLabelsEvery = 1;
+		_graphView.AxisY.Minimum = 0;
 
-			// One label every 5 atomic weight
-			graphView.AxisY.Increment = 5;
-			graphView.AxisY.ShowLabelsEvery = 1;
-			graphView.AxisY.Minimum = 0;
+		_graphView.SetNeedsDisplay ();
+	}
 
-			graphView.SetNeedsDisplay ();
-		}
+	void Zoom (float factor)
+	{
+		_graphView.CellSize = new PointF (
+			_graphView.CellSize.X * factor,
+			_graphView.CellSize.Y * factor
+		);
 
-		private void Zoom (float factor)
-		{
-			graphView.CellSize = new PointF (
-				graphView.CellSize.X * factor,
-				graphView.CellSize.Y * factor
-			);
+		_graphView.AxisX.Increment *= factor;
+		_graphView.AxisY.Increment *= factor;
 
-			graphView.AxisX.Increment *= factor;
-			graphView.AxisY.Increment *= factor;
+		_graphView.SetNeedsDisplay ();
+	}
 
-			graphView.SetNeedsDisplay ();
+	void Margin (bool left, bool increase)
+	{
+		if (left) {
+			_graphView.MarginLeft = (uint)Math.Max (0, _graphView.MarginLeft + (increase ? 1 : -1));
+		} else {
+			_graphView.MarginBottom = (uint)Math.Max (0, _graphView.MarginBottom + (increase ? 1 : -1));
 		}
-		private void Margin (bool left, bool increase)
+
+		_graphView.SetNeedsDisplay ();
+	}
+
+	void Quit () => Application.RequestStop ();
+
+	class DiscoBarSeries : BarSeries {
+		readonly Attribute brightgreen;
+		readonly Attribute brightred;
+		readonly Attribute brightyellow;
+		readonly Attribute green;
+		readonly Attribute red;
+
+		public DiscoBarSeries ()
 		{
-			if (left) {
-				graphView.MarginLeft = (uint)Math.Max (0, graphView.MarginLeft + (increase ? 1 : -1));
-			} else {
-				graphView.MarginBottom = (uint)Math.Max (0, graphView.MarginBottom + (increase ? 1 : -1));
-			}
 
-			graphView.SetNeedsDisplay ();
+			green = new Attribute (Color.BrightGreen, Color.Black);
+			brightgreen = new Attribute (Color.Green, Color.Black);
+			brightyellow = new Attribute (Color.BrightYellow, Color.Black);
+			red = new Attribute (Color.Red, Color.Black);
+			brightred = new Attribute (Color.BrightRed, Color.Black);
 		}
 
-		private void Quit ()
+		protected override void DrawBarLine (GraphView graph, Point start, Point end, BarSeriesBar beingDrawn)
 		{
-			Application.RequestStop ();
+			var driver = Application.Driver;
+
+			var x = start.X;
+			for (var y = end.Y; y <= start.Y; y++) {
+
+				var height = graph.ScreenToGraphSpace (x, y).Y;
+
+				if (height >= 85) {
+					driver.SetAttribute (red);
+				} else if (height >= 66) {
+					driver.SetAttribute (brightred);
+				} else if (height >= 45) {
+					driver.SetAttribute (brightyellow);
+				} else if (height >= 25) {
+					driver.SetAttribute (brightgreen);
+				} else {
+					driver.SetAttribute (green);
+				}
+
+				graph.AddRune (x, y, beingDrawn.Fill.Rune);
+			}
 		}
 	}
-}
+}

+ 6 - 4
UICatalog/Scenarios/InvertColors.cs

@@ -10,7 +10,7 @@ namespace UICatalog.Scenarios {
 	public class InvertColors : Scenario {
 		public override void Setup ()
 		{
-			Win.ColorScheme = Colors.TopLevel;
+			Win.ColorScheme = Colors.ColorSchemes ["TopLevel"];
 
 			List<Label> labels = new List<Label> ();
 			var foreColors = Enum.GetValues (typeof (ColorName)).Cast<ColorName> ().ToArray ();
@@ -24,7 +24,7 @@ namespace UICatalog.Scenarios {
 					ColorScheme = new ColorScheme (),
 					Y = y
 				};
-				label.ColorScheme.Normal = color;
+				label.ColorScheme = new ColorScheme (label.ColorScheme) { Normal = color };
 				Win.Add (label);
 				labels.Add (label);
 			}
@@ -33,13 +33,15 @@ namespace UICatalog.Scenarios {
 				X = Pos.Center (),
 				Y = foreColors.Length + 1,
 			};
-			button.Clicked += (s,e) => {
+			button.Clicked += (s, e) => {
 
 				foreach (var label in labels) {
 					var color = label.ColorScheme.Normal;
 					color = new Attribute (color.Background, color.Foreground);
 
-					label.ColorScheme.Normal = color;
+					label.ColorScheme = new ColorScheme (label.ColorScheme) {
+						Normal = color
+					};
 					label.Text = $"{color.Foreground} on {color.Background}";
 					label.SetNeedsDisplay ();
 

+ 5 - 5
UICatalog/Scenarios/Keys.cs

@@ -46,7 +46,7 @@ public class Keys : Scenario {
 			X = Pos.Right (keyPressedLabel) + 1,
 			Y = Pos.Top (keyPressedLabel),
 			TextAlignment = Terminal.Gui.TextAlignment.Centered,
-			ColorScheme = Colors.Error,
+			ColorScheme = Colors.ColorSchemes ["Error"],
 			AutoSize = true
 		};
 		Win.Add (labelTextViewKeypress);
@@ -62,7 +62,7 @@ public class Keys : Scenario {
 			X = Pos.Right (keyPressedLabel) + 1,
 			Y = Pos.Top (keyPressedLabel),
 			TextAlignment = Terminal.Gui.TextAlignment.Centered,
-			ColorScheme = Colors.Error,
+			ColorScheme = Colors.ColorSchemes ["Error"],
 			AutoSize = true
 		};
 		Win.Add (labelAppKeypress);
@@ -84,7 +84,7 @@ public class Keys : Scenario {
 			Width = "Key Down:".Length + maxKeyString,
 			Height = Dim.Fill (),
 		};
-		keyEventListView.ColorScheme = Colors.TopLevel;
+		keyEventListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
 		Win.Add (keyEventListView);
 
 		// OnKeyPressed
@@ -101,7 +101,7 @@ public class Keys : Scenario {
 			Width = maxKeyString,
 			Height = Dim.Fill (),
 		};
-		onKeyPressedListView.ColorScheme = Colors.TopLevel;
+		onKeyPressedListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
 		Win.Add (onKeyPressedListView);
 
 		// OnInvokeKeyBindings
@@ -116,7 +116,7 @@ public class Keys : Scenario {
 			Width = Dim.Fill (1),
 			Height = Dim.Fill (),
 		};
-		onInvokingKeyBindingsListView.ColorScheme = Colors.TopLevel;
+		onInvokingKeyBindingsListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
 		Win.Add (onInvokingKeyBindingsListView);
 
 		//Application.KeyDown += (s, a) => KeyDownPressUp (a, "Down");

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác