소스 검색

Merged v2_develop

Tig Kindel 1 년 전
부모
커밋
e480bafc0d
100개의 변경된 파일7634개의 추가작업 그리고 6916개의 파일을 삭제
  1. 0 29
      .editorconfig
  2. 1 1
      ReactiveExample/LoginView.cs
  3. 91 64
      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. 5 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. 9 0
      Terminal.Gui/Resources/Strings.Designer.cs
  16. 3 0
      Terminal.Gui/Resources/Strings.fr-FR.resx
  17. 3 0
      Terminal.Gui/Resources/Strings.ja-JP.resx
  18. 3 0
      Terminal.Gui/Resources/Strings.pt-PT.resx
  19. 3 0
      Terminal.Gui/Resources/Strings.resx
  20. 3 0
      Terminal.Gui/Resources/Strings.zh-Hans.resx
  21. 1 1
      Terminal.Gui/Terminal.Gui.csproj
  22. 1 1
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  23. 1 1
      Terminal.Gui/Text/TextFormatter.cs
  24. 177 0
      Terminal.Gui/View/Adornment/Adornment.cs
  25. 345 0
      Terminal.Gui/View/Adornment/Border.cs
  26. 37 0
      Terminal.Gui/View/Adornment/Margin.cs
  27. 37 0
      Terminal.Gui/View/Adornment/Padding.cs
  28. 0 446
      Terminal.Gui/View/Frame.cs
  29. 124 0
      Terminal.Gui/View/Layout/PosDim.cs
  30. 120 135
      Terminal.Gui/View/Layout/ViewLayout.cs
  31. 1 1
      Terminal.Gui/View/SuperViewChangedEventArgs.cs
  32. 7 11
      Terminal.Gui/View/View.cs
  33. 30 11
      Terminal.Gui/View/ViewDrawing.cs
  34. 98 92
      Terminal.Gui/View/ViewMouse.cs
  35. 10 11
      Terminal.Gui/View/ViewSubViews.cs
  36. 2 2
      Terminal.Gui/View/ViewText.cs
  37. 82 93
      Terminal.Gui/Views/Button.cs
  38. 14 14
      Terminal.Gui/Views/ColorPicker.cs
  39. 5 3
      Terminal.Gui/Views/ComboBox.cs
  40. 56 145
      Terminal.Gui/Views/DateField.cs
  41. 240 0
      Terminal.Gui/Views/DatePicker.cs
  42. 5 14
      Terminal.Gui/Views/Dialog.cs
  43. 15 13
      Terminal.Gui/Views/FileDialog.cs
  44. 5 3
      Terminal.Gui/Views/FileSystemColorProvider.cs
  45. 1 1
      Terminal.Gui/Views/FrameView.cs
  46. 30 37
      Terminal.Gui/Views/GraphView/Annotations.cs
  47. 280 271
      Terminal.Gui/Views/GraphView/GraphView.cs
  48. 2 2
      Terminal.Gui/Views/Line.cs
  49. 1 1
      Terminal.Gui/Views/Menu/Menu.cs
  50. 1 1
      Terminal.Gui/Views/Menu/MenuBar.cs
  51. 6 6
      Terminal.Gui/Views/MessageBox.cs
  52. 9 2
      Terminal.Gui/Views/ProgressBar.cs
  53. 49 58
      Terminal.Gui/Views/RadioGroup.cs
  54. 16 16
      Terminal.Gui/Views/Slider.cs
  55. 1 1
      Terminal.Gui/Views/StatusBar.cs
  56. 1223 1144
      Terminal.Gui/Views/TextField.cs
  57. 37 37
      Terminal.Gui/Views/TextView.cs
  58. 1 1
      Terminal.Gui/Views/TileView.cs
  59. 337 277
      Terminal.Gui/Views/TimeField.cs
  60. 48 71
      Terminal.Gui/Views/Toplevel.cs
  61. 1 1
      Terminal.Gui/Views/ToplevelOverlapped.cs
  62. 50 58
      Terminal.Gui/Views/TreeView/TreeNode.cs
  63. 1242 1221
      Terminal.Gui/Views/TreeView/TreeView.cs
  64. 61 52
      Terminal.Gui/Views/Window.cs
  65. 2 2
      Terminal.Gui/Views/Wizard/Wizard.cs
  66. 0 1
      Terminal.sln
  67. 4 0
      UICatalog/Properties/launchSettings.json
  68. 257 232
      UICatalog/Scenario.cs
  69. 441 0
      UICatalog/Scenarios/Adornments.cs
  70. 9 9
      UICatalog/Scenarios/AllViewsTester.cs
  71. 1 1
      UICatalog/Scenarios/AutoSizeAndDirectionText.cs
  72. 3 3
      UICatalog/Scenarios/BackgroundWorkerCollection.cs
  73. 3 2
      UICatalog/Scenarios/BasicColors.cs
  74. 7 7
      UICatalog/Scenarios/Buttons.cs
  75. 2 2
      UICatalog/Scenarios/CharacterMap.cs
  76. 6 6
      UICatalog/Scenarios/Clipping.cs
  77. 1 1
      UICatalog/Scenarios/CollectionNavigatorTester.cs
  78. 126 129
      UICatalog/Scenarios/ColorPicker.cs
  79. 1 1
      UICatalog/Scenarios/ComboBoxIteration.cs
  80. 13 13
      UICatalog/Scenarios/ComputedLayout.cs
  81. 21 0
      UICatalog/Scenarios/DatePickers.cs
  82. 1 1
      UICatalog/Scenarios/Dialogs.cs
  83. 2 2
      UICatalog/Scenarios/DimAutoDemo.cs
  84. 2 2
      UICatalog/Scenarios/DynamicMenuBar.cs
  85. 1 1
      UICatalog/Scenarios/DynamicStatusBar.cs
  86. 1 1
      UICatalog/Scenarios/Editor.cs
  87. 0 401
      UICatalog/Scenarios/Frames.cs
  88. 568 519
      UICatalog/Scenarios/GraphViewExample.cs
  89. 6 4
      UICatalog/Scenarios/InvertColors.cs
  90. 5 5
      UICatalog/Scenarios/Keys.cs
  91. 7 7
      UICatalog/Scenarios/LabelsAsButtons.cs
  92. 2 2
      UICatalog/Scenarios/LineCanvasExperiment.cs
  93. 2 2
      UICatalog/Scenarios/LineDrawing.cs
  94. 61 61
      UICatalog/Scenarios/ListColumns.cs
  95. 1 1
      UICatalog/Scenarios/ListViewWithSelection.cs
  96. 2 2
      UICatalog/Scenarios/ListsAndCombos.cs
  97. 1 1
      UICatalog/Scenarios/MenuBarScenario.cs
  98. 1 1
      UICatalog/Scenarios/MessageBoxes.cs
  99. 1 1
      UICatalog/Scenarios/Mouse.cs
  100. 317 313
      UICatalog/Scenarios/Notepad.cs

+ 0 - 29
.editorconfig

@@ -26,31 +26,6 @@ csharp_space_between_method_declaration_name_and_open_parenthesis = true
 csharp_style_var_elsewhere = true:none
 
 # ReSharper properties
-resharper_align_linq_query = true
-resharper_align_multiline_binary_patterns = true
-resharper_align_multiline_calls_chain = true
-resharper_align_multiline_extends_list = true
-resharper_align_multiline_parameter = true
-resharper_blank_lines_around_region = 1
-resharper_braces_redundant = true
-resharper_csharp_alignment_tab_fill_style = optimal_fill
-resharper_csharp_max_line_length = 200
-resharper_csharp_stick_comment = false
-resharper_csharp_wrap_parameters_style = chop_if_long
-resharper_force_attribute_style = separate
-resharper_indent_type_constraints = true
-resharper_xmldoc_indent_size = 2
-resharper_xmldoc_indent_style = space
-resharper_xmldoc_tab_width = 2
-#resharper_int_align_binary_expressions = true
-resharper_int_align_comments = true
-resharper_int_align_invocations = true
-resharper_int_align_nested_ternary = true
-resharper_int_align_switch_expressions = true
-resharper_int_align_switch_sections = true
-resharper_local_function_body = expression_body
-resharper_remove_blank_lines_near_braces_in_declarations = true
-resharper_use_roslyn_logic_for_evident_types = true
 csharp_space_around_binary_operators = before_and_after
 csharp_using_directive_placement = outside_namespace:silent
 csharp_prefer_simple_using_statement = true:suggestion
@@ -96,10 +71,6 @@ csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
 csharp_style_prefer_not_pattern = true:suggestion
 csharp_style_prefer_extended_property_pattern = true:suggestion
 csharp_style_var_for_built_in_types = true:none
-resharper_wrap_before_linq_expression = true
-resharper_wrap_chained_binary_expressions = chop_if_long
-resharper_wrap_chained_binary_patterns = chop_if_long
-
 
 [*.{cs,vb}]
 dotnet_style_operator_placement_when_wrapping = beginning_of_line

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

+ 91 - 64
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 (() => new Toplevel(), 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;
@@ -196,8 +269,8 @@ public static partial class Application {
 		Current = Top;
 
 		// Ensure Top's layout is up to date.
-		Current.SetRelativeLayout (Driver.Bounds); 
-		
+		Current.SetRelativeLayout (Driver.Bounds);
+
 		_cachedSupportedCultures = GetSupportedCultures ();
 		_mainThreadId = Thread.CurrentThread.ManagedThreadId;
 		_initialized = true;
@@ -241,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)
@@ -402,7 +426,7 @@ public static partial class Application {
 		}
 
 		//if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
-			Toplevel.SetRelativeLayout (Driver.Bounds);
+		Toplevel.SetRelativeLayout (Driver.Bounds);
 		//}
 		Toplevel.LayoutSubviews ();
 		Toplevel.PositionToplevels ();
@@ -447,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) {
@@ -881,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"/>)
@@ -1141,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.
@@ -1227,7 +1251,7 @@ public static partial class Application {
 			}
 		}
 
-		bool FrameHandledMouseEvent (Frame frame)
+		bool FrameHandledMouseEvent (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);
@@ -1333,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 {
@@ -1357,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 {
@@ -1381,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;
 	}

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

+ 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 ();
+	}
+}

+ 9 - 0
Terminal.Gui/Resources/Strings.Designer.cs

@@ -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>

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

@@ -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 - 1
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs

@@ -124,7 +124,7 @@ namespace Terminal.Gui {
 		public override ColorScheme ColorScheme {
 			get {
 				if (colorScheme == null) {
-					colorScheme = Colors.Menu;
+					colorScheme = Colors.ColorSchemes ["Menu"];
 				}
 				return colorScheme;
 			}

+ 1 - 1
Terminal.Gui/Text/TextFormatter.cs

@@ -1325,7 +1325,7 @@ namespace Terminal.Gui {
 					NeedsFormat = false;
 					return _lines;
 				}
-				
+
 				if (NeedsFormat) {
 					var shown_text = _text;
 					if (FindHotKey (_text, HotKeySpecifier, true, out _hotKeyPos, out var newHotKey)) {

+ 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.FrameToScreen 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;
-		}
-
-	}
-}

+ 124 - 0
Terminal.Gui/View/Layout/PosDim.cs

@@ -4,9 +4,11 @@ namespace Terminal.Gui;
 
 /// <summary>
 /// Describes the position of a <see cref="View"/> which can be an absolute value, a percentage, centered, or
+/// Describes the position of a <see cref="View"/> which can be an absolute value, a percentage, centered, or
 /// relative to the ending dimension. Integer values are implicitly convertible to
 /// an absolute <see cref="Pos"/>. These objects are created using the static methods Percent,
 /// AnchorEnd, and Center. The <see cref="Pos"/> objects can be combined with the addition and
+/// AnchorEnd, and Center. The <see cref="Pos"/> objects can be combined with the addition and
 /// subtraction operators.
 /// </summary>
 /// <remarks>
@@ -132,6 +134,128 @@ namespace Terminal.Gui;
 /// 
 ///                 </list>
 ///         </para>
+///         <para>
+///         Use the <see cref="Pos"/> objects on the X or Y properties of a view to control the position.
+///         </para>
+///         <para>
+///         These can be used to set the absolute position, when merely assigning an
+///         integer value (via the implicit integer to <see cref="Pos"/> conversion), and they can be combined
+///         to produce more useful layouts, like: Pos.Center - 3, which would shift the position
+///         of the <see cref="View"/> 3 characters to the left after centering for example.
+///         </para>
+///         <para>
+///         Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View).
+///         The X(View) and Y(View) are
+///         aliases to Left(View) and Top(View) respectively.
+///         </para>
+///         <para>
+///                 <list type="table">
+///                         <listheader>
+///                                 <term>Pos Object</term>
+///                                 <description>Description</description>
+///                         </listheader>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.Function(Func{int})"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that computes the position by executing the provided
+///                                 function. The function will be called every time the position is needed.
+///                                 </description>
+///                         </item>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.Percent(float)"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that is a percentage of the width or height of the
+///                                 SuperView.
+///                                 </description>
+///                         </item>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.Anchor(int)"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom)
+///                                 of the dimension,
+///                                 useful to flush the layout from the right or bottom.
+///                                 </description>
+///                         </item>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.Center"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.
+///                                 </description>
+///                         </item>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.At(int)"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that is an absolute position based on the specified
+///                                 integer value.
+///                                 </description>
+///                         </item>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.Left"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
+///                                 <see cref="View"/>.
+///                                 </description>
+///                         </item>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.X(View)"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
+///                                 <see cref="View"/>.
+///                                 </description>
+///                         </item>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.Top(View)"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
+///                                 <see cref="View"/>.
+///                                 </description>
+///                         </item>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.Y(View)"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
+///                                 <see cref="View"/>.
+///                                 </description>
+///                         </item>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.Right(View)"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the
+///                                 specified <see cref="View"/>.
+///                                 </description>
+///                         </item>
+///                         <item>
+///                                 <term>
+///                                         <see cref="Pos.Bottom(View)"/>
+///                                 </term>
+///                                 <description>
+///                                 Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the
+///                                 specified <see cref="View"/>
+///                                 </description>
+///                         </item>
+/// 
+///                 </list>
+///         </para>
 /// </remarks>
 public class Pos {
 	internal virtual int Anchor (int width) => 0;

+ 120 - 135
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -1,5 +1,4 @@
 using System;
-using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Diagnostics;
@@ -22,6 +21,20 @@ namespace Terminal.Gui;
 ///         <see cref="View.Height"/>
 ///         objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.
 ///         </para>
+///         <para>
+///         Indicates the LayoutStyle for the <see cref="View"/>.
+///         </para>
+///         <para>
+///         If Absolute, the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and
+///         <see cref="View.Height"/>
+///         objects are all absolute values and are not relative. The position and size of the view is described by
+///         <see cref="View.Frame"/>.
+///         </para>
+///         <para>
+///         If Computed, one or more of the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or
+///         <see cref="View.Height"/>
+///         objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.
+///         </para>
 /// </summary>
 public enum LayoutStyle {
 	/// <summary>
@@ -87,7 +100,7 @@ public partial class View {
 
 			// TODO: Figure out if the below can be optimized.
 			if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) {
-				LayoutFrames ();
+				LayoutAdornments ();
 				SetTextFormatterSize ();
 				SetNeedsLayout ();
 				SetNeedsDisplay ();
@@ -101,21 +114,19 @@ public partial class View {
 	/// </summary>
 	/// <remarks>
 	///         <para>
-	///         The frames (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
-	///         content
-	///         and are not clipped by the View's Clip Area.
+	///         The adornments (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
+	///         content and are not clipped by the View's Clip Area.
 	///         </para>
 	///         <para>
-	///         Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
-	///         will change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout
-	///         of the
-	///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
+	///         Changing the size of an adornment (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
+	///         will change the size of <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout
+	///         of the <see cref="SuperView"/> and its <see cref="Subviews"/>.
 	///         </para>
 	/// </remarks>
-	public Frame Margin { get; private set; }
+	public Margin Margin { get; private set; }
 
 	/// <summary>
-	/// The frame (specified as a <see cref="Thickness"/>) inside of the view that offsets the <see cref="Bounds"/> from the
+	/// The adornment (specified as a <see cref="Thickness"/>) inside of the view that offsets the <see cref="Bounds"/> from the
 	/// <see cref="Margin"/>.
 	/// The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title.
 	/// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and
@@ -126,9 +137,8 @@ public partial class View {
 	///         <see cref="BorderStyle"/> provides a simple helper for turning a simple border frame on or off.
 	///         </para>
 	///         <para>
-	///         The frames (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
-	///         content
-	///         and are not clipped by the View's Clip Area.
+	///         The adornments (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
+	///         content and are not clipped by the View's Clip Area.
 	///         </para>
 	///         <para>
 	///         Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
@@ -137,7 +147,7 @@ public partial class View {
 	///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
 	///         </para>
 	/// </remarks>
-	public Frame Border { get; private set; }
+	public Border Border { get; private set; }
 
 	/// <summary>
 	/// Gets or sets whether the view has a one row/col thick border.
@@ -146,12 +156,12 @@ public partial class View {
 	///         <para>
 	///         This is a helper for manipulating the view's <see cref="Border"/>. Setting this property to any value other
 	///         than
-	///         <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s <see cref="Frame.Thickness"/>
+	///         <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s <see cref="Adornment.Thickness"/>
 	///         to `1` and <see cref="BorderStyle"/> to the value.
 	///         </para>
 	///         <para>
 	///         Setting this property to <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s
-	///         <see cref="Frame.Thickness"/>
+	///         <see cref="Adornment.Thickness"/>
 	///         to `0` and <see cref="BorderStyle"/> to <see cref="LineStyle.None"/>.
 	///         </para>
 	///         <para>
@@ -159,18 +169,15 @@ public partial class View {
 	///         </para>
 	/// </remarks>
 	public LineStyle BorderStyle {
-		get => Border?.BorderStyle ?? LineStyle.None;
+		get => Border.LineStyle;
 		set {
-			if (Border == null) {
-				throw new InvalidOperationException ("Border is null; this is likely a bug.");
-			}
 			if (value != LineStyle.None) {
 				Border.Thickness = new Thickness (1);
 			} else {
 				Border.Thickness = new Thickness (0);
 			}
-			Border.BorderStyle = value;
-			LayoutFrames ();
+			Border.LineStyle = value;
+			LayoutAdornments ();
 			SetNeedsLayout ();
 		}
 	}
@@ -181,9 +188,8 @@ public partial class View {
 	/// </summary>
 	/// <remarks>
 	///         <para>
-	///         The frames (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
-	///         content
-	///         and are not clipped by the View's Clip Area.
+	///         The adornments (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
+	///         content and are not clipped by the View's Clip Area.
 	///         </para>
 	///         <para>
 	///         Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
@@ -192,24 +198,73 @@ public partial class View {
 	///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
 	///         </para>
 	/// </remarks>
-	public Frame Padding { get; private set; }
+	public Padding Padding { get; private set; }
 
 	/// <summary>
 	///         <para>
-	///         Gets the LayoutStyle for the <see cref="View"/>.
-	///         </para>
-	///         <para>
-	///         If Absolute, the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and
-	///         <see cref="View.Height"/>
-	///         objects are all absolute values and are not relative. The position and size of the view is described by
-	///         <see cref="View.Frame"/>.
-	///         </para>
-	///         <para>
-	///         If Computed, one or more of the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or
-	///         <see cref="View.Height"/>
-	///         objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.
+	///         Gets the thickness describing the sum of the Adornments' thicknesses.
 	///         </para>
 	/// </summary>
+	/// <returns>A thickness that describes the sum of the Adornments' thicknesses.</returns>
+	public Thickness GetAdornmentsThickness ()
+	{
+		int left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
+		int top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
+		int right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
+		int bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
+		return new Thickness (left, top, right, bottom);
+	}
+
+	/// <summary>
+	/// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
+	/// <see cref="Margin"/>, <see cref="Border"/> and <see cref="Padding"/>.
+	/// </summary>
+	public Point GetBoundsOffset () => new (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
+
+	/// <summary>
+	/// This internal method is overridden by Adornment to do nothing to prevent recursion during View construction.
+	/// And, because Adornments don't have Adornments. It's internal to support unit tests.
+	/// </summary>
+	/// <param name="adornmentType"></param>
+	/// <exception cref="ArgumentNullException"></exception>
+	/// <exception cref="ArgumentException"></exception>
+	internal virtual Adornment CreateAdornment (Type adornmentType)
+	{
+		void ThicknessChangedHandler (object sender, EventArgs e)
+		{
+			if (IsInitialized) {
+				LayoutAdornments ();
+			}
+			SetNeedsLayout ();
+			SetNeedsDisplay ();
+		}
+
+		Adornment adornment;
+
+		adornment = Activator.CreateInstance (adornmentType, this) as Adornment;
+		adornment.ThicknessChanged += ThicknessChangedHandler;
+
+		return adornment;
+	}
+
+	/// <summary>
+	/// Controls how the View's <see cref="Frame"/> is computed during <see cref="LayoutSubviews"/>. If the style is set to
+	/// <see cref="LayoutStyle.Absolute"/>, LayoutSubviews does not change the <see cref="Frame"/>.
+	/// If the style is <see cref="LayoutStyle.Computed"/> the <see cref="Frame"/> is updated using
+	/// the <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// Setting this property to <see cref="LayoutStyle.Absolute"/> will cause <see cref="Frame"/> to determine the
+	/// size and position of the view. <see cref="X"/> and <see cref="Y"/> will be set to <see cref="Dim.DimAbsolute"/> using <see cref="Frame"/>.
+	/// </para>
+	/// <para>
+	/// Setting this property to <see cref="LayoutStyle.Computed"/> will cause the view to use the <see cref="LayoutSubviews"/> method to 
+	/// size and position of the view. If either of the <see cref="X"/> and <see cref="Y"/> properties are `null` they will be set to <see cref="Pos.PosAbsolute"/> using
+	/// the current value of <see cref="Frame"/>. 
+	/// If either of the <see cref="Width"/> and <see cref="Height"/> properties are `null` they will be set to <see cref="Dim.DimAbsolute"/> using <see cref="Frame"/>.
+	/// </para>
+	/// </remarks>
 	/// <value>The layout style.</value>
 	public LayoutStyle LayoutStyle {
 		get {
@@ -253,8 +308,13 @@ public partial class View {
 				Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}");
 			}
 #endif // DEBUG
-			var frameRelativeBounds = FrameGetInsideBounds ();
-			return new Rect (default, frameRelativeBounds.Size);
+			// BUGBUG: I think there's a bug here. This should be && not ||
+			if (Margin == null || Border == null || Padding == null) {
+				return new Rect (default, Frame.Size);
+			}
+			var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
+			var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
+			return new Rect (Point.Empty, new Size (width, height));
 		}
 		set {
 			// TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is
@@ -380,6 +440,7 @@ public partial class View {
 
 	/// <summary>
 	/// Gets or sets the height dimension of the view.
+	/// Gets or sets whether validation of <see cref="Pos"/> and <see cref="Dim"/> occurs.
 	/// </summary>
 	/// <value>The <see cref="Dim"/> object representing the height of the view (the number of rows).</value>
 	/// <remarks>
@@ -452,7 +513,7 @@ public partial class View {
 				break;
 			case Pos pos and Pos.PosCombine:
 				// Recursively check for not Absolute or not View
-				ThrowInvalid (view, (pos as Pos.PosCombine)._left,  name);
+				ThrowInvalid (view, (pos as Pos.PosCombine)._left, name);
 				ThrowInvalid (view, (pos as Pos.PosCombine)._right, name);
 				break;
 
@@ -461,7 +522,7 @@ public partial class View {
 				break;
 			case Dim dim and Dim.DimCombine:
 				// Recursively check for not Absolute or not View
-				ThrowInvalid (view, (dim as Dim.DimCombine)._left,  name);
+				ThrowInvalid (view, (dim as Dim.DimCombine)._left, name);
 				ThrowInvalid (view, (dim as Dim.DimCombine)._right, name);
 				break;
 			}
@@ -476,15 +537,15 @@ public partial class View {
 		foreach (var view in Subviews) {
 			if (Width is Dim.DimAuto { _min: null }) {
 				ThrowInvalid (view, view.Width, nameof (view.Width));
-				ThrowInvalid (view, view.X,     nameof (view.X));
+				ThrowInvalid (view, view.X, nameof (view.X));
 			}
 			if (Height is Dim.DimAuto { _min: null }) {
 				ThrowInvalid (view, view.Height, nameof (view.Height));
-				ThrowInvalid (view, view.Y,      nameof (view.Y));
+				ThrowInvalid (view, view.Y, nameof (view.Y));
 			}
 		}
 	}
-	
+
 	internal bool LayoutNeeded { get; private set; } = true;
 
 	/// <summary>
@@ -527,76 +588,7 @@ public partial class View {
 	/// </summary>
 	public event EventHandler Initialized;
 
-	/// <summary>
-	/// Helper to get the total thickness of the <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>.
-	/// </summary>
-	/// <returns>A thickness that describes the sum of the Frames' thicknesses.</returns>
-	public Thickness GetFramesThickness ()
-	{
-		var left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
-		var top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
-		var right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
-		var bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
-		return new Thickness (left, top, right, bottom);
-	}
-
-	/// <summary>
-	/// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
-	/// <see cref="Margin"/>, <see cref="Border"/> and <see cref="Padding"/>.
-	/// </summary>
-	public Point GetBoundsOffset () => new (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
-
-	/// <summary>
-	/// Creates the view's <see cref="Frame"/> objects. This internal method is overridden by Frame to do nothing
-	/// to prevent recursion during View construction.
-	/// </summary>
-	internal virtual void CreateFrames ()
-	{
-		void ThicknessChangedHandler (object sender, EventArgs e)
-		{
-			if (IsInitialized) {
-				LayoutFrames ();
-			}
-			SetNeedsLayout ();
-			SetNeedsDisplay ();
-		}
-
-		if (Margin != null) {
-			Margin.ThicknessChanged -= ThicknessChangedHandler;
-			Margin.Dispose ();
-		}
-		Margin = new Frame { Id = "Margin", Thickness = new Thickness (0) };
-		Margin.ThicknessChanged += ThicknessChangedHandler;
-		Margin.Parent = this;
-
-		if (Border != null) {
-			Border.ThicknessChanged -= ThicknessChangedHandler;
-			Border.Dispose ();
-		}
-		Border = new Frame { Id = "Border", Thickness = new Thickness (0) };
-		Border.ThicknessChanged += ThicknessChangedHandler;
-		Border.Parent = this;
 
-		// TODO: Create View.AddAdornment
-
-		if (Padding != null) {
-			Padding.ThicknessChanged -= ThicknessChangedHandler;
-			Padding.Dispose ();
-		}
-		Padding = new Frame { Id = "Padding", Thickness = new Thickness (0) };
-		Padding.ThicknessChanged += ThicknessChangedHandler;
-		Padding.Parent = this;
-	}
-
-	Rect FrameGetInsideBounds ()
-	{
-		if (Margin == null || Border == null || Padding == null) {
-			return new Rect (default, Frame.Size);
-		}
-		var width = Math.Max (0,  Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
-		var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
-		return new Rect (Point.Empty, new Size (width, height));
-	}
 
 	// Diagnostics to highlight when X or Y is read before the view has been initialized
 	Pos VerifyIsInitialized (Pos pos, string member)
@@ -632,33 +624,24 @@ public partial class View {
 	/// </remarks>
 	internal void OnResizeNeeded ()
 	{
-		SuperView?.CheckDimAuto ();
-		
 		// TODO: Identify a real-world use-case where this API should be virtual. 
 		// TODO: Until then leave it `internal` and non-virtual
 		// First try SuperView.Bounds, then Application.Top, then Driver.Bounds.
 		// Finally, if none of those are valid, use int.MaxValue (for Unit tests).
-		var relativeBounds = SuperView is { IsInitialized: true }        ? SuperView.Bounds :
+		var relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds :
 			Application.Top != null && Application.Top.IsInitialized ? Application.Top.Bounds :
 										   Application.Driver?.Bounds ??
 										   new Rect (0, 0, int.MaxValue, int.MaxValue);
 		SetRelativeLayout (relativeBounds);
-		
+
 		// TODO: Determine what, if any of the below is actually needed here.
 		if (IsInitialized) {
 			SetFrameToFitText ();
-			LayoutFrames ();
+			LayoutAdornments ();
 			SetTextFormatterSize ();
 			SetNeedsLayout ();
 			SetNeedsDisplay ();
 		}
-
-		//if (IsInitialized && SuperView != null && LayoutStyle == LayoutStyle.Computed && (SuperView?.Height is Dim.DimAuto || SuperView?.Width is Dim.DimAuto)) {
-		//	// DimAuto is in play, force a layout.
-		//	// BUGBUG: This can cause LayoutSubviews to be called recursively resulting in a deadlock. 
-		//	//         SetNeedsLayout should be sufficient, but it's not.
-		//	SuperView.LayoutSubviews ();
-		//}
 	}
 
 	/// <summary>
@@ -792,6 +775,8 @@ public partial class View {
 		Debug.Assert (_width != null);
 		Debug.Assert (_height != null);
 
+		CheckDimAuto ();
+
 		int newX, newW, newY, newH;
 		var autosize = Size.Empty;
 
@@ -824,7 +809,7 @@ public partial class View {
 
 				case Dim.DimCombine combine:
 					// TODO: Move combine logic into DimCombine?
-					var leftNewDim = GetNewDimension (combine._left,   location, dimension, autosize);
+					var leftNewDim = GetNewDimension (combine._left, location, dimension, autosize);
 					var rightNewDim = GetNewDimension (combine._right, location, dimension, autosize);
 					if (combine._add) {
 						newDimension = leftNewDim + rightNewDim;
@@ -840,7 +825,7 @@ public partial class View {
 					break;
 
 				case Dim.DimAuto auto:
-					var thickness = GetFramesThickness ();
+					var thickness = GetAdornmentsThickness ();
 					//newDimension = GetNewDimension (auto._min, location, dimension, autosize);
 					if (width) {
 						var furthestRight = Subviews.Count == 0 ? 0 : Subviews.Where (v => v.X is not Pos.PosAnchorEnd).Max (v => v.Frame.X + v.Frame.Width);
@@ -883,6 +868,7 @@ public partial class View {
 				break;
 
 			case Pos.PosCombine combine:
+				// TODO: Move combine logic into PosCombine?
 				// TODO: Move combine logic into PosCombine?
 				int left, right;
 				(left, newDimension) = GetNewLocationAndDimension (width,  superviewBounds, combine._left,  dim, autosizeDimension);
@@ -936,15 +922,14 @@ public partial class View {
 
 			if (IsInitialized) {
 				// TODO: Figure out what really is needed here. All unit tests (except AutoSize) pass as-is
-				//LayoutFrames ();
 				SetTextFormatterSize ();
 				SetNeedsLayout ();
-				//SetNeedsDisplay ();
 			}
 
 			// BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
 			if (!SetFrameToFitText ()) {
 				SetTextFormatterSize ();
+				SetTextFormatterSize ();
 			}
 		}
 	}
@@ -1099,12 +1084,12 @@ public partial class View {
 	} // TopologicalSort
 
 	/// <summary>
-	/// Overriden by <see cref="Frame"/> to do nothing, as the <see cref="Frame"/> does not have frames.
+	/// Overriden by <see cref="Adornment"/> to do nothing, as the <see cref="Adornment"/> does not have adornments.
 	/// </summary>
-	internal virtual void LayoutFrames ()
+	internal virtual void LayoutAdornments ()
 	{
 		if (Margin == null) {
-			return; // CreateFrames() has not been called yet
+			return; // CreateAdornments () has not been called yet
 		}
 
 		if (Margin.Frame.Size != Frame.Size) {
@@ -1165,7 +1150,7 @@ public partial class View {
 
 		CheckDimAuto ();
 
-		LayoutFrames ();
+		LayoutAdornments (); 
 
 		var oldBounds = Bounds;
 		OnLayoutStarted (new LayoutEventArgs { OldBounds = oldBounds });

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

@@ -1,6 +1,6 @@
 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

+ 7 - 11
Terminal.Gui/View/View.cs

@@ -68,7 +68,7 @@ namespace Terminal.Gui;
 /// 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
+/// care of repositioning views when view's adornments are resized or
 /// if the terminal size changes.
 /// </para>
 /// <para>
@@ -88,7 +88,7 @@ namespace Terminal.Gui;
 /// 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"/>.
+/// 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
@@ -430,9 +430,9 @@ public partial class View : Responder, ISupportInitializeNotification {
 	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
 	///         </para>
 	/// </remarks>
-	/// <param name="rect">Location.</param>
+	/// <param name="frame">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);
+	public View (Rect frame, string text) => SetInitialProperties (text, frame, LayoutStyle.Absolute);
 
 
 	/// <summary>
@@ -493,7 +493,9 @@ public partial class View : Responder, ISupportInitializeNotification {
 
 		AddCommands ();
 
-		CreateFrames ();
+		Margin = CreateAdornment (typeof (Margin)) as Margin;
+		Border = CreateAdornment (typeof (Border)) as Border;
+		Padding = CreateAdornment (typeof (Padding)) as Padding;
 	}
 
 	/// <summary>
@@ -534,12 +536,6 @@ public partial class View : Responder, ISupportInitializeNotification {
 		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 ();
-
 		}
 
 		//throw new InvalidOperationException ("The view is already initialized.");

+ 30 - 11
Terminal.Gui/View/ViewDrawing.cs

@@ -58,7 +58,7 @@ public partial class View {
 	/// 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.
+	/// this View's <see cref="OnDrawAdornments"/> method will be called to render the borders.
 	/// </summary>
 	public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
 
@@ -87,7 +87,14 @@ public partial class View {
 	/// 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;
+	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.
@@ -97,7 +104,14 @@ public partial class View {
 	/// 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;
+	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.
@@ -122,7 +136,7 @@ public partial class View {
 	/// </summary>
 	protected void ClearNeedsDisplay ()
 	{
-		_needsDisplayRect   = Rect.Empty;
+		_needsDisplayRect = Rect.Empty;
 		SubViewNeedsDisplay = false;
 	}
 
@@ -159,7 +173,7 @@ public partial class View {
 		} else {
 			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 w = Math.Max (_needsDisplayRect.Width, region.Width);
 			var h = Math.Max (_needsDisplayRect.Height, region.Height);
 			_needsDisplayRect = new Rect (x, y, w, h);
 		}
@@ -260,6 +274,9 @@ public partial class View {
 	/// </remarks>
 	public Rect ClipToBounds ()
 	{
+		if (Driver == null) {
+			return Rect.Empty;
+		}
 		var previous = Driver.Clip;
 		Driver.Clip = Rect.Intersect (previous, BoundsToScreen (Bounds));
 		return previous;
@@ -334,7 +351,7 @@ public partial class View {
 	/// 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;
@@ -372,13 +389,13 @@ public partial class View {
 		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
@@ -389,7 +406,9 @@ public partial class View {
 			OnDrawContent (Bounds);
 		}
 
-		Driver.Clip = prevClip;
+		if (Driver != null) {
+			Driver.Clip = prevClip;
+		}
 
 		OnRenderLineCanvas ();
 		// Invoke DrawContentCompleteEvent
@@ -474,7 +493,7 @@ public partial class View {
 	{
 		if (NeedsDisplay) {
 			if (SuperView != null) {
-				Clear (BoundsToScreen (Bounds));
+				Clear (BoundsToScreen (contentArea));
 			}
 
 			if (!string.IsNullOrEmpty (TextFormatter.Text)) {
@@ -483,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 ();

+ 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;
 	}
-}
+}

+ 10 - 11
Terminal.Gui/View/ViewSubViews.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace Terminal.Gui; 
+namespace Terminal.Gui;
 
 public partial class View {
 	static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
@@ -71,16 +71,16 @@ public partial class View {
 			_addingView = true;
 			if (SuperView?.CanFocus == false) {
 				SuperView._addingView = true;
-				SuperView.CanFocus    = true;
+				SuperView.CanFocus = true;
 				SuperView._addingView = false;
 			}
-			CanFocus       = true;
+			CanFocus = true;
 			view._tabIndex = _tabIndexes.IndexOf (view);
-			_addingView    = false;
+			_addingView = false;
 		}
 		if (view.Enabled && !Enabled) {
 			view._oldEnabled = true;
-			view.Enabled     = false;
+			view.Enabled = false;
 		}
 
 		OnAdded (new SuperViewChangedEventArgs (this, view));
@@ -88,7 +88,6 @@ public partial class View {
 			view.BeginInit ();
 			view.EndInit ();
 		}
-
 		CheckDimAuto ();
 		SetNeedsLayout ();
 		SetNeedsDisplay ();
@@ -158,7 +157,7 @@ public partial class View {
 		_subviews.Remove (view);
 		_tabIndexes.Remove (view);
 		view._superView = null;
-		view._tabIndex  = -1;
+		view._tabIndex = -1;
 		SetNeedsLayout ();
 		SetNeedsDisplay ();
 
@@ -379,14 +378,14 @@ public partial class View {
 							if (!value) {
 								view._oldCanFocus = view.CanFocus;
 								view._oldTabIndex = view._tabIndex;
-								view.CanFocus     = false;
-								view._tabIndex    = -1;
+								view.CanFocus = false;
+								view._tabIndex = -1;
 							} else {
 								if (_addingView) {
 									view._addingView = true;
 								}
-								view.CanFocus    = view._oldCanFocus;
-								view._tabIndex   = view._oldTabIndex;
+								view.CanFocus = view._oldCanFocus;
+								view._tabIndex = view._oldTabIndex;
 								view._addingView = false;
 							}
 						}

+ 2 - 2
Terminal.Gui/View/ViewText.cs

@@ -193,7 +193,7 @@ public partial class View {
 			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);
@@ -307,7 +307,7 @@ public partial class View {
 			x = Bounds.X;
 			y = Bounds.Y;
 		}
-		var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); 
+		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);

+ 82 - 93
Terminal.Gui/Views/Button.cs

@@ -1,4 +1,4 @@
-//
+//
 // Button.cs: Button control
 //
 // Authors:
@@ -10,65 +10,64 @@ 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>
+///         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>
 /// </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 +75,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,10 +128,10 @@ 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;
@@ -120,42 +140,22 @@ public class Button : View {
 
 		// Override default behavior of View
 		// Command.Default sets focus
-		AddCommand (Command.Accept, () => { OnClicked (); return true; });
+		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 {
@@ -164,31 +164,20 @@ public class Button : View {
 		}
 	}
 
-	bool AcceptKey ()
-	{
-		//if (!HasFocus) {
-		//	SetFocus ();
-		//}
-		OnClicked ();
-		return true;
-	}
 
 	/// <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;
 
@@ -214,7 +203,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;
@@ -231,4 +220,4 @@ public class Button : View {
 
 		return base.OnEnter (view);
 	}
-}
+}

+ 14 - 14
Terminal.Gui/Views/ColorPicker.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Text;
 
-namespace Terminal.Gui; 
+namespace Terminal.Gui;
 
 /// <summary>
 /// Event arguments for the <see cref="Color"/> events.
@@ -94,9 +94,9 @@ public class ColorPicker : View {
 			var prev = (ColorName)_selectColorIndex;
 			_selectColorIndex = (int)value;
 			ColorChanged?.Invoke (this, new ColorEventArgs {
-									       PreviousColor = new Color (prev),
-									       Color         = new Color (value)
-								       });
+				PreviousColor = new Color (prev),
+				Color = new Color (value)
+			});
 			SetNeedsDisplay ();
 		}
 	}
@@ -112,8 +112,8 @@ public class ColorPicker : View {
 		AddCommands ();
 		AddKeyBindings ();
 		LayoutStarted += (o, a) => {
-			var thickness = GetFramesThickness ();
-			Width  = _cols * BoxWidth + thickness.Vertical;
+			var thickness = GetAdornmentsThickness ();
+			Width = _cols * BoxWidth + thickness.Vertical;
 			Height = _rows * BoxHeight + thickness.Horizontal;
 		};
 	}
@@ -123,9 +123,9 @@ public class ColorPicker : View {
 	/// </summary>
 	void AddCommands ()
 	{
-		AddCommand (Command.Left,     () => MoveLeft ());
-		AddCommand (Command.Right,    () => MoveRight ());
-		AddCommand (Command.LineUp,   () => MoveUp ());
+		AddCommand (Command.Left, () => MoveLeft ());
+		AddCommand (Command.Right, () => MoveRight ());
+		AddCommand (Command.LineUp, () => MoveUp ());
 		AddCommand (Command.LineDown, () => MoveDown ());
 	}
 
@@ -134,10 +134,10 @@ public class ColorPicker : View {
 	/// </summary>
 	void AddKeyBindings ()
 	{
-		KeyBindings.Add (KeyCode.CursorLeft,  Command.Left);
+		KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
 		KeyBindings.Add (KeyCode.CursorRight, Command.Right);
-		KeyBindings.Add (KeyCode.CursorUp,    Command.LineUp);
-		KeyBindings.Add (KeyCode.CursorDown,  Command.LineDown);
+		KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+		KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
 	}
 
 	///<inheritdoc/>
@@ -190,10 +190,10 @@ public class ColorPicker : View {
 		} 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 (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 (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 ()) {

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

@@ -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) { 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) { 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);
+	}
+}

+ 5 - 14
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,11 +65,13 @@ 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;
 
+		KeyBindings.Add (Key.Esc, Command.QuitToplevel);
+
 		if (buttons != null) {
 			foreach (var b in buttons) {
 				AddButton (b);
@@ -104,6 +106,7 @@ public class Dialog : Window {
 		//button.AutoSize = false; // BUGBUG: v2 - Hack to get around autosize not accounting for Margin?
 		buttons.Add (button);
 		Add (button);
+
 		SetNeedsDisplay ();
 		if (IsInitialized) {
 			LayoutSubviews ();
@@ -228,16 +231,4 @@ public class Dialog : Window {
 			break;
 		}
 	}
-
-	// BUGBUG: Why is this not handled by a key binding???
-	///<inheritdoc/>
-	public override bool OnProcessKeyDown (Key a)
-	{
-		switch (a.KeyCode) {
-		case KeyCode.Esc:
-			Application.RequestStop (this);
-			return true;
-		}
-		return false;
-	}
 }

+ 15 - 13
Terminal.Gui/Views/FileDialog.cs

@@ -8,7 +8,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Terminal.Gui.Resources;
 
-namespace Terminal.Gui; 
+namespace Terminal.Gui;
 
 /// <summary>
 /// Modal dialog for selecting files/directories. Has auto-complete and expandable
@@ -91,7 +91,7 @@ public class FileDialog : Dialog {
 		btnOk.Clicked += (s, e) => Accept (true);
 		btnOk.KeyDown += (s, k) => {
 			NavigateIf (k, KeyCode.CursorLeft, btnCancel);
-			NavigateIf (k, KeyCode.CursorUp,   tableView);
+			NavigateIf (k, KeyCode.CursorUp, tableView);
 		};
 
 		btnCancel = new Button (Strings.btnCancel) {
@@ -99,8 +99,8 @@ public class FileDialog : Dialog {
 			X = Pos.Right (btnOk) + 1
 		};
 		btnCancel.KeyDown += (s, k) => {
-			NavigateIf (k, KeyCode.CursorLeft,  btnToggleSplitterCollapse);
-			NavigateIf (k, KeyCode.CursorUp,    tableView);
+			NavigateIf (k, KeyCode.CursorLeft, btnToggleSplitterCollapse);
+			NavigateIf (k, KeyCode.CursorUp, tableView);
 			NavigateIf (k, KeyCode.CursorRight, btnOk);
 		};
 		btnCancel.Clicked += (s, e) => {
@@ -265,10 +265,10 @@ public class FileDialog : Dialog {
 		tableView.KeyUp += (s, k) => k.Handled = TableView_KeyUp (k);
 		tableView.SelectedCellChanged += TableView_SelectedCellChanged;
 
-		tableView.KeyBindings.Add (KeyCode.Home,                     Command.TopHome);
-		tableView.KeyBindings.Add (KeyCode.End,                      Command.BottomEnd);
+		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);
+		tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.BottomEndExtend);
 
 		treeView.KeyDown += (s, k) => {
 
@@ -475,7 +475,7 @@ public class FileDialog : Dialog {
 	string GetUpButtonText () => Style.UseUnicodeCharacters ? "◭" : "▲";
 
 	string GetToggleSplitterText (bool isExpanded) => isExpanded ?
-		new string ((char)Glyphs.LeftArrow.Value,  2) :
+		new string ((char)Glyphs.LeftArrow.Value, 2) :
 		new string ((char)Glyphs.RightArrow.Value, 2);
 
 	void Delete ()
@@ -657,7 +657,9 @@ public class FileDialog : Dialog {
 		tbPath.Caption = Style.PathCaption;
 		tbFind.Caption = Style.SearchCaption;
 
-		tbPath.Autocomplete.ColorScheme.Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background);
+		tbPath.Autocomplete.ColorScheme = new ColorScheme (tbPath.ColorScheme) {
+			Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background)
+		};
 
 		_treeRoots = Style.TreeRootGetter ();
 		Style.IconProvider.IsOpenGetter = treeView.IsExpanded;
@@ -1183,10 +1185,10 @@ public class FileDialog : Dialog {
 
 		// TODO: Add some kind of cache for this
 		return new ColorScheme {
-			Normal = new Attribute (color,    black),
+			Normal = new Attribute (color, black),
 			HotNormal = new Attribute (color, black),
-			Focus = new Attribute (black,     color),
-			HotFocus = new Attribute (black,  color)
+			Focus = new Attribute (black, color),
+			HotFocus = new Attribute (black, color)
 		};
 	}
 
@@ -1273,7 +1275,7 @@ public class FileDialog : Dialog {
 		// This portion is flexible based on the column clicked (e.g. alphabetical)
 		var ordered =
 			currentSortIsAsc ?
-				forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn,           f)) :
+				forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)) :
 				forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f));
 
 		State.Children = ordered.ToArray ();

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

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

+ 1 - 1
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;
 

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

+ 49 - 58
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;
 
@@ -40,18 +40,6 @@ public class RadioGroup : View {
 		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 (string [] radioLabels, int selected)
 	{
 		HotKeySpecifier = new Rune ('_');
@@ -86,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 ();
@@ -116,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) {
-				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;
 		}
@@ -198,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, true, out _, out var hotKey)
 					&& (key.NoAlt.NoCtrl.NoShift) == hotKey) {
 					SelectedItem = i;
 					keyEvent.Scope = KeyBindingScope.HotKey;
@@ -212,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;
@@ -231,11 +235,11 @@ 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;
 			}
@@ -275,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;
 		}
@@ -373,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 ();
@@ -395,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
-}

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

@@ -808,12 +808,12 @@ public class Slider<T> : View {
 		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 ())));
 		}
 	}
 
@@ -1047,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;
@@ -1187,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;
@@ -1459,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 ();
 	}
@@ -1497,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 ();

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

@@ -1,1375 +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;
-
-			LayoutComplete += TextField_LayoutComplete;
-
-			// 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;
-
-			KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu);
-		}
+	CursorVisibility _savedCursorVisibility;
+	int _selectedStart = -1; // -1 represents there is no text selection.
+	string _selectedText;
 
-		private void TextField_LayoutComplete (object sender, LayoutEventArgs e)
-		{
-			// Don't let height > 1
-			if (Frame.Height > 1) {
-				Height = 1;
-			} 
-		}
+	int _start;
+	List<Rune> _text;
 
+	CursorVisibility _visibility;
 
-		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);
-		}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="TextField"/> class using <see cref="LayoutStyle.Computed"/> positioning.
+	/// </summary>
+	public TextField () : this (string.Empty) { }
 
-		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>
+	/// <param name="text">Initial text contents.</param>
+	public TextField (string text) : base (text) => SetInitialProperties (text, text.GetRuneCount () + 1);
 
-			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.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);
 
-		void TextField_Initialized (object sender, EventArgs e)
-		{
-			Autocomplete.HostControl = this;
-			Autocomplete.PopupInsideContainer = false;
-		}
+	/// <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; }
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			if (IsInitialized) {
-				Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
-			}
+	/// <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);
 
-			return base.OnEnter (view);
-		}
+	/// <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; }
 
-		///<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 ();
-
-		/// <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;
 }

+ 37 - 37
Terminal.Gui/Views/TextView.cs

@@ -546,8 +546,8 @@ class TextModel {
 	string ReplaceText (List<RuneCell> source, string textToReplace, string matchText, int col)
 	{
 		var origTxt = ToString (source);
-		(var _, var len) = DisplaySize (source,  0,                      col,                     false);
-		(var _, var len2) = DisplaySize (source, col,                    col + matchText.Length,  false);
+		(var _, var len) = DisplaySize (source, 0, col, false);
+		(var _, var len2) = DisplaySize (source, col, col + matchText.Length, false);
 		(var _, var len3) = DisplaySize (source, col + matchText.Length, origTxt.GetRuneCount (), false);
 
 		return origTxt [..len] +
@@ -2158,9 +2158,9 @@ public class TextView : View {
 			SetOverwrite (false);
 			return true;
 		});
-		AddCommand (Command.Tab,          () => ProcessTab ());
-		AddCommand (Command.BackTab,      () => ProcessBackTab ());
-		AddCommand (Command.NextView,     () => ProcessMoveNextView ());
+		AddCommand (Command.Tab, () => ProcessTab ());
+		AddCommand (Command.BackTab, () => ProcessBackTab ());
+		AddCommand (Command.NextView, () => ProcessMoveNextView ());
 		AddCommand (Command.PreviousView, () => ProcessMovePreviousView ());
 		AddCommand (Command.Undo, () => {
 			Undo ();
@@ -2181,100 +2181,100 @@ public class TextView : View {
 		});
 
 		// Default keybindings for this view
-		KeyBindings.Add (KeyCode.PageDown,             Command.PageDown);
+		KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
 		KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.PageDown);
 
 		KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend);
 
-		KeyBindings.Add (KeyCode.PageUp,        Command.PageUp);
+		KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
 		KeyBindings.Add ('V' + KeyCode.AltMask, Command.PageUp);
 
 		KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend);
 
 		KeyBindings.Add (KeyCode.N | KeyCode.CtrlMask, Command.LineDown);
-		KeyBindings.Add (KeyCode.CursorDown,           Command.LineDown);
+		KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
 
 		KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend);
 
 		KeyBindings.Add (KeyCode.P | KeyCode.CtrlMask, Command.LineUp);
-		KeyBindings.Add (KeyCode.CursorUp,             Command.LineUp);
+		KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
 
 		KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend);
 
 		KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
-		KeyBindings.Add (KeyCode.CursorRight,          Command.Right);
+		KeyBindings.Add (KeyCode.CursorRight, Command.Right);
 
 		KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend);
 
 		KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
-		KeyBindings.Add (KeyCode.CursorLeft,           Command.Left);
+		KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
 
 		KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend);
 
 		KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
 
-		KeyBindings.Add (KeyCode.Home,                 Command.StartOfLine);
+		KeyBindings.Add (KeyCode.Home, Command.StartOfLine);
 		KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.StartOfLine);
 
 		KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.StartOfLineExtend);
 
-		KeyBindings.Add (KeyCode.Delete,               Command.DeleteCharRight);
+		KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
 		KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
 
-		KeyBindings.Add (KeyCode.End,                  Command.EndOfLine);
+		KeyBindings.Add (KeyCode.End, Command.EndOfLine);
 		KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.EndOfLine);
 
 		KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.EndOfLineExtend);
 
-		KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask,                          Command.CutToEndLine); // kill-to-end
+		KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine); // kill-to-end
 		KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToEndLine); // kill-to-end
 
-		KeyBindings.Add (KeyCode.K | KeyCode.AltMask,                              Command.CutToStartLine); // kill-to-start
+		KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine); // kill-to-start
 		KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToStartLine); // kill-to-start
 
-		KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask,     Command.Paste); // Control-y, yank
+		KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Paste); // Control-y, yank
 		KeyBindings.Add (KeyCode.Space | KeyCode.CtrlMask, Command.ToggleExtend);
 
-		KeyBindings.Add ('C' + KeyCode.AltMask,        Command.Copy);
+		KeyBindings.Add ('C' + KeyCode.AltMask, Command.Copy);
 		KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy);
 
-		KeyBindings.Add ('W' + KeyCode.AltMask,        Command.Cut);
+		KeyBindings.Add ('W' + KeyCode.AltMask, Command.Cut);
 		KeyBindings.Add (KeyCode.W | KeyCode.CtrlMask, Command.Cut);
 		KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut);
 
 		KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft);
-		KeyBindings.Add ('B' + KeyCode.AltMask,                 Command.WordLeft);
+		KeyBindings.Add ('B' + KeyCode.AltMask, Command.WordLeft);
 
 		KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordLeftExtend);
 
 		KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight);
-		KeyBindings.Add ('F' + KeyCode.AltMask,                  Command.WordRight);
+		KeyBindings.Add ('F' + KeyCode.AltMask, Command.WordRight);
 
 		KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordRightExtend);
-		KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask,                          Command.KillWordForwards);  // kill-word-forwards
-		KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask,                       Command.KillWordBackwards); // kill-word-backwards
+		KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards);  // kill-word-forwards
+		KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); // kill-word-backwards
 
 		// BUGBUG: If AllowsReturn is false, Key.Enter should not be bound (so that Toplevel can cause Command.Accept).
-		KeyBindings.Add (KeyCode.Enter,                                       Command.NewLine);
-		KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask,                      Command.BottomEnd);
-		KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask | KeyCode.ShiftMask,  Command.BottomEndExtend);
-		KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask,                     Command.TopHome);
+		KeyBindings.Add (KeyCode.Enter, Command.NewLine);
+		KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.BottomEnd);
+		KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.BottomEndExtend);
+		KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.TopHome);
 		KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.TopHomeExtend);
-		KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask,                        Command.SelectAll);
-		KeyBindings.Add (KeyCode.Insert,                                      Command.ToggleOverwrite);
-		KeyBindings.Add (KeyCode.Tab,                                         Command.Tab);
-		KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask,                     Command.BackTab);
+		KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll);
+		KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite);
+		KeyBindings.Add (KeyCode.Tab, Command.Tab);
+		KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.BackTab);
 
-		KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask,           Command.NextView);
+		KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextView);
 		KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextView);
 
 		KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.PreviousView);
-		KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey,          Command.PreviousView);
+		KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousView);
 
 		KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo);
 		KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.Redo);
 
-		KeyBindings.Add (KeyCode.G | KeyCode.CtrlMask,                     Command.DeleteAll);
+		KeyBindings.Add (KeyCode.G | KeyCode.CtrlMask, Command.DeleteAll);
 		KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll);
 
 		_currentCulture = Thread.CurrentThread.CurrentUICulture;
@@ -3183,7 +3183,7 @@ public class TextView : View {
 		foreach (char ch in toAdd) {
 			Key key;
 			try {
-				key = new Key(ch);
+				key = new Key (ch);
 			} catch (Exception) {
 				throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
 			}
@@ -3363,8 +3363,8 @@ public class TextView : View {
 		var offB = OffSetBackground ();
 		var line = GetCurrentLine ();
 		var need = NeedsDisplay || _wrapNeeded || !Used;
-		var tSize = TextModel.DisplaySize (line, -1,          -1,            false, TabWidth);
-		var dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true,  TabWidth);
+		var tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
+		var dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth);
 		if (!_wordWrap && CurrentColumn < _leftColumn) {
 			_leftColumn = CurrentColumn;
 			need = true;

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

+ 48 - 71
Terminal.Gui/Views/Toplevel.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 
@@ -12,7 +12,7 @@ namespace Terminal.Gui;
 /// <remarks>
 ///         <para>
 ///         Toplevels can run as modal (popup) views, started by calling
-///         <see cref="Application.Run(Toplevel, System.Func{System.Exception,bool})"/>.
+///         <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>
@@ -22,12 +22,10 @@ namespace Terminal.Gui;
 ///         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})"/>.
+///         call <see cref="Application.Run(Toplevel, Func{Exception,bool})"/>.
 ///         </para>
 /// </remarks>
 public partial class Toplevel : View {
-	internal static Point? _dragPosition;
-	Point _startGrabPoint;
 
 	/// <summary>
 	/// Initializes a new instance of the <see cref="Toplevel"/> class with the specified
@@ -253,7 +251,7 @@ public partial class Toplevel : View {
 
 	void SetInitialProperties ()
 	{
-		ColorScheme = Colors.TopLevel;
+		ColorScheme = Colors.ColorSchemes ["TopLevel"];
 
 		Application.GrabbingMouse += Application_GrabbingMouse;
 		Application.UnGrabbingMouse += Application_UnGrabbingMouse;
@@ -292,64 +290,32 @@ public partial class Toplevel : View {
 			Application.Refresh ();
 			return true;
 		});
-		AddCommand (Command.Accept, () => {
-			// TODO: Perhaps all views should support the concept of being default?
-			// TODO: It's bad that Toplevel is tightly coupled with Button
-			if (Subviews.FirstOrDefault (v => v is Button && ((Button)v).IsDefault && ((Button)v).Enabled) is Button defaultBtn) {
-				defaultBtn.InvokeCommand (Command.Accept);
-				return true;
-			}
-			return false;
-		});
+
 
 		// Default keybindings for this view
-		KeyBindings.Add ((KeyCode)Application.QuitKey, Command.QuitToplevel);
-
-		KeyBindings.Add (KeyCode.CursorRight, Command.NextView);
-		KeyBindings.Add (KeyCode.CursorDown,  Command.NextView);
-		KeyBindings.Add (KeyCode.CursorLeft,  Command.PreviousView);
-		KeyBindings.Add (KeyCode.CursorUp,    Command.PreviousView);
-		KeyBindings.Add (KeyCode.CursorDown,  Command.NextView);
-		KeyBindings.Add (KeyCode.CursorLeft,  Command.PreviousView);
-		KeyBindings.Add (KeyCode.CursorUp,    Command.PreviousView);
-
-		KeyBindings.Add (KeyCode.Tab,                                        Command.NextView);
-		KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask,                    Command.PreviousView);
-		KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask,                     Command.NextViewOrTop);
-		KeyBindings.Add (KeyCode.Tab,                                        Command.NextView);
-		KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask,                    Command.PreviousView);
-		KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask,                     Command.NextViewOrTop);
-		KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.PreviousViewOrTop);
-
-		KeyBindings.Add (KeyCode.F5,                                Command.Refresh);
-		KeyBindings.Add ((KeyCode)Application.AlternateForwardKey,  Command.NextViewOrTop);     // Needed on Unix
-		KeyBindings.Add (KeyCode.F5,                                Command.Refresh);
-		KeyBindings.Add ((KeyCode)Application.AlternateForwardKey,  Command.NextViewOrTop);     // Needed on Unix
-		KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
+		KeyBindings.Add (Application.QuitKey, Command.QuitToplevel);
 
-#if UNIX_KEY_BINDINGS
-			KeyBindings.Add (Key.Z | Key.CtrlMask, Command.Suspend);
-			KeyBindings.Add (Key.L | Key.CtrlMask, Command.Refresh);// Unix
-			KeyBindings.Add (Key.F | Key.CtrlMask, Command.NextView);// Unix
-			KeyBindings.Add (Key.I | Key.CtrlMask, Command.NextView); // Unix
-			KeyBindings.Add (Key.B | Key.CtrlMask, Command.PreviousView);// Unix
-#endif
-		// This enables the default button to be activated by the Enter key.
-		KeyBindings.Add (KeyCode.Enter, Command.Accept);
-	}
+		KeyBindings.Add (Key.CursorRight, Command.NextView);
+		KeyBindings.Add (Key.CursorDown, Command.NextView);
+		KeyBindings.Add (Key.CursorLeft, Command.PreviousView);
+		KeyBindings.Add (Key.CursorUp, Command.PreviousView);
 
-	void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
-	{
-		if (Application.MouseGrabView == this && _dragPosition.HasValue) {
-			e.Cancel = true;
-		}
-	}
+		KeyBindings.Add (Key.Tab, Command.NextView);
+		KeyBindings.Add (Key.Tab.WithShift, Command.PreviousView);
+		KeyBindings.Add (Key.Tab.WithCtrl, Command.NextViewOrTop);
+		KeyBindings.Add (Key.Tab.WithShift.WithCtrl, Command.PreviousViewOrTop);
 
-	void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
-	{
-		if (Application.MouseGrabView == this && _dragPosition.HasValue) {
-			e.Cancel = true;
-		}
+		KeyBindings.Add (Key.F5, Command.Refresh);
+		KeyBindings.Add (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix
+		KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
+
+#if UNIX_KEY_BINDINGS
+			KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
+			KeyBindings.Add (Key.L.WithCtrl, Command.Refresh);// Unix
+			KeyBindings.Add (Key.F.WithCtrl, Command.NextView);// Unix
+			KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix
+			KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView);// Unix
+#endif
 	}
 
 	/// <summary>
@@ -363,7 +329,7 @@ public partial class Toplevel : View {
 	/// <param name="e"></param>
 	public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
 	{
-		KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
+		KeyBindings.Replace (e.OldKey, e.NewKey);
 		AlternateForwardKeyChanged?.Invoke (this, e);
 	}
 
@@ -378,7 +344,7 @@ public partial class Toplevel : View {
 	/// <param name="e"></param>
 	public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e)
 	{
-		KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
+		KeyBindings.Replace (e.OldKey, e.NewKey);
 		AlternateBackwardKeyChanged?.Invoke (this, e);
 	}
 
@@ -393,16 +359,10 @@ public partial class Toplevel : View {
 	/// <param name="e"></param>
 	public virtual void OnQuitKeyChanged (KeyChangedEventArgs e)
 	{
-		KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
+		KeyBindings.Replace (e.OldKey, e.NewKey);
 		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 (new Rect (0, 0, Driver.Cols, Driver.Rows));
-
 	void MovePreviousViewOrTop ()
 	{
 		if (Application.OverlappedTop == null) {
@@ -609,7 +569,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);
@@ -656,7 +616,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) {
@@ -694,7 +654,7 @@ public partial class Toplevel : View {
 		var layoutSubviews = false;
 		var 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)
 		    // BUGBUG: Prevously PositionToplevel required LayotuStyle.Computed
@@ -780,6 +740,23 @@ public partial class Toplevel : View {
 		return false;
 	}
 
+	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)
 	{

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

@@ -42,7 +42,7 @@ public static partial class Application {
 	/// </summary>
 	public static Toplevel OverlappedTop {
 		get {
-			if (Top.IsOverlappedContainer) {
+			if (Top is { IsOverlappedContainer: true }) {
 				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);
 }

+ 61 - 52
Terminal.Gui/Views/Window.cs

@@ -1,60 +1,69 @@
-using System;
-using System.Collections;
+using System.Linq;
 using System.Text.Json.Serialization;
-using System.Text;
-using Terminal.Gui;
-using static Terminal.Gui.ConfigurationManager;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
 
+/// <summary>
+/// A <see cref="Toplevel"/> <see cref="View"/> with <see cref="View.BorderStyle"/> set to
+/// <see cref="LineStyle.Single"/>. Provides a container for other views. 
+/// </summary>
+/// <remarks>
+/// <para>
+/// If any subview is a button and the <see cref="Button.IsDefault"/> property is set to true, the Enter key
+/// will invoke the <see cref="Command.Accept"/> command on that subview.
+/// </para>
+/// </remarks>
+public class Window : Toplevel {
 	/// <summary>
-	/// A <see cref="Toplevel"/> <see cref="View"/> with <see cref="View.BorderStyle"/> set to <see cref="LineStyle.Single"/>. 
+	/// Initializes a new instance of the <see cref="Window"/> class using
+	/// <see cref="LayoutStyle.Computed"/> positioning.
+	/// </summary>
+	public Window () => SetInitialProperties ();
+
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Window"/> class using
+	/// <see cref="LayoutStyle.Computed"/> positioning.
+	/// </summary>
+	public Window (Rect frame) : base (frame) => SetInitialProperties ();
+
+	// TODO: enable this
+	///// <summary>
+	///// The default <see cref="LineStyle"/> for <see cref="Window"/>'s border. The default is <see cref="LineStyle.Single"/>.
+	///// </summary>
+	///// <remarks>
+	///// 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.ColorSchemes ["Base"];
+
+	/// <summary>
+	/// The default <see cref="LineStyle"/> for <see cref="Window"/>'s border. The default is
+	/// <see cref="LineStyle.Single"/>.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	/// This is a helper class to simplify creating a <see cref="Toplevel"/> with a border.
-	/// </para>
+	/// This property can be set in a Theme to change the default <see cref="LineStyle"/> for all
+	/// <see cref="Window"/>s.
 	/// </remarks>
-	public class Window : Toplevel {
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Window"/> class using <see cref="LayoutStyle.Computed"/> positioning.
-		/// </summary>
-		public Window () : base () {
-			SetInitialProperties ();
-		}
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Window"/> class using <see cref="LayoutStyle.Computed"/> positioning.
-		/// </summary>
-		public Window (Rect frame) : base (frame)
-		{
-			SetInitialProperties ();
-		}
-
-		// TODO: enable this
-		///// <summary>
-		///// The default <see cref="LineStyle"/> for <see cref="Window"/>'s border. The default is <see cref="LineStyle.Single"/>.
-		///// </summary>
-		///// <remarks>
-		///// 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;
-
-		/// <summary>
-		/// The default <see cref="LineStyle"/> for <see cref="Window"/>'s border. The default is <see cref="LineStyle.Single"/>.
-		/// </summary>
-		/// <remarks>
-		/// 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 LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
-
-		void SetInitialProperties ()
-		{
-			CanFocus = true;
-			ColorScheme = Colors.Base; // TODO: make this a theme property
-			BorderStyle = DefaultBorderStyle;
-		}
+	[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+	[JsonConverter (typeof (JsonStringEnumConverter))]
+	public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
+
+	void SetInitialProperties ()
+	{
+		CanFocus = true;
+		ColorScheme = Colors.ColorSchemes ["Base"]; // TODO: make this a theme property
+		BorderStyle = DefaultBorderStyle;
+
+		// This enables the default button to be activated by the Enter key.
+		AddCommand (Command.Accept, () => {
+			// TODO: Perhaps all views should support the concept of being default?
+			if (Subviews.FirstOrDefault (v => v is Button { IsDefault: true, Enabled: true }) is Button defaultBtn) {
+				defaultBtn.InvokeCommand (Command.Accept);
+				return true;
+			}
+			return false;
+		});
+
+		KeyBindings.Add (Key.Enter, Command.Accept);
 	}
-}
+}

+ 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 ();
 	}
-}
+}

+ 441 - 0
UICatalog/Scenarios/Adornments.cs

@@ -0,0 +1,441 @@
+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 ("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.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;
+				}
+			}
+		}
+	}
+}

+ 9 - 9
UICatalog/Scenarios/AllViewsTester.cs

@@ -72,7 +72,7 @@ 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 ()) {
@@ -81,7 +81,7 @@ public class AllViewsTester : Scenario {
 			Width = Dim.Fill (0),
 			Height = Dim.Fill (0),
 			AllowsMarking = false,
-			ColorScheme = Colors.TopLevel,
+			ColorScheme = Colors.ColorSchemes ["TopLevel"],
 			SelectedItem = 0
 		};
 		_classListView.OpenSelectedItem += (s, a) => {
@@ -106,7 +106,7 @@ 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.Toggled += (s, e) => {
@@ -228,7 +228,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);
@@ -285,8 +285,8 @@ public class AllViewsTester : Scenario {
 	}
 
 	// TODO: This is missing some
-	List<string> _posNames = new() { "Factor", "AnchorEnd", "Center", "Absolute" };
-	List<string> _dimNames = new() { "Factor", "Fill", "Absolute" };
+	List<string> _posNames = new () { "Factor", "AnchorEnd", "Center", "Absolute" };
+	List<string> _dimNames = new () { "Factor", "Fill", "Absolute" };
 
 	void UpdateSettings (View view)
 	{
@@ -340,7 +340,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
@@ -387,10 +387,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) };
 				}
 			};
 		}

+ 7 - 7
UICatalog/Scenarios/Buttons.cs

@@ -99,7 +99,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!
@@ -123,7 +123,7 @@ public class Buttons : Scenario {
 			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 +137,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 +155,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);
@@ -164,7 +164,7 @@ public class Buttons : Scenario {
 
 		// 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,
+			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 +215,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 +227,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);
+	}
+}
+

+ 1 - 1
UICatalog/Scenarios/Dialogs.cs

@@ -144,7 +144,7 @@ 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" };

+ 2 - 2
UICatalog/Scenarios/DimAutoDemo.cs

@@ -21,7 +21,7 @@ public class DimAutoDemo : Scenario {
 			X = Pos.Left (textField),
 			Y = Pos.Bottom (textField),
 			AutoSize = true,
-			ColorScheme = Colors.Error
+			ColorScheme = Colors.ColorSchemes["Error"]
 		};
 
 		textField.TextChanged += (s, e) => {
@@ -89,7 +89,7 @@ public class DimAutoDemo : Scenario {
 			Y = 0,
 			Height = Dim.Auto (min: 3),
 			Width = Dim.Auto (min: 20),
-			ColorScheme = Colors.Menu
+			ColorScheme = Colors.ColorSchemes ["Menu"]
 		};
 
 		var text = new TextField () {

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

+ 1 - 1
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 () {

+ 0 - 401
UICatalog/Scenarios/Frames.cs

@@ -1,401 +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 ()
-			{
-				Height = Dim.Auto ();
-				Width = Dim.Auto ();
-				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");

+ 7 - 7
UICatalog/Scenarios/LabelsAsButtons.cs

@@ -114,7 +114,7 @@ namespace UICatalog.Scenarios {
 			var removeLabel = new Label ("Remove this Label") {
 				X = 2,
 				Y = Pos.Bottom (Label) + 1,
-				ColorScheme = Colors.Error,
+				ColorScheme = Colors.ColorSchemes ["Error"],
 				HotKeySpecifier = (Rune)'_',
 				CanFocus = true,
 			};
@@ -141,7 +141,7 @@ namespace UICatalog.Scenarios {
 				X = 0,
 				Y = Pos.Center () - 1,
 				Width = 30,
-				ColorScheme = Colors.Error,
+				ColorScheme = Colors.ColorSchemes ["Error"],
 				HotKeySpecifier = (Rune)'_',
 				CanFocus = true,
 			};
@@ -158,7 +158,7 @@ namespace UICatalog.Scenarios {
 				X = 0,
 				Y = Pos.Center () + 1,
 				Width = 30,
-				ColorScheme = Colors.Error,
+				ColorScheme = Colors.ColorSchemes ["Error"],
 				HotKeySpecifier = (Rune)'_',
 				CanFocus = true,
 				AutoSize = false
@@ -179,7 +179,7 @@ namespace UICatalog.Scenarios {
 
 			// Demonstrates how changing the View.Frame property can move Views
 			var moveBtnA = new Label (0, 0, "Move This Label via Frame") {
-				ColorScheme = Colors.Error,
+				ColorScheme = Colors.ColorSchemes ["Error"],
 				HotKeySpecifier = (Rune)'_',
 				CanFocus = true,
 			};
@@ -190,7 +190,7 @@ namespace UICatalog.Scenarios {
 
 			// Demonstrates how changing the View.Frame property can SIZE Views (#583)
 			var sizeBtnA = new Label (0, 2, " ~  s  gui.cs   master ↑10 = Со_хранить") {
-				ColorScheme = Colors.Error,
+				ColorScheme = Colors.ColorSchemes ["Error"],
 				HotKeySpecifier = (Rune)'_',
 				CanFocus = true,
 				AutoSize = false
@@ -246,7 +246,7 @@ namespace UICatalog.Scenarios {
 				X = 2,
 				Y = Pos.Bottom (radioGroup) + 1,
 				Width = Dim.Width (computedFrame) - 2,
-				ColorScheme = Colors.TopLevel,
+				ColorScheme = Colors.ColorSchemes ["TopLevel"],
 				HotKeySpecifier = (Rune)'_',
 				CanFocus = true,
 			};
@@ -260,7 +260,7 @@ namespace UICatalog.Scenarios {
 				X = Pos.Left (absoluteFrame) + 1,
 				Y = Pos.Bottom (radioGroup) + 1,
 				Width = Dim.Width (absoluteFrame) - 2,
-				ColorScheme = Colors.TopLevel,
+				ColorScheme = Colors.ColorSchemes ["TopLevel"],
 				HotKeySpecifier = (Rune)'_',
 				CanFocus = true,
 			};

+ 2 - 2
UICatalog/Scenarios/LineCanvasExperiment.cs

@@ -123,10 +123,10 @@ namespace UICatalog.Scenarios {
 				Y = 8,
 				Width = 25,
 				Height = 10,
-				//ColorScheme = Colors.Error,
+				//ColorScheme = Colors.ColorSchemes ["Error"],
 				SuperViewRendersLineCanvas = true
 			};
-			marginWindow.Margin.ColorScheme = Colors.Dialog;
+			marginWindow.Margin.ColorScheme = Colors.ColorSchemes ["Dialog"];
 			marginWindow.Margin.Thickness = new Thickness (1);
 			marginWindow.Border.Thickness = new Thickness (1,2,1,1);
 

+ 2 - 2
UICatalog/Scenarios/LineDrawing.cs

@@ -55,8 +55,8 @@ namespace UICatalog.Scenarios {
 			private void ToolsView_Initialized (object sender, EventArgs e)
 			{
 				LayoutSubviews ();
-				Width = Math.Max (_colorPicker.Frame.Width, _stylePicker.Frame.Width) + GetFramesThickness ().Horizontal;
-				Height = _colorPicker.Frame.Height + _stylePicker.Frame.Height + _addLayerBtn.Frame.Height + GetFramesThickness ().Vertical;
+				Width = Math.Max (_colorPicker.Frame.Width, _stylePicker.Frame.Width) + GetAdornmentsThickness ().Horizontal;
+				Height = _colorPicker.Frame.Height + _stylePicker.Frame.Height + _addLayerBtn.Frame.Height + GetAdornmentsThickness ().Vertical;
 				SuperView.LayoutSubviews ();
 			}
 

+ 61 - 61
UICatalog/Scenarios/ListColumns.cs

@@ -24,9 +24,9 @@ public class ListColumns : Scenario {
 	MenuItem _miSmoothScrolling;
 	MenuItem _miTopline;
 
-	ColorScheme alternatingColorScheme;
-	DataTable currentTable;
-	TableView listColView;
+	ColorScheme _alternatingColorScheme;
+	DataTable _currentTable;
+	TableView _listColView;
 
 	public override void Setup ()
 	{
@@ -34,7 +34,7 @@ public class ListColumns : Scenario {
 		Win.Y = 1;                 // menu
 		Win.Height = Dim.Fill (1); // status bar
 
-		listColView = new TableView {
+		_listColView = new TableView {
 			X = 0,
 			Y = 0,
 			Width = Dim.Fill (),
@@ -58,36 +58,36 @@ public class ListColumns : Scenario {
 			}),
 			new ("_View", new [] {
 				_miTopline = new MenuItem ("_TopLine", "", () => ToggleTopline ()) {
-					Checked = listColView.Style.ShowHorizontalHeaderOverline,
+					Checked = _listColView.Style.ShowHorizontalHeaderOverline,
 					CheckType = MenuItemCheckStyle.Checked
 				},
 				_miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline ()) {
-					Checked = listColView.Style.ShowHorizontalBottomline,
+					Checked = _listColView.Style.ShowHorizontalBottomline,
 					CheckType = MenuItemCheckStyle.Checked
 				},
 				_miCellLines = new MenuItem ("_CellLines", "", () => ToggleCellLines ()) {
-					Checked = listColView.Style.ShowVerticalCellLines,
+					Checked = _listColView.Style.ShowVerticalCellLines,
 					CheckType = MenuItemCheckStyle.Checked
 				},
 				_miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn ()) {
-					Checked = listColView.Style.ExpandLastColumn,
+					Checked = _listColView.Style.ExpandLastColumn,
 					CheckType = MenuItemCheckStyle.Checked
 				},
 				_miAlwaysUseNormalColorForVerticalCellLines =
 					new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "",
 						() => ToggleAlwaysUseNormalColorForVerticalCellLines ()) {
-						Checked = listColView.Style.AlwaysUseNormalColorForVerticalCellLines,
+						Checked = _listColView.Style.AlwaysUseNormalColorForVerticalCellLines,
 						CheckType = MenuItemCheckStyle.Checked
 					},
 				_miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) {
-					Checked = listColView.Style.SmoothHorizontalScrolling,
+					Checked = _listColView.Style.SmoothHorizontalScrolling,
 					CheckType = MenuItemCheckStyle.Checked
 				},
 				_miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors ())
 					{ CheckType = MenuItemCheckStyle.Checked },
 				_miCursor = new MenuItem ("Invert Selected Cell First Character", "",
 					() => ToggleInvertSelectedCellFirstCharacter ()) {
-					Checked = listColView.Style.InvertSelectedCellFirstCharacter,
+					Checked = _listColView.Style.InvertSelectedCellFirstCharacter,
 					CheckType = MenuItemCheckStyle.Checked
 				}
 			}),
@@ -114,11 +114,11 @@ public class ListColumns : Scenario {
 		});
 		Application.Top.Add (statusBar);
 
-		Win.Add (listColView);
+		Win.Add (_listColView);
 
 		var selectedCellLabel = new Label {
 			X = 0,
-			Y = Pos.Bottom (listColView),
+			Y = Pos.Bottom (_listColView),
 			Text = "0,0",
 			Width = Dim.Fill (),
 			TextAlignment = TextAlignment.Right
@@ -127,12 +127,12 @@ public class ListColumns : Scenario {
 
 		Win.Add (selectedCellLabel);
 
-		listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{listColView.SelectedRow},{listColView.SelectedColumn}"; };
-		listColView.KeyDown += TableViewKeyPress;
+		_listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{_listColView.SelectedRow},{_listColView.SelectedColumn}"; };
+		_listColView.KeyDown += TableViewKeyPress;
 
 		SetupScrollBar ();
 
-		alternatingColorScheme = new ColorScheme {
+		_alternatingColorScheme = new ColorScheme {
 
 			Disabled = Win.ColorScheme.Disabled,
 			HotFocus = Win.ColorScheme.HotFocus,
@@ -141,24 +141,24 @@ public class ListColumns : Scenario {
 		};
 
 		// if user clicks the mouse in TableView
-		listColView.MouseClick += (s, e) => {
+		_listColView.MouseClick += (s, e) => {
 
-			listColView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var clickedCol);
+			_listColView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var clickedCol);
 		};
 
-		listColView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
+		_listColView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
 	}
 
 	void SetupScrollBar ()
 	{
-		var scrollBar = new ScrollBarView (listColView, true); // (listColView, true, true);
+		var scrollBar = new ScrollBarView (_listColView, true); // (listColView, true, true);
 
 		scrollBar.ChangedPosition += (s, e) => {
-			listColView.RowOffset = scrollBar.Position;
-			if (listColView.RowOffset != scrollBar.Position) {
-				scrollBar.Position = listColView.RowOffset;
+			_listColView.RowOffset = scrollBar.Position;
+			if (_listColView.RowOffset != scrollBar.Position) {
+				scrollBar.Position = _listColView.RowOffset;
 			}
-			listColView.SetNeedsDisplay ();
+			_listColView.SetNeedsDisplay ();
 		};
 		/*
 		scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => {
@@ -170,9 +170,9 @@ public class ListColumns : Scenario {
 		};
 		*/
 
-		listColView.DrawContent += (s, e) => {
-			scrollBar.Size = listColView.Table?.Rows ?? 0;
-			scrollBar.Position = listColView.RowOffset;
+		_listColView.DrawContent += (s, e) => {
+			scrollBar.Size = _listColView.Table?.Rows ?? 0;
+			scrollBar.Position = _listColView.RowOffset;
 			//scrollBar.OtherScrollBarView.Size = listColView.Table?.Columns - 1 ?? 0;
 			//scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset;
 			scrollBar.Refresh ();
@@ -185,11 +185,11 @@ public class ListColumns : Scenario {
 		if (e.KeyCode == KeyCode.Delete) {
 
 			// set all selected cells to null
-			foreach (var pt in listColView.GetAllSelectedCells ()) {
-				currentTable.Rows [pt.Y] [pt.X] = DBNull.Value;
+			foreach (var pt in _listColView.GetAllSelectedCells ()) {
+				_currentTable.Rows [pt.Y] [pt.X] = DBNull.Value;
 			}
 
-			listColView.Update ();
+			_listColView.Update ();
 			e.Handled = true;
 		}
 
@@ -198,48 +198,48 @@ public class ListColumns : Scenario {
 	void ToggleTopline ()
 	{
 		_miTopline.Checked = !_miTopline.Checked;
-		listColView.Style.ShowHorizontalHeaderOverline = (bool)_miTopline.Checked;
-		listColView.Update ();
+		_listColView.Style.ShowHorizontalHeaderOverline = (bool)_miTopline.Checked;
+		_listColView.Update ();
 	}
 
 	void ToggleBottomline ()
 	{
 		_miBottomline.Checked = !_miBottomline.Checked;
-		listColView.Style.ShowHorizontalBottomline = (bool)_miBottomline.Checked;
-		listColView.Update ();
+		_listColView.Style.ShowHorizontalBottomline = (bool)_miBottomline.Checked;
+		_listColView.Update ();
 	}
 
 	void ToggleExpandLastColumn ()
 	{
 		_miExpandLastColumn.Checked = !_miExpandLastColumn.Checked;
-		listColView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked;
+		_listColView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked;
 
-		listColView.Update ();
+		_listColView.Update ();
 
 	}
 
 	void ToggleAlwaysUseNormalColorForVerticalCellLines ()
 	{
 		_miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked;
-		listColView.Style.AlwaysUseNormalColorForVerticalCellLines = (bool)_miAlwaysUseNormalColorForVerticalCellLines.Checked;
+		_listColView.Style.AlwaysUseNormalColorForVerticalCellLines = (bool)_miAlwaysUseNormalColorForVerticalCellLines.Checked;
 
-		listColView.Update ();
+		_listColView.Update ();
 	}
 
 	void ToggleSmoothScrolling ()
 	{
 		_miSmoothScrolling.Checked = !_miSmoothScrolling.Checked;
-		listColView.Style.SmoothHorizontalScrolling = (bool)_miSmoothScrolling.Checked;
+		_listColView.Style.SmoothHorizontalScrolling = (bool)_miSmoothScrolling.Checked;
 
-		listColView.Update ();
+		_listColView.Update ();
 
 	}
 
 	void ToggleCellLines ()
 	{
 		_miCellLines.Checked = !_miCellLines.Checked;
-		listColView.Style.ShowVerticalCellLines = (bool)_miCellLines.Checked;
-		listColView.Update ();
+		_listColView.Style.ShowVerticalCellLines = (bool)_miCellLines.Checked;
+		_listColView.Update ();
 	}
 
 	void ToggleAlternatingColors ()
@@ -248,49 +248,49 @@ public class ListColumns : Scenario {
 		_miAlternatingColors.Checked = !_miAlternatingColors.Checked;
 
 		if (_miAlternatingColors.Checked == true) {
-			listColView.Style.RowColorGetter = a => { return a.RowIndex % 2 == 0 ? alternatingColorScheme : null; };
+			_listColView.Style.RowColorGetter = a => { return a.RowIndex % 2 == 0 ? _alternatingColorScheme : null; };
 		} else {
-			listColView.Style.RowColorGetter = null;
+			_listColView.Style.RowColorGetter = null;
 		}
-		listColView.SetNeedsDisplay ();
+		_listColView.SetNeedsDisplay ();
 	}
 
 	void ToggleInvertSelectedCellFirstCharacter ()
 	{
 		//toggle menu item
 		_miCursor.Checked = !_miCursor.Checked;
-		listColView.Style.InvertSelectedCellFirstCharacter = (bool)_miCursor.Checked;
-		listColView.SetNeedsDisplay ();
+		_listColView.Style.InvertSelectedCellFirstCharacter = (bool)_miCursor.Checked;
+		_listColView.SetNeedsDisplay ();
 	}
 
 	void ToggleVerticalOrientation ()
 	{
 		_miOrientVertical.Checked = !_miOrientVertical.Checked;
-		if ((ListTableSource)listColView.Table != null) {
-			((ListTableSource)listColView.Table).Style.Orientation = (bool)_miOrientVertical.Checked ? Orientation.Vertical : Orientation.Horizontal;
-			listColView.SetNeedsDisplay ();
+		if ((ListTableSource)_listColView.Table != null) {
+			((ListTableSource)_listColView.Table).Style.Orientation = (bool)_miOrientVertical.Checked ? Orientation.Vertical : Orientation.Horizontal;
+			_listColView.SetNeedsDisplay ();
 		}
 	}
 
 	void ToggleScrollParallel ()
 	{
 		_miScrollParallel.Checked = !_miScrollParallel.Checked;
-		if ((ListTableSource)listColView.Table != null) {
-			((ListTableSource)listColView.Table).Style.ScrollParallel = (bool)_miScrollParallel.Checked;
-			listColView.SetNeedsDisplay ();
+		if ((ListTableSource)_listColView.Table != null) {
+			((ListTableSource)_listColView.Table).Style.ScrollParallel = (bool)_miScrollParallel.Checked;
+			_listColView.SetNeedsDisplay ();
 		}
 	}
 
 	void SetListMinWidth ()
 	{
 		RunListWidthDialog ("MinCellWidth", (s, v) => s.MinCellWidth = v, s => s.MinCellWidth);
-		listColView.SetNeedsDisplay ();
+		_listColView.SetNeedsDisplay ();
 	}
 
 	void SetListMaxWidth ()
 	{
 		RunListWidthDialog ("MaxCellWidth", (s, v) => s.MaxCellWidth = v, s => s.MaxCellWidth);
-		listColView.SetNeedsDisplay ();
+		_listColView.SetNeedsDisplay ();
 	}
 
 	void RunListWidthDialog (string prompt, Action<TableView, int> setter, Func<TableView, int> getter)
@@ -306,7 +306,7 @@ public class ListColumns : Scenario {
 		var d = new Dialog (ok, cancel) { Title = prompt };
 
 		var tf = new TextField {
-			Text = getter (listColView).ToString (),
+			Text = getter (_listColView).ToString (),
 			X = 0,
 			Y = 1,
 			Width = Dim.Fill ()
@@ -320,14 +320,14 @@ public class ListColumns : Scenario {
 		if (accepted) {
 
 			try {
-				setter (listColView, int.Parse (tf.Text));
+				setter (_listColView, int.Parse (tf.Text));
 			} catch (Exception ex) {
 				MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok");
 			}
 		}
 	}
 
-	void CloseExample () => listColView.Table = null;
+	void CloseExample () => _listColView.Table = null;
 
 	void Quit () => Application.RequestStop ();
 
@@ -335,9 +335,9 @@ public class ListColumns : Scenario {
 
 	void SetTable (IList list)
 	{
-		listColView.Table = new ListTableSource (list, listColView);
-		if ((ListTableSource)listColView.Table != null) {
-			currentTable = ((ListTableSource)listColView.Table).DataTable;
+		_listColView.Table = new ListTableSource (list, _listColView);
+		if ((ListTableSource)_listColView.Table != null) {
+			_currentTable = ((ListTableSource)_listColView.Table).DataTable;
 		}
 	}
 

+ 1 - 1
UICatalog/Scenarios/ListViewWithSelection.cs

@@ -50,7 +50,7 @@ namespace UICatalog.Scenarios {
 				Y = 2,
 				Height = Dim.Fill (),
 				Width = Dim.Fill (1),
-				//ColorScheme = Colors.TopLevel,
+				//ColorScheme = Colors.ColorSchemes ["TopLevel"],
 				AllowsMarking = false,
 				AllowsMultipleSelection = false
 			};

+ 2 - 2
UICatalog/Scenarios/ListsAndCombos.cs

@@ -25,7 +25,7 @@ namespace UICatalog.Scenarios {
 
 			// ListView
 			var lbListView = new Label ("Listview") {
-				ColorScheme = Colors.TopLevel,
+				ColorScheme = Colors.ColorSchemes ["TopLevel"],
 				X = 0,
 				Width = Dim.Percent (40)
 			};
@@ -67,7 +67,7 @@ namespace UICatalog.Scenarios {
 
 			// ComboBox
 			var lbComboBox = new Label ("ComboBox") {
-				ColorScheme = Colors.TopLevel,
+				ColorScheme = Colors.ColorSchemes ["TopLevel"],
 				X = Pos.Right (lbListView) + 1,
 				Width = Dim.Percent(40)
 			};

+ 1 - 1
UICatalog/Scenarios/MenuBarScenario.cs

@@ -53,7 +53,7 @@ public class MenuBarScenario : Scenario {
 	public override void Init ()
 	{
 		Application.Init ();
-		Application.Top.ColorScheme = Colors.Base;
+		Application.Top.ColorScheme = Colors.ColorSchemes ["Base"];
 	}
 
 	Label _currentMenuBarItem;

+ 1 - 1
UICatalog/Scenarios/MessageBoxes.cs

@@ -175,7 +175,7 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Bottom (frame) + 5,
 				Width = 25,
 				Height = 1,
-				ColorScheme = Colors.Error,
+				ColorScheme = Colors.ColorSchemes ["Error"],
 				TextAlignment = Terminal.Gui.TextAlignment.Centered
 			};
 

+ 1 - 1
UICatalog/Scenarios/Mouse.cs

@@ -23,7 +23,7 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Top (test) + 1,
 				Width = Dim.Fill () - 1,
 				Height = Dim.Fill (),
-				ColorScheme = Colors.TopLevel
+				ColorScheme = Colors.ColorSchemes ["TopLevel"]
 			};
 			Win.Add (rmeList);
 

+ 317 - 313
UICatalog/Scenarios/Notepad.cs

@@ -1,413 +1,417 @@
-using System;
-using System.IO;
+using System.IO;
 using System.Linq;
 using Terminal.Gui;
 
-namespace UICatalog.Scenarios {
+namespace UICatalog.Scenarios;
 
-	[ScenarioMetadata (Name: "Notepad", Description: "Multi-tab text editor using the TabView control.")]
-	[ScenarioCategory ("Controls"), ScenarioCategory ("TabView"), ScenarioCategory ("TextView")]
-	public class Notepad : Scenario {
-		TabView tabView;
+[ScenarioMetadata (Name: "Notepad", Description: "Multi-tab text editor using the TabView control.")]
+[ScenarioCategory ("Controls"), ScenarioCategory ("TabView"), ScenarioCategory ("TextView")]
+public class Notepad : Scenario {
+	TabView tabView;
 
-		private int numbeOfNewTabs = 1;
-		private TabView focusedTabView;
-		private StatusItem lenStatusItem;
+	private int _numbeOfNewTabs = 1;
+	private TabView _focusedTabView;
+	private StatusItem _lenStatusItem;
 
-		// Don't create a Window, just return the top-level view
-		public override void Init ()
-		{
-			Application.Init ();
-			Application.Top.ColorScheme = Colors.Base;
-		}
+	// Don't create a Window, just return the top-level view
+	public override void Init ()
+	{
+		Application.Init ();
+		Application.Top.ColorScheme = Colors.ColorSchemes ["Base"];
+	}
 
-		public override void Setup ()
-		{
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_New", "", () => New(), null, null, KeyCode.N | KeyCode.CtrlMask | KeyCode.AltMask),
-					new MenuItem ("_Open", "", () => Open()),
-					new MenuItem ("_Save", "", () => Save()),
-					new MenuItem ("Save _As", "", () => SaveAs()),
-					new MenuItem ("_Close", "", () => Close()),
-					new MenuItem ("_Quit", "", () => Quit()),
-				}),
-				new MenuBarItem ("_About", "", () => MessageBox.Query("Notepad", "About Notepad...", "Ok"))
-				});
-			Application.Top.Add (menu);
-
-			tabView = CreateNewTabView ();
-
-			tabView.Style.ShowBorder = true;
-			tabView.ApplyStyleChanges ();
-
-			// Start with only a single view but support splitting to show side by side
-			var split = new TileView (1) {
-				X = 0,
-				Y = 1,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (1),
-			};
-			split.Tiles.ElementAt (0).ContentView.Add (tabView);
-			split.LineStyle = LineStyle.None;
+	public override void Setup ()
+	{
+		var menu = new MenuBar (new MenuBarItem [] {
+			new MenuBarItem ("_File", new MenuItem [] {
+				new MenuItem ("_New", "", () => New(), null, null, KeyCode.N | KeyCode.CtrlMask | KeyCode.AltMask),
+				new MenuItem ("_Open", "", () => Open()),
+				new MenuItem ("_Save", "", () => Save()),
+				new MenuItem ("Save _As", "", () => SaveAs()),
+				new MenuItem ("_Close", "", () => Close()),
+				new MenuItem ("_Quit", "", () => Quit()),
+			}),
+			new MenuBarItem ("_About", "", () => MessageBox.Query("Notepad", "About Notepad...", "Ok"))
+			});
+		Application.Top.Add (menu);
+
+		tabView = CreateNewTabView ();
+
+		tabView.Style.ShowBorder = true;
+		tabView.ApplyStyleChanges ();
+
+		// Start with only a single view but support splitting to show side by side
+		var split = new TileView (1) {
+			X = 0,
+			Y = 1,
+			Width = Dim.Fill (),
+			Height = Dim.Fill (1),
+		};
+		split.Tiles.ElementAt (0).ContentView.Add (tabView);
+		split.LineStyle = LineStyle.None;
+
+		Application.Top.Add (split);
+
+		_lenStatusItem = new StatusItem (KeyCode.CharMask, "Len: ", null);
+		var statusBar = new StatusBar (new StatusItem [] {
+			new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
+
+			// These shortcut keys don't seem to work correctly in linux 
+			//new StatusItem(Key.CtrlMask | Key.N, "~^O~ Open", () => Open()),
+			//new StatusItem(Key.CtrlMask | Key.N, "~^N~ New", () => New()),
+
+			new StatusItem(KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save()),
+			new StatusItem(KeyCode.CtrlMask | KeyCode.W, "~^W~ Close", () => Close()),
+			_lenStatusItem,
+		});
+		_focusedTabView = tabView;
+		tabView.SelectedTabChanged += TabView_SelectedTabChanged;
+		tabView.Enter += (s, e) => _focusedTabView = tabView;
+
+		Application.Top.Add (statusBar);
+		Application.Top.Ready += (s, e) => New ();
+	}
 
-			Application.Top.Add (split);
+	private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)
+	{
+		_lenStatusItem.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
+		e.NewTab?.View?.SetFocus ();
+	}
 
-			lenStatusItem = new StatusItem (KeyCode.CharMask, "Len: ", null);
-			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
+	private void TabView_TabClicked (object sender, TabMouseEventArgs e)
+	{
+		// we are only interested in right clicks
+		if (!e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+			return;
+		}
 
-				// These shortcut keys don't seem to work correctly in linux 
-				//new StatusItem(Key.CtrlMask | Key.N, "~^O~ Open", () => Open()),
-				//new StatusItem(Key.CtrlMask | Key.N, "~^N~ New", () => New()),
+		MenuBarItem items;
 
-				new StatusItem(KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save()),
-				new StatusItem(KeyCode.CtrlMask | KeyCode.W, "~^W~ Close", () => Close()),
-				lenStatusItem,
+		if (e.Tab == null) {
+			items = new MenuBarItem (new MenuItem [] {
+				new MenuItem ($"Open", "", () => Open()),
 			});
-			focusedTabView = tabView;
-			tabView.SelectedTabChanged += TabView_SelectedTabChanged;
-			tabView.Enter += (s, e) => focusedTabView = tabView;
 
-			Application.Top.Add (statusBar);
-			Application.Top.Ready += (s, e) => New ();
-		}
+		} else {
 
-		private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)
-		{
-			lenStatusItem.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
-		}
+			var tv = (TabView)sender;
+			var t = (OpenedFile)e.Tab;
 
-		private void TabView_TabClicked (object sender, TabMouseEventArgs e)
-		{
-			// we are only interested in right clicks
-			if (!e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
-				return;
-			}
+			items = new MenuBarItem (new MenuItem [] {
+				new MenuItem ($"Save", "", () => Save(_focusedTabView, e.Tab)),
+				new MenuItem ($"Close", "", () => Close(tv, e.Tab)),
+				null,
+				new MenuItem ($"Split Up", "", () => SplitUp(tv,t)),
+				new MenuItem ($"Split Down", "", () => SplitDown(tv,t)),
+				new MenuItem ($"Split Right", "", () => SplitRight(tv,t)),
+				new MenuItem ($"Split Left", "", () => SplitLeft(tv,t)),
+			});
+		}
 
-			MenuBarItem items;
+	((View)sender).BoundsToScreen (e.MouseEvent.X, e.MouseEvent.Y, out int screenX, out int screenY, true);
 
-			if (e.Tab == null) {
-				items = new MenuBarItem (new MenuItem [] {
-					new MenuItem ($"Open", "", () => Open()),
-				});
+		var contextMenu = new ContextMenu (screenX, screenY, items);
 
-			} else {
+		contextMenu.Show ();
+		e.MouseEvent.Handled = true;
+	}
 
-				var tv = (TabView)sender;
-				var t = (OpenedFile)e.Tab;
+	private void SplitUp (TabView sender, OpenedFile tab)
+	{
+		Split (0, Orientation.Horizontal, sender, tab);
+	}
+	private void SplitDown (TabView sender, OpenedFile tab)
+	{
+		Split (1, Orientation.Horizontal, sender, tab);
 
-				items = new MenuBarItem (new MenuItem [] {
-					new MenuItem ($"Save", "", () => Save(focusedTabView, e.Tab)),
-					new MenuItem ($"Close", "", () => Close(tv, e.Tab)),
-					null,
-					new MenuItem ($"Split Up", "", () => SplitUp(tv,t)),
-					new MenuItem ($"Split Down", "", () => SplitDown(tv,t)),
-					new MenuItem ($"Split Right", "", () => SplitRight(tv,t)),
-					new MenuItem ($"Split Left", "", () => SplitLeft(tv,t)),
-				});
-			}
+	}
+	private void SplitLeft (TabView sender, OpenedFile tab)
+	{
+		Split (0, Orientation.Vertical, sender, tab);
+	}
+	private void SplitRight (TabView sender, OpenedFile tab)
+	{
+		Split (1, Orientation.Vertical, sender, tab);
+	}
 
-		((View)sender).BoundsToScreen (e.MouseEvent.X, e.MouseEvent.Y, out int screenX, out int screenY, true);
+	private void Split (int offset, Orientation orientation, TabView sender, OpenedFile tab)
+	{
 
-			var contextMenu = new ContextMenu (screenX, screenY, items);
+		var split = (TileView)sender.SuperView.SuperView;
+		var tileIndex = split.IndexOf (sender);
 
-			contextMenu.Show ();
-			e.MouseEvent.Handled = true;
+		if (tileIndex == -1) {
+			return;
 		}
 
-		private void SplitUp (TabView sender, OpenedFile tab)
-		{
-			Split (0, Orientation.Horizontal, sender, tab);
+		if (orientation != split.Orientation) {
+			split.TrySplitTile (tileIndex, 1, out split);
+			split.Orientation = orientation;
+			tileIndex = 0;
 		}
-		private void SplitDown (TabView sender, OpenedFile tab)
-		{
-			Split (1, Orientation.Horizontal, sender, tab);
 
-		}
-		private void SplitLeft (TabView sender, OpenedFile tab)
-		{
-			Split (0, Orientation.Vertical, sender, tab);
-		}
-		private void SplitRight (TabView sender, OpenedFile tab)
-		{
-			Split (1, Orientation.Vertical, sender, tab);
-		}
+		var newTile = split.InsertTile (tileIndex + offset);
+		var newTabView = CreateNewTabView ();
+		tab.CloneTo (newTabView);
+		newTile.ContentView.Add (newTabView);
 
-		private void Split (int offset, Orientation orientation, TabView sender, OpenedFile tab)
-		{
-
-			var split = (TileView)sender.SuperView.SuperView;
-			var tileIndex = split.IndexOf (sender);
+		newTabView.EnsureFocus ();
+		newTabView.FocusFirst ();
+		newTabView.FocusNext ();
+	}
 
-			if (tileIndex == -1) {
-				return;
-			}
+	private TabView CreateNewTabView ()
+	{
+		var tv = new TabView () {
+			X = 0,
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill (),
+		};
+
+		tv.TabClicked += TabView_TabClicked;
+		tv.SelectedTabChanged += TabView_SelectedTabChanged;
+		tv.Enter += (s, e) => _focusedTabView = tv;
+		return tv;
+	}
 
-			if (orientation != split.Orientation) {
-				split.TrySplitTile (tileIndex, 1, out split);
-				split.Orientation = orientation;
-				tileIndex = 0;
-			}
+	private void New ()
+	{
+		Open (null, $"new {_numbeOfNewTabs++}");
+	}
 
-			var newTile = split.InsertTile (tileIndex + offset);
-			var newTabView = CreateNewTabView ();
-			tab.CloneTo (newTabView);
-			newTile.ContentView.Add (newTabView);
+	private void Close ()
+	{
+		Close (_focusedTabView, _focusedTabView.SelectedTab);
+	}
+	private void Close (TabView tv, Tab tabToClose)
+	{
+		var tab = tabToClose as OpenedFile;
 
-			newTabView.EnsureFocus ();
-			newTabView.FocusFirst ();
-			newTabView.FocusNext ();
+		if (tab == null) {
+			return;
 		}
 
-		private TabView CreateNewTabView ()
-		{
-			var tv = new TabView () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-			};
+		_focusedTabView = tv;
 
-			tv.TabClicked += TabView_TabClicked;
-			tv.SelectedTabChanged += TabView_SelectedTabChanged;
-			tv.Enter += (s, e) => focusedTabView = tv;
-			return tv;
-		}
+		if (tab.UnsavedChanges) {
 
-		private void New ()
-		{
-			Open (null, $"new {numbeOfNewTabs++}");
-		}
+			int result = MessageBox.Query ("Save Changes", $"Save changes to {tab.Text.TrimEnd ('*')}", "Yes", "No", "Cancel");
 
-		private void Close ()
-		{
-			Close (focusedTabView, focusedTabView.SelectedTab);
-		}
-		private void Close (TabView tv, Tab tabToClose)
-		{
-			var tab = tabToClose as OpenedFile;
+			if (result == -1 || result == 2) {
 
-			if (tab == null) {
+				// user cancelled
 				return;
 			}
 
-			focusedTabView = tv;
-
-			if (tab.UnsavedChanges) {
+			if (result == 0) {
+				if (tab.File == null) {
+					SaveAs ();
+				} else {
+					tab.Save ();
+				}
+			}
+		}
 
-				int result = MessageBox.Query ("Save Changes", $"Save changes to {tab.Text.TrimEnd ('*')}", "Yes", "No", "Cancel");
+		// close and dispose the tab
+		tv.RemoveTab (tab);
+		tab.View.Dispose ();
+		_focusedTabView = tv;
 
-				if (result == -1 || result == 2) {
+		if (tv.Tabs.Count == 0) {
 
-					// user cancelled
-					return;
-				}
+			var split = (TileView)tv.SuperView.SuperView;
 
-				if (result == 0) {
-					if (tab.File == null) {
-						SaveAs ();
-					} else {
-						tab.Save ();
-					}
-				}
+			// if it is the last TabView on screen don't drop it or we will
+			// be unable to open new docs!
+			if (split.IsRootTileView () && split.Tiles.Count == 1) {
+				return;
 			}
 
-			// close and dispose the tab
-			tv.RemoveTab (tab);
-			tab.View.Dispose ();
-			focusedTabView = tv;
-
-			if (tv.Tabs.Count == 0) {
+			var tileIndex = split.IndexOf (tv);
+			split.RemoveTile (tileIndex);
 
-				var split = (TileView)tv.SuperView.SuperView;
+			if (split.Tiles.Count == 0) {
+				var parent = split.GetParentTileView ();
 
-				// if it is the last TabView on screen don't drop it or we will
-				// be unable to open new docs!
-				if (split.IsRootTileView () && split.Tiles.Count == 1) {
+				if (parent == null) {
 					return;
 				}
 
-				var tileIndex = split.IndexOf (tv);
-				split.RemoveTile (tileIndex);
-
-				if (split.Tiles.Count == 0) {
-					var parent = split.GetParentTileView ();
-
-					if (parent == null) {
-						return;
-					}
-
-					var idx = parent.IndexOf (split);
+				var idx = parent.IndexOf (split);
 
-					if (idx == -1) {
-						return;
-					}
-
-					parent.RemoveTile (idx);
+				if (idx == -1) {
+					return;
 				}
+
+				parent.RemoveTile (idx);
 			}
 		}
+	}
 
-		private void Open ()
-		{
-			var open = new OpenDialog ("Open") { AllowsMultipleSelection = true };
+	private void Open ()
+	{
+		var open = new OpenDialog ("Open") { AllowsMultipleSelection = true };
 
-			Application.Run (open);
+		Application.Run (open);
 
-			if (!open.Canceled) {
+		if (!open.Canceled) {
 
-				foreach (var path in open.FilePaths) {
+			foreach (var path in open.FilePaths) {
 
-					if (string.IsNullOrEmpty (path) || !File.Exists (path)) {
-						return;
-					}
-
-					// TODO should open in focused TabView
-					Open (new FileInfo (path), Path.GetFileName (path));
+				if (string.IsNullOrEmpty (path) || !File.Exists (path)) {
+					return;
 				}
-			}
-		}
 
-		/// <summary>
-		/// Creates a new tab with initial text
-		/// </summary>
-		/// <param name="fileInfo">File that was read or null if a new blank document</param>
-		private void Open (FileInfo fileInfo, string tabName)
-		{
-			var tab = new OpenedFile (focusedTabView, tabName, fileInfo);
-			focusedTabView.AddTab (tab, true);
-		}
-
-		public void Save ()
-		{
-			Save (focusedTabView, focusedTabView.SelectedTab);
+				// TODO should open in focused TabView
+				Open (new FileInfo (path), Path.GetFileName (path));
+			}
 		}
-		public void Save (TabView tabViewToSave, Tab tabToSave)
-		{
-			var tab = tabToSave as OpenedFile;
+	}
 
-			if (tab == null) {
-				return;
-			}
+	/// <summary>
+	/// Creates a new tab with initial text
+	/// </summary>
+	/// <param name="fileInfo">File that was read or null if a new blank document</param>
+	private void Open (FileInfo fileInfo, string tabName)
+	{
+		var tab = new OpenedFile (_focusedTabView, tabName, fileInfo);
+		_focusedTabView.AddTab (tab, true);
+	}
 
-			if (tab.File == null) {
-				SaveAs ();
-			}
+	public void Save ()
+	{
+		Save (_focusedTabView, _focusedTabView.SelectedTab);
+	}
+	public void Save (TabView tabViewToSave, Tab tabToSave)
+	{
+		var tab = tabToSave as OpenedFile;
 
-			tab.Save ();
-			tabViewToSave.SetNeedsDisplay ();
+		if (tab == null) {
+			return;
 		}
 
-		public bool SaveAs ()
-		{
-			var tab = focusedTabView.SelectedTab as OpenedFile;
-
-			if (tab == null) {
-				return false;
-			}
+		if (tab.File == null) {
+			SaveAs ();
+		}
 
-			var fd = new SaveDialog ();
-			Application.Run (fd);
+		tab.Save ();
+		tabViewToSave.SetNeedsDisplay ();
+	}
 
-			if (string.IsNullOrWhiteSpace (fd.Path)) {
-				return false;
-			}
+	public bool SaveAs ()
+	{
+		var tab = _focusedTabView.SelectedTab as OpenedFile;
 
-			if (fd.Canceled) {
-				return false;
-			}
+		if (tab == null) {
+			return false;
+		}
 
-			tab.File = new FileInfo (fd.Path);
-			tab.Text = fd.FileName;
-			tab.Save ();
+		var fd = new SaveDialog ();
+		Application.Run (fd);
 
-			return true;
+		if (string.IsNullOrWhiteSpace (fd.Path)) {
+			return false;
 		}
 
-		private class OpenedFile : Tab {
-			public FileInfo File { get; set; }
+		if (fd.Canceled) {
+			return false;
+		}
 
-			/// <summary>
-			/// The text of the tab the last time it was saved
-			/// </summary>
-			/// <value></value>
-			public string SavedText { get; set; }
+		tab.File = new FileInfo (fd.Path);
+		tab.Text = fd.FileName;
+		tab.Save ();
 
-			public bool UnsavedChanges => !string.Equals (SavedText, View.Text);
+		return true;
+	}
 
-			public OpenedFile (TabView parent, string name, FileInfo file)
-				: base (name, CreateTextView (file))
-			{
+	private class OpenedFile : Tab {
+		public FileInfo File { get; set; }
 
-				File = file;
-				SavedText = View.Text;
-				RegisterTextViewEvents (parent);
-			}
+		/// <summary>
+		/// The text of the tab the last time it was saved
+		/// </summary>
+		/// <value></value>
+		public string SavedText { get; set; }
 
-			private void RegisterTextViewEvents (TabView parent)
-			{
-				var textView = (TextView)View;
-				// when user makes changes rename tab to indicate unsaved
-				textView.KeyUp += (s, k) => {
+		public bool UnsavedChanges => !string.Equals (SavedText, View.Text);
 
-					// if current text doesn't match saved text
-					var areDiff = this.UnsavedChanges;
+		public OpenedFile (TabView parent, string name, FileInfo file)
+			: base (name, CreateTextView (file))
+		{
+			File = file;
+			SavedText = View.Text;
+			RegisterTextViewEvents (parent);
+		}
 
-					if (areDiff) {
-						if (!this.Text.EndsWith ('*')) {
+		private void RegisterTextViewEvents (TabView parent)
+		{
+			var textView = (TextView)View;
+			// when user makes changes rename tab to indicate unsaved
+			textView.KeyUp += (s, k) => {
 
-							this.Text = this.Text + '*';
-							parent.SetNeedsDisplay ();
-						}
-					} else {
+				// if current text doesn't match saved text
+				var areDiff = this.UnsavedChanges;
 
-						if (Text.EndsWith ('*')) {
+				if (areDiff) {
+					if (!Text.EndsWith ('*')) {
 
-							Text = Text.TrimEnd ('*');
-							parent.SetNeedsDisplay ();
-						}
+						Text = Text + '*';
+						parent.SetNeedsDisplay ();
 					}
-				};
-			}
+				} else {
 
-			private static View CreateTextView (FileInfo file)
-			{
-				string initialText = string.Empty;
-				if (file != null && file.Exists) {
+					if (Text.EndsWith ('*')) {
 
-					initialText = System.IO.File.ReadAllText (file.FullName);
+						Text = Text.TrimEnd ('*');
+						parent.SetNeedsDisplay ();
+					}
 				}
+			};
+		}
 
-				return new TextView () {
-					X = 0,
-					Y = 0,
-					Width = Dim.Fill (),
-					Height = Dim.Fill (),
-					Text = initialText,
-					AllowsTab = false,
-				};
-			}
-			public OpenedFile CloneTo (TabView other)
-			{
-				var newTab = new OpenedFile (other, base.Text.ToString (), File);
-				other.AddTab (newTab, true);
-				return newTab;
+		private static View CreateTextView (FileInfo file)
+		{
+			string initialText = string.Empty;
+			if (file != null && file.Exists) {
+
+				initialText = System.IO.File.ReadAllText (file.FullName);
 			}
-			internal void Save ()
-			{
-				var newText = View.Text;
 
-				System.IO.File.WriteAllText (File.FullName, newText);
-				SavedText = newText;
+			return new TextView () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = initialText,
+				AllowsTab = false,
+			};
+		}
 
-				Text = Text.TrimEnd ('*');
-			}
+		public OpenedFile CloneTo (TabView other)
+		{
+			var newTab = new OpenedFile (other, base.Text.ToString (), File);
+			other.AddTab (newTab, true);
+			return newTab;
 		}
 
-		private void Quit ()
+		internal void Save ()
 		{
-			Application.RequestStop ();
+			var newText = View.Text;
+
+			if (File is null || string.IsNullOrWhiteSpace (File.FullName)) {
+				return;
+			}
+
+			System.IO.File.WriteAllText (File.FullName, newText);
+			SavedText = newText;
+
+			Text = Text.TrimEnd ('*');
 		}
 	}
+
+	private void Quit ()
+	{
+		Application.RequestStop ();
+	}
 }

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.